Silverlight – koncepcja logowania z użyciem MembershipProviders oraz WCF RIA cz.1

W dzisiejszym wpisie postaram się przedstawić moją koncepcję logowania do aplikacji Silverlightowej z wykorzystaniem MembershipProviders oraz własnego AuthenticationService.
Zacznijmy od stworzenia bazy danych, w której będziemy przetrzymywać informacje o użytkownikach – hasła, role itp. Bazę danych stworzymy przy użyciu narzędzia aspnet_regsql.exe, które to wygeneruje schemat bazy danych dostosowany do możliwości SqlMembershipProvider-a. aspnet_regsql.exe znajduje się w katalogu C:WINDOWS\Microsoft.NET\Framework\wersjaFrameworka\aspnet_regsql.exe. Czyli w moim przypadku jest to C:WINDOWS\Microsoft.NET\Frameworkv\4.0.30319\aspnet_regsql.exe.
regsql
Klikając podwójnie na ikonę aplikacji pojawia nam się następujące okno
wizzard
Klikamy w przycisk “Dalej”. W kolejnym oknie mamy do wyboru dwie opcje

  • Configure SQL Server for application services
  • Remove application services from an existing database

Pierwsza opcja odpowiedzialna jest za stworzenie struktury danych do naszego sytemu logowania, natomiast druga usuwa z bazy danych tabele służące do logowania itp. Wybieramy opcję bramkę nr 1. W kolejnym oknie musimy podać connection string do naszego serwera bazy danych oraz login i hasło. W moim przypadku wygląda to w następujący sposób:

wizzard2

Jeżeli wszystko pójdzie OK pokaże się nam następujące okno, w którym to możemy zobaczyć, że została stworzona baz danych aspnetdb

wizzard3

Ostatecznie klikamy na przycisk “Next”,a następnie w “Finish” aby dokończyć działanie kreatora

wizzard4

Mając gotową bazę danych możemy przystąpić do właściwego kodowania. Zacznijmy od stworzenia nowego projektu typu “Silverlight Application”

silverlightapp
po kliknięciu przycisku “OK” włączamy WCF RIA Service
EnableWCF

Mając stworzony szkielet aplikacji możemy zabrać się za napisanie własnego AuthenticationService. Do projektu webowego (nazwaprojektu.Web) dodajmy nowy element typu “Domain Service”. PPM na projekt, następnie “Add new item”, w oknie które się otworzy wybieramy “Domain Service”. Nadajemu mu nazwę CustomAuthenticationService.cs

AddItemSmall

DomainServiceClass

Nasza właśnie dodana klasa powinna wyglądać mniej więcej w taki sposób

W następnym kroku zaimplementujmy w klasie CustomAuthenticationService interfejs
IAuthentication where T: IUser. Interfejs ten wygląda w następujący sposób:

Widzimy zatem, że zanim go zaimplementujemy musimy posiadać obiekt implementujący inny interfejs – IUser. Przystąpmy zatem do stworzenia naszej klasy modelującej użytkownika – nazwijmy ją UserDTO.Stwórzmy nowy projekt typu “Class library”. W projekcie tym stwórzmy klasę UserDTO, która będzie dziedziczyła po klasie UserBase znajdującej się w przestrzeni nazw System.ServiceModel.DomainServices.Server.ApplicationServices

Mając gotową klasę reprezentującą użytkownika zaimplementujmy w końcu w klasie CustomAuthenticationService interfejs IAuthentication. Po implementacji interfejsu nasz serwis wygląda w następujący sposób:

Pozostaje nam “tylko” zaimplementować odpowiednie metody. Funkcja która nas najbardziej interesuje to oczywiście funkcja logowania. Przy jej implementacji posłuży się mechanizmem znanym z ASP mianowicie z MembershipProviders. Klasa MembershipProvider jest to abstrakcyjna klasa posiadająca szereg funkcji mającej na celu walidowanie poprawnego użytkownika, rejestrację itp. Stwórzmy zatem własną klasę providera dziedziczącą po klasie MembershipProvicer. .NET dostarcza nam domyślny provider, którym jest SqlMembershipProvider, jest on napisany w taki sposób, aby mógł porozumiewać się z bazą, którą stworzyliśmy na początku. Aby wykorzystać wspomnianego wcześniej providera musimy odpowiednio zmodyfikować plik Web.config. Po pierwsze dodajemy do niego ConnectionString do naszej bazy danych, w moim przypadku będzie to wyglądało w następujący sposób

Następnie musimy “pokazać” naszej aplikacji, że będziemy używać FormsAuthentication oraz MembershipProvider-ow. Dorzućmy zatem do config następujące rzeczy:(w sekcji system.web)

następnie dorzucamy do kolekcji providerów SqlMembershipProvider

We wpisach tych dorzuciliśmy możliwość autentykacji poprzez providery, ustawiliśmy SqlMembershipProvider jako domyślny provider, oraz dorzuciliśmy go do listy wszystkich providerów. W celu sprawdzenia czy nasza konfiguracja jest poprawna możemy posłużyć się mechanizmem dostarczonym nam przez Visual Studio. Klikamy “Projekt”, a następnie ASP.NET Configuration
ASPConfiguration
Zostaniemy przeniesieni do domyślnej przeglądarki internetowej, a naszym oczom ukaże się następująca strona.
AdminTool
Z poziomu tej stronki możemy dodawać użytkowników, przydzielać im role itp, dzięki czemu możemy szybko sprawdzić czy podstawowe funkcje naszego providera działają, oraz czy nasza konfiguracja została przeprowadzona prawidłowo. Wracając natomiast do naszego systemu logowania, pozostało nam jedynie odpowiednio zmodyfikować funkcję odpowiedzialną za logowanie.Robimy to w następujący sposób:

W funkcji ValidateCredentials odbywa się sprawdzanie czy użytkownik podał właściwe hasło oraz login. Do walidacji wykorzystujemy dodany wcześniej SqlMembershipProvider – właściwość Membership.Provider zawsze zwraca obiekt domyślnie zdefiniowanego providera. W przypadku gdy walidacja się powiedzie tworzymy obiekt użytkownika, a następnie tworzymy i szyfrujemy FormsAuthenticationTicket oraz tworzymy ciasteczko (cookie), które przesyłamy w responsie wysyłanym przez serwer do klienta. Mechanizm logowania jest już prawie gotowy. Musimy jedynie zaimplementować jeszcze pozostałe funkcje interfejsu IAuthentication.Nie ma raczej w nich nic trudnego

Funkcja Logout po prostu wywołuje funkcję SingOut z klasy FormsAuthentication. Natomiast funkcja GetUser zwraca zalogowanego użytkownika. W funkcji tej najpierw sprawdzamy czy użytkownik jest zalogowany (do tego celu używamy zmiennej IsAuthenticated), a następnie sprawdzamy czy przypadkiem jego sesja nie wygasła. W przypadku gdy wszystko jest OK zwracamy obiekt typu UserDTO,w przeciwnym razie zwracamy null.

Podsumowując, udało nam się stworzyć mechanizm logowania. Jak na razie gotowa jest strona serwera.
W następnym wpisie pokażę w jaki sposób należy wywołać logowanie od strony klienta. Przedstawię również pomysł w jaki sposób zmieniać providerów przez które użytkownik ma się logować. Może to być przydatne jeżeli chcielibyśmy logować się np. poprzez inne serwisy jak Google, Facebook itp.

Silverlight – koncepcja logowania z użyciem MembershipProviders oraz WCF RIA cz.1

Coded UI Test – tworzenie testów UI przy pomocy Visual Studio Ultimate

Poszukując sposobów automatycznego testowania aplikacji natrafiłem na ciekawą funkcjonalność Visual Studio 2010 Ultimate. Mianowicie w tej wersji naszego ulubionego IDE znalazło się miejsce na nowy typ testów – Coded UI Test. Coded UI Test jest to automatyczny test UI, który tworzymy poprzez nagrywanie akcji jakie wykonujemy w naszej aplikacji. Nie musimy pisać ani jednej linijki kodu żeby przetestować jakąś funkcjonalność naszego programu. Jedyne co musimy zrobić jest to przejście przez wszystkie niezbędne kroki w naszej aplikacji, tak aby zweryfikować naszą funkcjonalność. W celu stworzenia Coded UI Test musimy po pierwsze zaopatrzyć się w wersję Ultimate Visual Studio 2010. Z tego co wiem, niestety niższe wersje nie mają funkcjonalności automatycznego testowania. Następnie musimy stworzyć nowy Test Project. W tym celu z menu File wybieramy New a następnie Project. W oknie, które się pokaże, w drzewie po lewej stronie wybieramy Test, a następnie w prawej części okna Test Project
Project
Mając stworzony projekt testowy dodajemy do niego nowy Coded UI Test. W Solution Explore-rze klikamy PPM na projekt testowy, a następnie z menu contextowego wybieramy Coded UI Test.
CodedUITest
W oknie, które się pojawi wybieramy Record actions,edit UI map or add assertion lub gdy mamy już gotowe testy możemy wybrać opcję nr 2. Po wybraniu pierwszej opcji okno Visual Studio zostanie zminimalizowane, a w prawym dolnym rogu pulpitu ukaże się następujące okienko.
Record
Służy ono do nagrywania testu. Naciskając czerwony przycisk rozpoczynamy proces nagrywania testu. W moim przypadku będę testować aplikację, którą testowałem również w poprzednim poście. Wykonuje zatem czynności mające na celu sprawdzenie funkcjonalności dodawania liczb. Po kliknięciu na textbox-a pojawia się nad nim dymek,że dana akcja jest nagrywana.
UI
Po "wyklikaniu" całej ścieżki mającej sprawdzić funkcjonalność klikamy przycisk "Generate code”" (ten najbardziej z prawej strony). Następnie w okienku, które się pojawi
MethodName
podajemy nazwę naszego testu. Po kliknięciu OK, Visual Studio wygeneruje nam kod odpowiedzialny za nasz test.
SourceUIMap
W celu odpalenia naszego testu z menu Test wybieramy Run, a następnie Tests in Current Context. Visual Studio przejdzie teraz wszystkie kroki, które zostały nagrane w teście. Ok, niby wszystko ładnie pięknie, ale tak naprawdę nic nie sprawdziliśmy. Jedyne co zrobiliśmy to wykonaliśmy pewne operacje na naszej aplikacji, jednakże nie zweryfikowaliśmy danych otrzymanych w wyniku operacji dodaj. W celu dodania warunku sprawdzającego do naszego testu, musimy dodać tzw. asercję. Aby to zrobić klikamy PPM na wolne pole edytora, pod funkcją this.UIMap.RecordedMethod1() (ta funkcja została wygenerowana podczas nagrywania testu). Następnie z menu contextowego wybieramy
Generate
Otwiera nam się znane już wcześniej okienko nagrywania testu. Jednakże tym razem naciśnijmy przycisk celownika i przeciągnijmy go na textbox-a, w którym ma pojawić się wynik dodawania
AutomationAdding
Nasz textbox zostanie obramowany, natomiast w prawym dolnym rogu pulpitu pojawi się nowe okno pokazujące propertisy naszego przycisku. Wybierzmy właściwość Text ,a następnie naciśnijmy przycisk Add Assertion
Assertion
W oknie, które się pojawi możemy wybrać warunek, który musi spełnić dana właściwość (w tym przypadku Text), aby test przeszedł. Ustawmy tam wartość np. 3. Następnie klikamy OK oraz nadajemy nazwę naszej assercji.Po wykonaniu tych czynności w kodzie naszego pojawi się dodatkowa linijka
AddAssertion
Teraz za każdym razem gdy odpalimy test na samym jego końcu będzie sprawdzana nasza assercja. W przypadku gdy wartość właściwości Text nie będzie równa 3 nasz test nie przejdzie.
Coded UI Test

Coded UI Test – tworzenie testów UI przy pomocy Visual Studio Ultimate

UI Automation – czyli testy automatyczne w .NET

Biblioteka Microsoft UI Automation ujrzała światło dzienne wraz z premierą .NET 3.0 – jednakże pozostała ona w cieniu swoich większych braci WPF oraz WPF, które również zostały wprowadzone do Frameworka 3.0. Microsoft UI automation zapewnia nam dostęp do wszystkich elementów drzewa wizualnego aplikacji. Dzięki czemu mamy możliwość:

  • Znajdowania wybranych przez nas kontrolek
  • Interakcji z kontrolkami – wpisywanie tekstów do TextBox-ów, klikanie w przyciski itp
  • Wczytywania wartości już wprowadzonych do kontrolek

Każda kontrolka znajdująca się na widoku jest traktowana przez UI Automation jako AutomationElement. Rootem naszego drzewa wizualnego pulpit, dostęp do niego uzyskujemy poprzez statyczną właściwość

Mając dostęp do roota możemy następnie poruszać się po drzewie tak aby znaleźć interesujące nas elementy. Załóżmy, że napisaliśmy prostą aplikację WPF-ową, która wykonuje podstawowe obliczenia matematyczne (pomysł na prostą aplikację do testowania zaczerpnięty z tej strony – kod jednak pisałem po swojemu :D).
Wizualnie aplikacja ta przedstawia się w następujący sposób:

W celu poruszania się po drzewie wizualnym możemy skorzystać z dwóch metod

pierwsza z nich zwraca nam konkretny element, który spełnia podane przez nas kryterium. Pierwszy parametr funkcji TreeScope scope określa nam sposób przeszukiwania drzewa wizualnego. Dostępne opcje to:

  • Element – wyszukiwanie odbywa się tylko na danym elemencie
  • Children – wyszukiwanie odbywa się na wszystkich dzieciach danego elementu – nie zagłębiamy się rekurencyjnie,sprawdzamy tylko jeden poziom w dół od danego elementu
  • Descendants – wyszukiwanie odbywa się na wszystkich potomkach – przeszukiwanie rekurencyjne w dół
  • Subtree – wyszukiwanie odbywa się na wszystkich potomkach, oraz elemencie od którego zaczynamy
  • Ancestors – wyszukiwanie odbywa się na wszystkich rodzicach – rekurencyjnie w górę drzewa

Drugim z parametrów jakie musimy podać jest to UIAutomation.Condition. Parametr ten określa jaki warunek musi spełnić dany element aby został uwzględniony w wyniku wyszukiwania. Sama klasa Condition jest klasą abstrakcyjną, z której dziedziczą wyspecjalizowane klasy. Do określenia warunków najczęściej będziemy korzystać z potomka klasy Condition, mianowicie z

określa ona jaka właściwość kontrolki/aplikacji będzie uwzględniana przy wyszukiwaniu wyniku.

Pierwszym krokiem przy testowaniu aplikacji będzie oczywiście znalezienie głównego okna programu. Mając w solucji stworzony projekt z aplikacją WPF-ową, dodajmy drugi projekt – tym razem będzie to projekt typu ConsoleApplication.Następnie musimy dodać referencję do odpowiednich ddl-ek. Potrzebujemy następujących bibliotek:

  • UIAutomation.dll
  • UIAutomationClient.dll
  • UIAutomationType.dll

Następnie w naszym projekcie konsolowym w metodzie main odpalamy aplikację, którą chcemy testować. W moim przypadku wygląda to w następujący sposób:

Następnie musimy dostać się do głównego okna testowanej aplikacji. Możemy to zrobić używając funkcji FindFirst:

lub możemy uzyskać dostęp do okna przy pomocy funkcji

W pierwszym przypadku wykorzystując funkcję FindFirst przeszukujemy wszystkie dzieci (TreeScope.Children) roota (czyli pulpitu). Jako warunek wyszukiwania podaliśmy obiekt klasy PropertyCondition. Jako właściwość do porównywania podaliśmy id processu (AutomationElement.ProcessIdProperty). W drugim przypadku po prostu uzyskujemy dostęp do okna dzięki znajomości jego uchwytu (właściwość MainWindowHandle z klasy Process). Według mnie lepiej skorzystać z funkcji pierwszej, zwłaszcza gdy odpalamy aplikację, która się długo uruchamia. W pierwszym przypadku wystarczy zrobić odpowiednią funkcję oczekującą na odpalanie (gdyż funkcja zwróci nam null gdy okna jeszcze nie będzie), natomiast druga opcja rzuci nam wyjątek. Oczekiwania na załadowanie okna może wyglądać w następujący sposób

Mając dostęp do głównego okna możemy przeprowadzić testy – przetestujemy czy po wpisaniu danych do tekstboksów i przeprowadzeniu odpowiednich akcji (dodawanie,usuwanie,odejmowanie,dzielenie) w polu wynik pojawi się odpowiednia wartość. Zacznijmy od zlokalizowania textboxów. Posłużmy się tutaj przedstawioną wcześniej funkcją FindFirst.

Wyszukiwanie odbywa się po właściwości AutomationElement.AutomationIdProperty – właściwość ta jest zawsze taka sama jak nazwa kontrolki – chyba, że ktoś ją zmieni przy pomocy AttachedProperty AutomationProperties.AutomationId. Odpalając powyższy kod okazuje się, że nasza zmienna firstTextbox jest nullem – czyli framework nie był w stanie znaleźć naszego textboxa. Dlaczego tak się stało?? Odpowiedzi może nam dostarczyć aplikacja Snoop, dzięki której możemy podejrzeć drzewo wizualne naszego programu.
snoopResult
Na powyższym screenie widzimy, że szukany textbox nie jest bezpośrednim dzieckiem MainWindow, jest natomiast dzieckiem grida (tego można było się spodziewać zwłaszcza, że umieściliśmy textbox w gridzie). Zauważmy jednak, że MainWindow nie jest bezpośrednim rodzicem grida – znajduje się on dopiero na poziomie 4 w drzewie. Z racji dużego skomplikowania drzewa wizualnego aplikacji według mnie bezpiecznie jest używać funkcji Find z parametrem TreeScope.Descendants. Unikniemy dzięki temu przykrych niespodzianek z nieodnalezionymi kontrolkami. Nasza funkcja zatem powinna wyglądać w następujący sposób.

W analogiczny sposób znajdujemy pozostałe przyciski na naszej formie, a następnie postarajmy się wprowadzić do nich jakiś text. W celu interakcji z kontrolkami wykorzystamy tzw. Patterns
Patterns definiują określone funkcjonalności, które wspiera nasza kontrolka. Do najczęściej używanych patterns należą:

  • SelectionPattern – używany do manipulacjami kontrolkami wspierającymi zaznaczanie np.ListBox-ami
  • TextPattern – używany do manipulacjami kontrolkami wspierającymi edycję
  • ValuePattern – używany do pobierania i ustawiania wartości kontrolek nie wspierających wielokrotnych wartości
  • InvokePattern – używane do kontrolek wspierających wywołania – np. przyciski (wywołanie przyciśnięcia)
  • ScrollPattern – używane do kontrolek posiadających ScrolBar-y
  • RangeValuePattern – używany do kontrolek mogących posiadać jakiś zakres wartości np. ComboBox

Zatem w celu ustawienia wartości w textbox-ie posłużymy się następującym kodem

W analogiczny sposób ustawiamy zawartość drugiego textbox-a

Mając wprowadzone dane do textbox-ów wypadałoby teraz przeprowadzić obliczenia. Zatem musimy w jakiś sposób aby “nacisnąć” jeden z naszych czterech przycisków. Znajdźmy zatem nasze przyciski:

Następnie wykorzystując wspomniany wcześniej InvokePattern naciśnijmy przycisk “Dodaj”.

Ostatnim krokiem będzie zweryfikowanie czy wartość w textbox-ie txtResult jest zgodna z oczekiwaniami. W celu wyciągnięcia wartości z txtResult po raz kolejny posłużę się ValuePattern

Wynik otrzymany w textbox-ie porównujemy z oczekiwanym rezultatem

Na koniec najlepiej wrzucić nasz test w jakąś pętle, losowo generować wartości oraz porównywać je z wartościami oczekiwanymi. Dobrym pomysłem jest również stworzenie sobie jakiejś klasy pomocniczej, w której zostałyby umieszczone funkcje do wypełniania textbox-ow, klikania w buttony itp – gdyż jak widać dużo kodu jest powtarzalna. Ostatecznie aplikacja testowa może wyglądać w następujący sposób:

oraz klasa pomocnicza

result

UI Automation – czyli testy automatyczne w .NET

MVVM i okna modalne

Często podczas użytkowania programów zachodzi konieczność pokazania dodatkowego okna, służącego do interakcji z użytkownikiem. Najczęściej okno takie należy pokazać w określonym przypadku. Jak już wcześniej zostało wspomniane cała logika powinna być umieszczona w ViewMoedlu, zatem nasuwa się pytanie, w jaki sposób z ViewModelu pokazać okno modalne – przecież ViewModel nie powinien mieć żadnych informacji o widoku. Rozwiązanie tego problemu polega na oddelegowaniu pokazywania widoków lub MessageBox-ów do wyspecjalizowanych klas tzw. serwisów. Ponadto w przypadku gdy chcemy pokazywać proste komunikaty możemy również skorzystać z Interaction/Interactivity oraz klasy Prisma InteractionRequest.

1. InteractionRequest

Jak już wcześniej wspomniano klasa InteractionRequest odpowiedzialna jest za pokazywanie komunikatów użytkownikowi – koordynuje ona działanie pomiędzy ViewModelem a widokiem. W celu skorzystania z klasy InteractionRequest w ViewModelu tworzymy publiczną właściwość udostpęniającą obiekt typu InteractionRequest na zewnątrz

Jak widać za generyczny typ T została podstawiona klasa Notification. Parametr T może przyjmować klasy dziedziczące po klasie Notification. Domyślnie w Prismie są dwie takie klasy:

  • Notification – wspomniana już wcześniej, służy do powiadomienia użytkownika o jakimś zdarzeniu
  • Confirmation – również służy powiadominiu użytkownika o jakiś zdarzeniu, ale zwraca ona reakcję użytkownika (czy potwierdził daną akcję czy nie – coś jak MessageBox YesNo)

W celu wywołania okienka należy odpalić funkcję Raise na przykład w taki sposób:

Funkcja Raise przyjmuje jeden lub dwa parametry. Przedstawy bardziej zaawansowaną wersję tej funkcji – tą z dwoma parametrami. W pierwszym parametrze podajemy obiekt typu Notification, w którym przekazujemy informacje do przekazania użytkownikowi (Content) oraz tytuł okna (Title). Drugim parametrem jest delegat Action , który określa co mamy zrobić po zamknięciu okna przez użytkownika.
Ostatnią rzeczą jaką musimy zrobić, żeby wykorzystać NotificationRequest jest zdefiniowanie triggera w widoku, który będzie reagował na odpalenie funkcji Raise. Robimy to w następujący sposób:

  • definiujemy w XAML-u alias do namespaca interactivity
  • tworzymy InteractionRequestTrigger

2. MessageBoxService

Alternatywą dla InteractionRequest jest stworzenie własnej klasy, do której zostałyby oddelegowane wszystkie prośby o pokazania MessageBoxa. Przykładowa implementacja takiego serwisu może wyglądać w następujący sposób. Po pierwsze stwórzmy interfejs z funkcjami potrzebnymi do pokazania MessageBoxa

Następnie zaimplementujmy ten interfejs w klasie MessageBoxService – właśnie do tej klasy będą oddelegowywane wszystkie prośby o pokazanie MessageBoxa

Taki serwis musimy następnie przekazać do każdego ViewModelu , w którym będzie potrzeba pokazania MessageBoxa. Jako, że ręczne przekazywanie tego obiektu może być nużące, możemy skorzystać z dobrodziejstw Inversion of Control. Ja skorzystałem z kontenera z Unity. Po pierwsze musimy zarejestrować nasz typ w kontenerze

następnie MessageBoxService może zostać wstrzyknięty do każdego ViewModelu przy np. przy pomocy wstrzykiwania właściwości.

Oczywiście żeby nasza właściwość została wstrzyknięta, nasz ViewModel musi zostać stworzony przy użycia kontenera IO
Zauważmy, że właściwość MessageBoxService nie jest konkretnym typem, lecz interfejsem. Dzięki takiemu podejściu nasza aplikacja wciąż jest w łatwy sposób testowalna oraz ViewModel nie ma nic wspólnego z klasami widoku.

3. ModalWindowService

Do pokazywania widoków(jako okien) z poziomu ViewModelu posłużmy się podobnym mechanizmem jak w przypadku pokazywania MessageBox-ów. Zasada działania klasy, która stworzymy będzie taka sama, jednakże będziemy musieli odrobinę poszerzyć jej funkcjonalność. Po pierwsze tak jak poprzednio stwórzmy odpowiedni interfejs – IModalWindowService.
UWAGA
Przykład ModalWindowServicu zostanie zaprezentowany dla Silverlight-a, gdyż okna modalne w silerlighcie to tak naprawdę okna semi-modalne. Główny wątek się nie zatrzymuje, jednakże użytkownik nie ma możliwości interakcji z oknami będącymi pod aktualnie widocznym oknem.

Zauważmy, że funkcja ShowDialog w parametrze nie przyjmuje konkretnego widoku, lecz interfejs, który każde okno modalne powinno implementować – IModalView. Interfejs ten może wyglądać w następujący sposób:

Jako, że okna modalne w Silverlighcie są tak naprawdę oknami semi-modalnymi (patrz wytłumaczenie w sekcji UWAGA) musimy wiedzieć kiedy dane okno zostanie zamknięte. Dlatego, też interfejs ten zawiera zdarzenie

ponadto wypadałoby sprawdzić czy dane wpisane w oknie są poprawne, dlatego też dodano zdarzenie

w przypadku gdy dane wpisane w oknie nie będą poprawne, nasz ModalDialogService zapobiegnie zamknięciu okna. Ostatecznie nasza klasa ModalDialogService może wyglądać w następujący sposób:

Nasz serwis podobnie jak w przypadku MessageBoxServicu rejestrujemy w kontenerze, a następnie wstrzykujemy go do ViewModelu

Pozostaje nam jedynie kwestia wyjaśnienia w jaki sposób wywołać nasze okno. Nie powinniśmy tworzyć okna w ViewModelu, gdyż łamie to zasadę MVVM-a. Dlatego też po raz kolejny skorzystamy z kontenera IoC. W kontenerze IoC rejestrujemy sobie nasz widok

jako, że ViewModel jest wstrzykiwany do View poprzez Dependency Injection należy również w kontenerze zarejestrować odpowiedni typ ViewModelu – czyli w tym przypadku AddPersonViewModel

Mając już zarejestrowane w kontenerze wszystkie niezbędne typu możemy wywołać okno z ViewModelu w następujący sposób:

Obiekt widoku otrzymujemy z kontenera IoC wykorzystując funkcję Resolve, w miejsce LambdaExpression wstawiamy nasze wyrażenie w którym określamy co zrobimy po poprawnym zamknięciu okna. Zauważmy, że ViewModel nie ma żadnych informacji o widoku – kontener nie zwraca nam konkretnego typu lecz interfejs. Dzięki takiemu podejściu nasza aplikacja może być w łatwy sposób testowalna.

MVVM i okna modalne

Interactivity oraz Interaction

Interactions oraz Interactivity są to dwie dll-ki, które poszerzają sposób komunikacji pomiędzy widokiem oraz ViewModelem. Wprowadzają one nowy typ EventTriggerów(w Silverlighcie nie ma triggerów, natomiast w WPF-ie event triggery można praktycznie używać jedynie do animacji) oraz dodatkowo wprowadzają ciekawe sposoby(funkcje) na interakcję widoku z ViewModelem. Pierwszym z tych sposobów jest użycie właściwości InvokeCommandAction.Jak sama nazwa wskazuje można wywołać komendę z ViewModelu. Jaka jest różnica między wywołaniem komendy poprzez InvokeCommandAction, a Command=”{Binding CommandName}” ? InvokeCommandAction ma tą przewagę nad normalnm bindowaniem do property Command,że pozwala wywołać daną komendę w odpowiedzi na zajście jakiegoś zdarzenia na widoku. Przykładowe użycie może wyglądać następująco:

Powyższy przykład działa w następujący sposób. Za każdym razem gdy zostanie wywołane zdarzenie MouseMove na przycisku, odpalona zostanie komenda SubmitCommand. Wadą używania InvokeCommandAction jest to, że sami musimy się martwić o wygaszenie kontrolki w przypadku gdy dana komenda nie może być wykonana. Dlatego też w pierwszej linii mamy bindowanie właściwości IsEnabled przycisku do właściwości CanSubmit z ViewModelu. Alias i: jest aliasen na namespace interactivity z dll-ki

Drugim sposobem powiadomiania ViewModelu o jakimś zdarzeniu w widoku jest użycie InvokeMethodAction. Dzięki tej właściwości możemy bezpośrednio z widoku (z XAML-a) wywołać funkcję w ViewModelu. Składnia przedstawia się w następujący sposób:

TargetObject jest zbindowany do DataContextu, to właśnie tam będzie szukana funkcja Submit. Należy pamiętać, że właściwość CallMethodAction nie wspiera parametrów funkcji. Zatem jeżeli musimy wywołać jakąś funkcję z parametrami, należy opakować ją w funkcję bezparametrową, natomiast wszystkie parametry przekazać z właściwości ViewModelu.

Kolejnym ciekawym featurem Interactivity jest klasa Behavior. Jest niezastąpiona jeżeli potrzebujemy ingerować w działanie widoku po jakimś zdarzeniu. Nie musimy wówczas tworzyć obsługi eventów w code behind. Możemy wyłuskać daną funkcjonalność i zamknąć ją w klasie Behavior a następnie doczepić do wszystkich widoków danego typu. Załóżmy, że sterujemy ListBoxem z poziomu ViewModelu (przełączamy SelectedItem). Możemy zaznaczyć jakiś Item, co jednak jeżeli nasza lista jest bardzo długa,a zaznaczamy ostatni element. Na widoku element rzeczywiście się zaznaczył jednak lista się nie przewinęła. Przez co cały czas wiszą elementy z początku i nie widać czy któryś element jest zaznaczony. Dopiero gdy przeskrolujemy ręcznie ListBoxa widzimy, że rzeczywiście prawidłowo zareagował na zmianę property w ViewModelu. Jest to idealna sytuacja, aby stworzyć własny Behavior, który będziemy mogli podczepić do wszystkich listboxów w każdym widoku.

Metoda OnAttached odpala się w momencie, gdy behavior jest podpinany do kontrolki. W metodzie tej powinniśmy podpiąć się pod wszystkie eventy, które są dla nas istotne. W momencie, gdy widok jest niszczony wywołuje się metoda OnDetaching, w której powinniśmy odpiąć wszystkie metody od eventów.

Interactivity oraz Interaction