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

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

Prism – CommandBehaviors

Analizując wcześniejsze przykłady pokazujące użycie DelegateCommand można odnieść wrażenie, że mimo tego iż są one bardzo użyteczne,ich użycie jest niestety bardzo ograniczone.
Jedynie niektóre komponenty WPF-a i Silverlight-a mają właściwość Command do której można zbindować nasz obiekt DelegateCommand.Ponadto właściwość ta reaguje jedynie na wybrane zahardcodowane w kontrolce zdarzenie (zdarzenie Click). Co w przypadku gdybyśmy chcieli zareagować np. na zdarzenie MouseMove za pomocą komendy? Nasz problem możemy rozwiązać poprzez:

  • Interactivity
  • Interactions
  • CommandBehaviors

W tym poście zostanie przedstawione rozwiązanie trzecie – CommandBehaviors

Pierwszą rzeczą jaką należy zrobić w celu stworzenia komendy reagującej na inne zdarzenie niż Click jest stworzenie klasy dziedziczącej po CommandBehaviorBase. Załóżmy, że zrobimy komendę reagującą na zdarzenie MouseMove

Piersza część już za nami, ale pojawia się teraz pytanie jak z tego skorzystać. Żeby mieć możliwość użycia powyżej klasy wykorzytamy attached dependency property. Musimy sobie stworzyć pomocniczą statyczną klasę, w której to zarejestrujemy nasze właściwości. Zacznijmy od zarejestrowania dependency property o nazwie Command – do tego obiektu będziemy bindowali komendy z ViewModelu (komendy reagujące na zdarzenie MouseMove)

zauważmy, że podłączyliśmy event handlera reagującego na zmianę naszej nowo stworzonej właściwości Command. Funkcja OnSetCustomCommandCallback wygląda następująco:

Funkcja ta po prostu w razie potrzeby tworzy nowy obiekt typu MouseMoveCommandBehavior (gdy ktoś zbinduje komende) i dopina go do naszej kontrolki. Pomocnicza funkcja GetorCreateMouseMoveBehavior wygląda tak:

Obiekt rejestrowany poprzez funkcję GetorCreateMouseMoveBehavior jest dopinany do kolejnej attached property, która również została zadeklarowana w tym pliku

Mając zdefiniowane wszystkie powyższe właściwości możemy już wykorzystać naszą komendę. Jednakże przydałoby się dodać jeszcze jedną właściwość (opcjonalnie), mianowicie właściwość CommandParameters

W tym propertisie również reagujemy na przypisnie obiektu.Handler OnSetCustomCommandParameterCallback wygląda następująco

Mając już zdefiniowane wszystkie niezbędne właściwości możemy użyć ich w XAML-u. Po pierwsze musimy dodać alias do namespaca gdzie znajduje się nasza statycza klasa MouseMove. W moim przypadku wygląda to następująco:

Następnie bindujemy jakąś kontrolkę do naszego attached property Command. Wygląda to w następujący sposób:

Od teraz za każdym razem jak najedziemy na txtButton wywoła się komenda SomeCommandFromViewModel.
Podsumowując trzeba się całkiem sporo napisać żeby zmusić do działania CommandBehaviors . W następnym poście przedstawię dwa sposoby na osiągnięcie tego samego efektu mniejszym kosztem.

Prism – CommandBehaviors

Prism – Komendy

1. DelegateCommands

DelegateCommands w Prismie są to obiekty, które implementują interfejs ICommand, służą one do interakcji widoku z ViewModelem. Korzystając z Prisma nie musimy już pisać własnej klasy implementującej ten interfejs (tak jak to zrobiliśmy tutaj). Konstruktor klasy DelegateCommand przyjmuje dwa parametry:

  • Action execteMethod – funkcja odpalania przy wywołaniu komendy,
  • Func canExecuteMethod – funkcja sprawdzająca czy daną komendę można wywołać

DelegateCommands z Prisma używamy w taki sam sposób w jaki używaliśmy komend w tym poście.

2. CompositeCommands

CompositeCommands są to obiekty, które przechowują kolekcją obiektów typu DelegateCommand. Tworzenie CompositeCommand jest bardzo proste.
Po pierwsze musimy stworzyć obiekt typu CompositeCommand

następnie rejestrujemy w tym obiekcie obiekt implementujący interfejs ICommand, czyli w naszym przypadku jakiś obiekt typu DelegateCommand. Robimy to w następujący sposób:

w razie potrzeby możemy usunąć komendę z kolekcji komend przy pomocy następującej składni:

Mając już stworzony obiekt typu CompositeCommand oraz zarejestrowane w nim obiekty DelegateCommand możemy zastosować bindowanie.

Od teraz, w chwili naciśnięcia przycisku, najpierw sprawdzane jest czy dana CompositeCommand może zostać wykonana. CompositeCommand może zostać wykonana tylko wtedy, gdy wszystkie komendy do niej podpięte mogą zostać wykonane. Jeżeli warunek ten zostanie spełniony następuje wywoływanie po kolei wszystkich metod execute z komend podpiętych do danej CompositCommand. CompositeCommand znajduje zastosowanie w przypadku gdy pozwalamy użytkownikowi na edycję wielu elementów jednocześnie i potrzebujemy w prosty sposób przeprowadzić walidację na poszczególnych widokach.
Komendy
CompositeCommand może również wykorzystana np. do zapisania tylko aktywnego widoku. Dzięki temu, że DelegateCommand implementuje interfejs IActiveAware, CompositeCommand wie, który widok jest aktualnie zaznaczony i zapisze tylko wybrany widok.

Interfejs IActiveAware wygląda w następujący sposób

  • bool IsActive – wskazuje czy dana komenda jest aktualnie aktywna
  • event EventHandler IsActiveChanged – informuje o zmianie aktywności komendy

W celu wykorzystania tego interfejsu przez CompositeCommand należy skorzystać z drugiego dostępnego konstruktora tej klasy

Od teraz po naciśnięciu przycisku zbindowanego do tej komendy, wywołana zostanie jedynie aktywna komenda – czyli taka, której właściwość IsActive została ustawiona na true. Niestety o ustawianie aktywności danej komendy musimy zadbać sami.

Prism – Komendy

Walidacja

Walidacja jest to technika sprawdzająca, czy dany obiekt spełnia pewne założenia poprawności danych. W WPF-ie oraz Silverlighcie istnieją trzy sposoby walidacji obiektów:

  • walidacja poprzez rzucanie wyjątków,
  • walidacja z użyciem interfejsu IDataErrorInfo,
  • walidacja z użyciem interfejsu INotifyDataErrorInfo

1. Walidacja poprzez rzucanie wyjątków

Walidacja poprzez rzucanie wyjątków odbywa się w następujący sposób. W seterze danej właściwości dodajemy warunek sprawdzający czy wpisane dane są poprawne. Jeżeli nie to najzwyczajniej w świecie rzucamy wyjątek, w którym podajemy komunikat błędu. Przykładowy properties z walidacją może wyglądać w ten sposób:

W celu “wyłapania” tego wyjątku i pokazania odpowiedniego komunikatu,w bindingu musimy ustawić właściwość ValidatesOnExceptions na wartość true.

Taki sposób walidowania jest jednak rzadko stosowany i wielu programistów twierdzi, że rzucanie wyjątków powinno się odbywać tylko w przypadku nieprawidłowego działania aplikacji. Ponadto walidowane propertisy nie mogą być autopropertisami, co dodatkowo wydłuża czas tworzenia klas.

2. Walidacja z użyciem interfejsu IDataErrorInfo

W celu wyłapywania błędów w widoku, należy w bindingu ustawić ValidatesOnDataError = true

Przykładowa klasa implementująca interfejs IDataErrorInfo może wyglądać następująco:

Najważniejszą metodą w powyższej klasie jest indekser

to właśnie tutaj mogą zostać sprawdzone wszystkie właściwości danego obiektu – propertyName oznacza nazwę propertisu, który walidujemy. W przypadku, gdy wartość jakiejś właściwości jest nieprawidłowa, w pole result wpisujemy komunikat błędu. Komunikaty te “wyłapywane” są przez widok, a następnie wyświetlane w postaci komunikatów przy odpowiednich kontrolkach. Jeżeli wszystko jest OK zwracamy string.Empty.Walidacje przy pomocy interfejsu IDataErrorInfo idealnie nadają się do walidowania modelu.

3. Walidacja z użyciem interfejsu INotifyDataErrorInfo

Interfejs INotifyDataErrorInfo prezentuje się w następujący sposób:

  • bool HasErrors – określa czy dany obiekt zawiera błędy
  • event EventHandler ErrorsChanged– zdarzenie informujące o zmienie ilości błędów w obiekcie
  • IEnumerable GetErrors(string propertyName) – funkcja pobierająca kolekcję błędów dla danego propertisa

W celu “wyłapania” błędów przez widok należy w bindingu ustawić properties
NotifyOnValidationError = true

Jak już wcześniej wspomniano funkcja GetErrors(string propertyName) zwraca kolekcję błędów dla danej właściwości. Zatem do klasy, która będzie implementowała interfejs INotifyDataErrorInfo należy dodać kolekcję przechowującą obiekty typu ValidationResult. Przykładowa implementacja interfejsu może wyglądać w następujący sposób:

Funkcja Validate() najpierw czyści wszystkie poprzednie wyniki walidacji, a następnie przy pomocy klasy Validator oraz funkcji TryValidateObject waliduje wszystkie właściwości, które zostały oznaczone atrybutem dziedziczącym po klasie ValidationAttribute.Z kolei funkcja Validate(string propertyName) waliduje tylko konkretną właściwość.
Przykładowe walidowanie właściwości przy pomocy atrybutów może wyglądać w następujący sposób:

W celu stworzenia własnych regół walidacji, nie uwzględnionych w zapewnionych przez framework atrybutach należy stworzyć własną klasę dziedziczącą po klasie ValidationAttribute, a następnie przeciążyć metodę IsValid. Przykładowa klasa może wyglądać w następujący sposób:

Walidacja przy użyciu interfejsu INotifyDataErrorInfo idealnie nadaje się (wg mnie) do walidowaniu całych ViewModeli. Przy zamykaniu okna wystarczy wywołać funkcję Validate() z bazowego ViewModelu, a w przypadku gdy zwróci ona false zatrzymać zamykanie okna. Jako, że walidacja zostanie przeprowadzona na wszystkich wybranych przez nas propertisach, widok automatycznie się zaktualizuje i pokaże komunikaty błędów na odpowiednich kontrolkach okna.

Walidacja