Trzeci Samouczek Silverlight – Część 3

9 lutego, 2011 Kategorie: Silverlight

W części 2 stworzyliśmy możliwość edycji i tworzenia nowych wydarzeń. W tym kroku zajmiemy się obsługą użytkowników oraz walidacją danych. Odpowiada to 3 modułowi oryginalnego samouczka (zadania 1-6).

 

Wprowadzenie

Nasza aplikacja Silverlight używa prostych metod, które świetnie pracują w podstawowych aplikacjach, ale mogą stać się problematyczne wraz z rozwojem aplikacji. Kontrolki są bezpośrednio powiązane z danymi zwracanym poprzez WCF RIA Services. Cała obsługa aplikacji mieści sie w plikach pomocniczych (*.cs) dla plików XAML. Z wielu powodów, może to powodować problemy. Umieszczanie obsługi aplikacji w plikach pomocniczych XAML, znacznie utrudnia testowanie zachowań aplikacji. Nie można stworzyć scenariuszy testowych, które nie używają plików XAML. Bezpośrednie powiązanie z obiektami zwracanymi przez usługi sieciowe utrudnia wizualizacje właściwości obiektów, które nie są odzwierciedlone bezpośrednio we właściwościach samego obiektu. Nie chcemy dodać nowych elementów do modelu obiektu zwróconego przez usługę sieciową, tylko dla łatwego wsparcia interfejsu użytkownika, bez informacji czy użytkownik zatwierdził lub nie dodanie nowych elementów ? właściwość nie powiązana bezpośrednio z obiektem.

Bardziej zaawansowane aplikacje Silverlight dodają dodatkową warstwę pomiędzy XAML a modelem danych w celu zwiększenia elastyczności i możliwości testowania.

Koncepcje konta i roli są ważne dla strony klienckiej i serwerowej. Szablon Silverlight Business Application standardowo tworzy usługi wspierające te koncepcje. Logika decydująca, który użytkownik ma dostęp do różnych elementów interfejsu należy do strony klienckiej aplikacji i właśnie ta logika będzie częścią dodatkowej warstwy. Nazywamy ją ViewModel (widokiem modelu), ponieważ znajduje si pomiędzy View (widokiem ? pliki XAML wraz z plikami pomocniczymi) a Model (modelem ? strona kliencka usługi sieciowej WCF RIA Services).

 

Dodajemy warstwę ViewModel

Do autoryzacji będziemy używać tabel aspnet_* oraz mechanizmów autentykacji ASP.NET.

image

Jeśli obejrzymy zawartość tabeli aspnet_Users (prawy klik na tabeli i Show Table Data), zobaczymy dwóch zdefiniowanych uzytkowników:

image

Tabele aspnet_* to standard rozpoznawany przez różne elementy ASP.NET, stosowane do pracy z użytkownikami, rolami i profilami. Zazwyczaj można je znaleźć w automatycznie generowanej przez SQL Express bazie danych ASPNETDB.MDF, my użyjemy tabel z bazy danych naszej aplikacji. Są one wypełnione przygotowanymi danymi, ponadto korzystanie z jednej bazy danych ułatwia wdrożenie aplikacji.

Musimy nakazać ASP.NET korzystanie z tabel w naszej bazie danych, ponieważ standardowo tworzy ono własne tabele w swojej bazie. Jeśli już próbowałeś opcji logowania, którą szablon Silverlight Business Application dodaje standardowo w aplikacjach, bardzo możliwe, że ASP.NET utworzył juz swoje własne tabele aspnet_*. W części serwerowej aplikacji rozwiń folder App_Data i kliknij na ikonę Show All Files (pokazuje wszystkie pliki, nawet te ukryte):

image

Jeśli widzisz ASPNETDB.MDF (u mnie nie ma 🙂 ), oznacza to że nastąpiła automatyczna generacja tych tabel. To żaden problem, wskażemy teraz ASP.NET naszą bazę danych.

Edytujemy plik Web.config (część serwerowa)

image

Znajdujemy sekcje <system.web>.

Kasujemy tag

<roleManager enabled="true" />

oraz tag

<profile>
      <properties>
        <add name="FriendlyName" />
      </properties>
    </profile>

Wewnątrz sekcji <system.web> dodajemy:

      <membership defaultProvider="SlEventManagerMembershipProvider" userIsOnlineTimeWindow="15">
          <providers>
              <clear />
              <add name="SlEventManagerMembershipProvider"
                   type="System.Web.Security.SqlMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                   connectionStringName="SlEventManagerDb" applicationName="/SlEventManager" enablePasswordRetrieval="false"
                   enablePasswordReset="true" requiresQuestionAndAnswer="true" requiresUniqueEmail="true"
                   passwordFormat="Hashed" />
          </providers>
      </membership>

      <roleManager enabled="true" defaultProvider="SlEventManagerRoleProvider">
          <providers>
              <clear />
              <add name="SlEventManagerRoleProvider"
                   type="System.Web.Security.SqlRoleProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                   connectionStringName="SlEventManagerDb" applicationName="/SlEventManager" />
          </providers>
      </roleManager>

      <profile enabled="true" defaultProvider="SlEventManagerProfileProvider">
          <providers>
              <clear />
              <add name="SlEventManagerProfileProvider" connectionStringName="SlEventManagerDb"
                   applicationName="/SlEventManager"
                   type="System.Web.Profile.SqlProfileProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
          </providers>
          <properties>
              <add name="FriendlyName" />
          </properties>
      </profile>

 

Definiujemy w ten sposób lokalizacje danych o użytkownikach (Memebership), rolach (Roles) i profilach (Profiles). Te definicje pozwalają na podanie nazwy aplikacji co powiązuje użytkowników, role i profile z naszą aplikacją (takie powiązanie jest uznawane za dobra praktykę). Dodatkowo definiujemy połączenie z bazą danych, którą będzie wykorzystywał ASP.NET. Wszystkie trzy elementy używają bazy SlEventManagerDB.

Znajdź sekcje <connectionStrings> i dodaj informacje o bazie danych do autentykacji:

    <add name="SlEventManagerDb"
       connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\SlEventManager.mdf;Integrated Security=True;User Instance=True;MultipleActiveResultSets=True"
       providerName="System.Data.SqlClient" />

Uruchamiamy aplikacje. Próbujemy się zalogować ? przycisk login:

image

Użytkownik = administrator, hasło = P@ssw0rd  (po literze w jest numer zero)

image

W użyciu były tabele aspnet_*. Możesz to sprawdzić podając inna nazwę użytkownika lub hasło.

Przyjrzyjmy się teraz danym w tabeli aspnet_Roles:

image

Widzimy dwie role: Event Administrators (administratorzy wydarzeń) i Registered Users (zarejestrowani użytkownicy). W naszej bazie użytkownik ?administrator? należy do obu ról, użytkownik ?ian? tylko do drugiej (przynależność do ról zdefiniowana jest w tabeli aspnet_UsersInRoles). Rola Registered Users (zarejestrowani użytkownicy) jest standardowo tworzona w takich aplikacjach, ponieważ szablon Silverlight Business Application zawiera kod obsługujący tę role. Metoda AddUser znajdująca się w pliku UserRegistrationService.cs (cześć serwerowa naszego projektu) używa klas ról ASP.NET do tworzenia roli Registered Users (zarejestrowani użytkownicy), jeśli jeszcze nie istnieje i automatycznie przypisuje ja nowo utworzonym użytkownikom.

Uruchom aplikacje. Jeśli jesteś zalogowany, wyloguj sie (logout). Użyj interfejsu użytkownika aby stworzyć nowego użytkownika (cały kod niezbędny do tego pochodzi z szablonu).

image

image

Po rejestracji możesz zobaczyć dodatkowe wpisy w tabelach aspnet_Users, aspnet_Membership, and aspnet_UsersInRoles. Został zapisany Twój nowy użytkownik i jego przynależność do roli Registered Users (zarejestrowani użytkownicy).

image

image

 

Tworzymy przyciski rejestracji

Przejdźmy do strony Home.xaml (katalog Views w części klienckiej projektu SlEventManager). Po widocznym w końcowej części kontrolce StackPanel, dodajemy kolejną identyczną kontrolkę:

<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Name="registerForEventButton" Content="Register" />
            <Button x:Name="unregisterForEventButton" Content="Unregister" />
        </StackPanel>

W chwili obecnej oba panele są widoczne jednocześnie (i jeden zakrywa drugi). Zmienimy to później w ViewModel poprzez wprowadzenie powiązanych zmiennych, kontrolujących widoczność obu paneli.

Dodajemy nową klasę do projektu (Shift+Alt+C lub Add->Class ? w menu kontekstowym Solution Explorer). Nazywamy ją ViewModelBase.

image

image

ViewModel musi powiadomić Silverlight o każdej zmianie swoich właściwości, co spowoduje aktualizacje w UI wszystkich powiązanych z danymi zmiennych. Zazwyczaj robi się to poprzez klasę INotifyPropertyChange (InformujeOZmianieWłaściowości). Typowym jest implementacja bazowej klasy, używanej przez wszystkie ViewModel.

W pliku ViewModelBase.cs dodajemy deklaracje:

using System.ComponentModel;

oraz implementujemy klasę:

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this,
                new PropertyChangedEventArgs(propertyName));
        }
    }
}

 

Tworzymy ViewModel (widok Modelu)

Dodajemy nowy katalog w projekcie SlEventManager i nazywamy go ViewModels.

image

image

Modele widoku są zazwyczaj wprowadzane dla konkretnych widoków (np. jednego pliku XAML i jego pliku z kodem) lub jakiejś części widoku (np. pojedynczego elementu na liście wewnątrz widoku). My zdefiniujemy ViewModel (model widoku) dla strony Home.xaml.

Dodajemy klasę HomeViewModel w katalogu ViewModels. Tworzymy ja jako dziedziczącą klasy ViewModelBase (utworzonej przez nas wcześniej).

image

Ustalamy dziedziczenie:

namespace SlEventManager.ViewModels
{
    public class HomeViewModel : ViewModelBase
    {

ViewModel potrzebuje informować o  widoczności przycisków administracyjnych. Dodajemy następujące definicje właściwości:

private Visibility _adminButtonsVisibility;
public Visibility AdminButtonsVisibility
{
    get { return _adminButtonsVisibility; }
    set
    {
        if (_adminButtonsVisibility != value)
        {
            _adminButtonsVisibility = value;
            OnPropertyChanged("AdminButtonsVisibility");
        }
    }
}

Jest to standardowy szablon używany dla właściwości zgłaszających zmianę stanu. W tym przypadku ?widoczność? (visibility) ? nazwa pochodzi od właściwości Visibility (widoczność) kontrolki StackPanel, z którą będziemy robić powiązanie (bind).

Dodajemy kolejną podobną właściwość AttendeeButtonsVisibility (widoczność przycisków uczestników):

private Visibility _attendeeButtonsVisibility;
public Visibility AttendeeButtonsVisibility
{
    get { return _attendeeButtonsVisibility; }
    set
    {
        if (_attendeeButtonsVisibility != value)
        {
            _attendeeButtonsVisibility = value;
            OnPropertyChanged("AttendeeButtonsVisibility");
        }
    }
}

Dodatkowy kod do ustawiania powyższych właściwości:

private void UpdateForUserRole()
{
    bool isLoggedIn = WebContext.Current.User.IsAuthenticated;
    bool isAdmin = isLoggedIn &&
                   WebContext.Current.User.IsInRole("Event Administrators");

    AdminButtonsVisibility = isAdmin ?
        Visibility.Visible : Visibility.Collapsed;
    AttendeeButtonsVisibility = (isLoggedIn && !isAdmin) ?
        Visibility.Visible : Visibility.Collapsed;
}

Kod ten używa klasy WebContext, będącej częścią WCF RIA Services. Posiada wbudowane wsparcie dla koncepji autentykacji użytkowników i ich ról. Polega na AuthenticationService z katalogu Services w SlEventManager.Web, części serwerowej projektu.

 

Podczas inicjalizacji interfejsu użytkownika musimy wywołać powyższą metodę. Także podczas zalogowania / wylogowania użytkownika. Bazujemy na AuthenticationService z katalogu Services w SlEventManager.Web, części serwerowej projektu.

public HomeViewModel()
{
    WebContext.Current.Authentication.LoggedIn += (s, e) => UpdateForUserRole();
    WebContext.Current.Authentication.LoggedOut += (s, e) => UpdateForUserRole();
    UpdateForUserRole();
}

Obiekt WebContext.Current.Authentication powiadamia nas kiedy użytkownik loguje/ wyloguje się, powodując aktualizacje stanów widoczności elementów UI.

W pliku Home.xaml.cs deklarujemy i inicjalizujemy nasz nowo stworzony VieModel.

Dodajemy referencje:

using SlEventManager.ViewModels;

Deklarujemy:

HomeViewModel _viewModel = new HomeViewModel();

Inicjujemy:

this.DataContext = _viewModel;

Końcowy efekt (Home.xaml.cs):

namespace SlEventManager { using System.Windows.Controls; using System.Windows.Navigation; using System; using SlEventManager.Web; using SlEventManager.ViewModels;

public partial class Home : Page { HomeViewModel _viewModel = new HomeViewModel(); public Home() { InitializeComponent(); this.Title = ApplicationStrings.HomePageTitle; this.DataContext = _viewModel; } ?...

W pliku Home.xaml lokalizujemy kontrolkę StackPanel z przyciskiem do edycji i tworzenia wydarzeń. Dodajemy atrybut widoczności, powiązując kontrolkę z naszym ViewModel.

Visibility="{Binding Path=AdminButtonsVisibility}"

Podobnie w drugiej kontrolce StackPanel dodajemy:

"{Binding Path=AttendeeButtonsVisibility}"

Końcowy efekt (Home.xaml):

 

 

 

 

!!! WPIS NIEKOMPLETNY !!!

Znaczniki: , , ,
Brak komentarzy

Wpisz komentarz