Trzeci Samouczek Silverlight – Część 2
W części 1 rozpoczęliśmy prace nad aplikacją zarządzającą konferencjami. Stworzyliśmy interfejs użytkownika i połączenie do bazy danych na serwerze. W tej części dodamy możliwość edycji i tworzenia nowych wydarzeń. Dodamy również możliwość szczegółowego opisania konferencji poprzez definiowanie prelekcji. Odpowiada to 2 modułowi oryginalnego samouczka (zadania 2-4).
Dodamy nową stronę w naszym projekcie do edycji wydarzeń. Zobrazuje to jak, zdefiniowany przez nas w poprzedniej części, serwis sieciowy pozwala na selektywne pobieranie i zapisywanie danych.
W projekcie SLEventManager dodajemy nową stronę w Views (szablon to Silverlight Page, nazwa to EditEvent):
Otwieramy okno źródeł danych (Data Sources)
Wyszukujemy element Event (ten sam, który przeciągaliśmy na główną stronę) i podświetlamy go. Korzystamy z listy rozwijanej i wybieramy detale (details). Lista ta pozwala wskazać jaka kontrolka pojawi się kiedy przeciągniesz element na stronę. standardowo oferowana jest tabela ale jest też alternatywa ? detale.
Przeciągamy teraz element Event na nowo stworzoną stronę (na widok projektowania ? design). Teraz zamiast tabeli powinniśmy zobaczyć zestaw pól pozwalających na edycje.
Jeśli przyjrzycie się teraz kodowi xaml, zauważycie, że Web Developer dodał niewidoczną w UI kontrolkę DomainDataSource (łączącą z danymi), który jest skonfigurowany dokładnie tak samo jak przy poprzednim przeciąganiu tego elementu (w części 1). To by miało sens przy tworzeniu strony ogół/szczegół (master/detail), gdzie edytujemy szczegóły elementu wybranego z ogólnej listy. W naszym przypadku strona ma pokazywać tylko szczegóły jednego wydarzenia. Jak w takim razie skłonimy DomainDataSource do pobierania tylko danych jednego wydarzenia i to tego wydarzenia, które chcemy edytować? Użyjemy do tego celu linku, tak że administrator wydarzeń, będzie mógł zaznaczyć element do edycji. Link będzie w postaci adres.com/SlEventManagerTestPage.aspx#/EditEvent?EventID=3
Przycisk nawigacyjny
W chwili obecnej nie ma możliwości przejścia do naszej nowo stworzonej strony, także zaczniemy od dodania na stronie Home.xaml przycisku przekierowującego do edycji wybranego wydarzenia.
Otwieramy plik Home.xaml.cs i tworzymy tam pomocniczą funkcje, ułatwiajacą przekierowanie do strony EditEvent.
private void NavigateToEditEvent(int eventId) { NavigationService.Navigate(new Uri("/EditEvent?EventID=" + eventId, UriKind.Relative)); }
Nie zapomnijmy dodać referencji do biblioteki system:
using System;
lub (prawy klik na uri )
Funkcja ta tworzy link razem z numerem wydarzenia, który przekieruje na stronę edycji detali tego wydarzenia (będziemy potrzebowali tego przekierowania w kilku innych miejscach, dlatego stworzyliśmy funkcje pomocniczą).
Przechodzimy do widoku projektowania (design) strony Home.xaml. Zróbmy trochę miejsca nad tabelka, przeciągając jej górna krawędź na dół. Dodajemy wiersz siatki poprzez kliknięcie na niebieskim pasku po lewej
Po końcu znacznika DataGrid ale przed końcem znacznika Grid dodajemy:
</sdk:DataGrid> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> <Button x:Name="editCurrentButton" Content="Edit current event" /> </StackPanel> </Grid>
Pojawił się przycisk:
W sekcji Grid.RowDefinitions(blisko początku kodu), która powstała po dodaniu wiersza siatki (kliknięcie na niebieskim pasku), zmieniamy dla pierwszego wiersza parametr Height (wysokość) na Auto.
<Grid x:Name="LayoutRoot"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="443*" /> </Grid.RowDefinitions>
Klikamy podwójnie na nowo utworzony przycisk co powoduje utworzenie funkcji obsługującej zdarzenie kliknięcia na przycisk. Dodajemy tam kod:
private void editCurrentButton_Click(object sender, System.Windows.RoutedEventArgs e) { Event currentEvent = eventDataGrid.SelectedItem as Event; if (currentEvent != null) { NavigateToEditEvent(currentEvent.EventID); } }
Typ Event do którego odnosi się kod pochodzi z serwisu sieciowego zdefiniowanego w poprzedniej części samouczka. Połączenie WCF RIA Services wygenerowało niezbędne klasy po stronie klienta aby korzystać z elementów serwisu. Niestety zostały one wygenerowane w oddzielnej przestrzeni nazw (namespace) dlatego kod sie teraz nie skompiluje i zakomunikuje błąd.
Musimy dodać referencje to części sieciowej projektu.
using SlEventManager.Web;
Powinniśmy udostępnić przycisk tylko gdy użytkownik zaznaczy wydarzenie. Przechodzimy do kodu XAML i ustawiamy atrybut przycisku IsEnabled (jest aktywny) na false (fałsz)
Click="editCurrentButton_Click" IsEnabled="False"/>
Klikamy podwójnie na kontrolkę DataGrid co powoduje dodanie w pliku Home.xaml.cs obsługi zdarzenia SelectionChanged. Modyfikujemy kod:
private void eventDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) { editCurrentButton.IsEnabled = eventDataGrid.SelectedItem != null; }
Uruchamiamy aplikacje (F5). Wybieramy wydarzenie w tabeli i klikamy na przycisk ?Edit current event?. Aplikacja przekierowuje nas do strony edycji wydarzenia. Widzimy identyfikator ?ID? wybranego wydarzenie. Niestety, strona edycji ignoruje ten identyfikator. Możemy edytować tylko pierwsze przesłane wydarzenie. Wybieram drugie wydarzenie:
Po przejściu do edycji, widzimy pierwsze wydarzenie:
Chcemy aby strona edycji reagowała na przesłany identyfikator wydarzenia. Przechodzimy do kodu w pliku EditEvent.xaml.cs. Widzimy tam standardowo dodaną funkcję OnNavigatedTo, która jest uruchamiana za każdym przekierowaniem do strony edycji. Modyfikujemy kod:
// Executes when the user navigates to this page. protected override void OnNavigatedTo(NavigationEventArgs e) { string eventId = NavigationContext.QueryString["EventID"]; eventDomainDataSource.FilterDescriptors.Add( new FilterDescriptor { PropertyPath = "EventID", Operator = FilterOperator.IsEqualTo, Value = eventId } ); }
Odczytujemy ID wydarzenia z URLa. Następnie dodajemy filtrowanie danych przekazywanych przez eventDomainDataSource. Szukamy wydarzeń z identycznym identyfikatorem do przekazanego w URL.
Uruchamiamy aplikację i teraz edytujemy wybrane wydarzenie.
Pomimo zdefiniowania filtrowania w części klienckiej aplikacji, samo filtrowanie (dzięki elastyczności WCF RIA Services) odbywa się po stronie serwera. Kiedy serwis sieciowy zwraca IQueryable<T>, WCF RIA Services pozwala klientowi na przesłanie opcji filtrowania, sortowania lub grupowania jako część zapytania. Kryteria te są zastosowane do IQueryable<T>, zwracanego przez usługą sieciową. Filtrowanie, sortowanie lub grupowanie wykonywane jest za pomocą standardowych operatorów zapytań LINQ.
Ponieważ nasza usługa sieciowa zwraca IQueryable<T> bezpośrednio z Entity Framework, oznacz to że filtrowanie, sortowanie lub grupowanie jest wykonywane przez Entity Framework, który wykorzystuje standardy LINQ w bazie danych. W naszym przypadku, oznacza to że zdefiniowany filtr jest wykonywany jako klauzula WHERE w zapytaniu SQL do bazy danych.
Pozwalamy na zapisywanie wydarzeń
Użytkownik może edytować wydarzenie, ale wszystkie zmiany pozostają po stronie klienta. Usługa RIA nie prześle zmian do bazy danych, ponieważ nie wie kiedy to zrobić. Musimy dodać kod umożliwiający zapis zmian.
W pliku EditEvent.xaml dodajemy przycisk (przeciągamy kontrolkę z Toolbox).
Nazywamy go saveChangesButton i zmieniamy jego Content na Save Changes.
Klikamy podwójnie na przycisku aby wygenerować funkcję obsługi zdarzenia kliknięcia na przycisku. Modyfikujemy kod:
private void saveChangesButton_Click(object sender, RoutedEventArgs e) { eventDomainDataSource.SubmitChanges(); }
Metoda SubmitChanges tworzy informacje o wszystkich zmianach od czasu pobrania informacji z serwera (wszystkie wygenerowane przez WCF RIA Services obiekty po stronie klienta przechowują historie zmian). Informacja ta przekazywana jest do serwera gdzie serwerowa część RIA Services zainicjuje transakcje i wywoła metody w usłudze sieciowej, niezbędne do zapisania zmian.
Z punktu widzenia komunikacji sieciowej jest to pojedyncza operacja, spakowana w jedną paczkę. Strona serwerowa dostarcza operacje dodania, zmiany lub skasowania danych, RIA Services wywołają niezbędne metody do obsługi całej paczki. (Metody obsługi dodania, zmiany i skasowania danych zostały wygenerowane kiedy dodawaliśmy usługę sieciową, gdyż zaznaczyliśmy możliwość edycji ? zobacz tworzenie EventManagerDomainService w części 1)
Teraz możemy zmieniać wydarzenie. Uruchom aplikacje i sprawdź.
Dodana przed chwila funkcjonalność nie jest niezbędna dla edytowania wydarzeń. Kontrolka DataGrid standardowo pozwala na edycje danych. Tworzy dwukierunkowe powiązanie z wyświetlanymi obiektami, można podwójnie kliknąć dane w tabeli aby je edytować. Mogliśmy dodać przycisk ?Zapisz zmiany? na stronie głównej, używając metody SubmitChanges. Dobrą praktyką jest tworzenie specjalizowanych stron, takich jak edycja wydarzenia dodana w tej części samouczka. Gdy w następnych częściach dodamy edycje prelekcji i informacji o sesjach, będzie dużo więcej danych do pokazania dla pojedynczego wydarzenia. Umożliwienie edycji tych danych z jednej strony powodowało by niepotrzebne przekazywanie dużych ilości danych (co może zakończyć się ściąganiem przez klienta całej zawartości bazy danych przy starcie aplikacji). Oczywiście działało by to dla małych ilości danych ? jak w naszym przykładzie ? ale nie jest to dobre podejście w prawdziwych biznesowych aplikacjach.
Tworzymy nowe wydarzenia
Edytujemy plik Home.xaml i lokalizujemy kontrolkę StackPanel z przyciskiem edycji wydarzenia. Dodajemy kolejny przycisk i nazywamy ctreateNewEventButton. Wyświetlany teksy ustawiamy na Create New Event. Dodajemy obsługę zdarzenia kliknięcia na przycisku.
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Visibility="{Binding Path=AdminButtonsVisibility}"> <Button x:Name="editCurrentButton" Content="Edit current event" Click="editCurrentButton_Click" IsEnabled="False"/> <Button Name="createEventButton" Content="Create New Event" Click="editCurrentButton_Click"/> </StackPanel>
Dodajemy referencje (plik Home.xaml.cs):
using SlEventManager.Web.Services;
I kod obsługi zdarzenia kliknięcia na nowym przycisku:
private void createEventButton_Click(object sender, System.Windows.RoutedEventArgs e) { EventManagerDomainContext ctx = new EventManagerDomainContext(); DateTime today = DateTime.Now.Date; Event newEvent = new Event { EventTitle = "<Tytuł>", EventVenueName = "<Lokalizacja>", EventDescription = "<Opis>", EventStartDate = today, EventEndDate = today }; ctx.Events.Add(newEvent); ctx.SubmitChanges((op) => { if (!op.HasError) { NavigateToEditEvent(newEvent.EventID); } }, null); }
Powyższy kod tworzy nowy kontekst do komunikacji z usługami sieciowymi. Mogliśmy uzyć kontekstu DomainDataSource, ale on chce kontrolować własne zmiany i aktualizować kontrolki kiedy zmiany zostały zakończone. W naszym przypadku, chcemy wyświetlić inną stronę jak tylko zakończymy wprowadzać nowe wydarzenie, dlatego łatwiej stworzyć własny kontekst do obsługi zakończenia tej operacji.
Większość kodu inicjalizuje nowe wydarzenie, nadając początkowe wartości w celu uniknięcia błędów walidacji. Nowe wydarzenie zostałoby odrzucone gdyby brakowało jakiś informacji. Wywołując metodę SubmitChanges bezpośrednio na kontekście (a nie na niewidocznej kontrolce DomainDataSource) musimy stworzyć funkcje zwracającą informacje o zakończeniu aktualizacji danych. Jeśli operacja zakończyła się sukcesem, ID nowego wydarzenia zostanie zaktualizowane o wartość z bazy danych, które użyjemy do nawigacji do następnej strony (za pomocą tej samej funkcji przekierowujacej, używanej przy edycji istniejącego wydarzenia).
Uruchom aplikacje. Dodaj nowe wydarzenie i obejrzyj je na liście wydarzeń na stronie Home.
Sesje i prelekcje
Wydarzenia nie były zbyt interesujące gdyby uczestnicy nie mogli uczestniczyć w prelekcjach. Nasza strona edycji wydarzeń powinna zawierać listę sesji wydarzenia wraz z szczegółami prelekcji dla każdej sesji. W naszym edytorze wydarzeń dodamy dwie kontrolki datagrid, dodatkowo zmienimy usługę aby zapewnić dostępność wszystkich niezbędnych danych.
Metody usługi sieciowej używanej przez naszą aplikacje Silverlight dostarczają tylko informacji o wydarzeniu. Metoda GetEvents w EventManagerDomainService zwraca tylko ObjectContext.Events i standardowo nie pobiera dodatkowych danych z powiązanych tabel.
Dla strony Home.xaml jest to co potrzebujemy. Strona pokazuje listę wydarzeń, wiec nie potrzebuje żadnych dodatkowych informacji, których pobieranie może tylko spowolnić ładowanie strony. Aczkolwiek, dla strony edycji pojedynczego wydarzenia, potrzebujemy pobrać informacje o sesjach i prelekcjach tego wydarzenia.
Tworzymy dostosowane zapytania usługi sieciowej
Lokalizujemy metodę GetEvent w EventManagerDomainServices.cs (w części serwerowej projektu SlEventManager.Web). Tworzymy kopie metody, nazywamy ją GetEventsWithTracksAndTalks i lekko modyfikujemy:
public IQueryable<Event> GetEventsWithTracksAndTalks() { return this.ObjectContext.Events.Include("EventTracks.Talks"); }
Metoda Include informuje Entity Framework jakich nawigacyjnych właściwości planujemy użyć na zwróconych danych. Powoduje to pobranie od razu wszystkich EventTracks (Sesji) i Talks (Prelekcji) dla każdego wskazanego wydarzenia.
Otwieramy widok EditEvent.xaml (część kliencka projektu) i dzielimy Grid na dwie równe części (dwie kolumny). Można to zrobić poprzez klikniecie na górny niebieski pasek.
Otwieramy panel Data Sources (źródła danych):
Rozwiń Event i zauważ element EventTracks (sesje), a ten z kolej po rozwinięciu zawiera element Talks (prelekcje).
Warto zauważyć, że elementy EventTracks i Talks będące częścią Event różnią się od EventTrack and Talk będących bezpośrednimi elementami kontekstu EventManagerDomainContext. Elementy będące składową innej kolekcji elementów reprezentują tylko dane bezpośrednio odnoszące się do nadrzędnego elementu (w przeciwieństwie do wszystkich danych tego typu). Jeśli przeciągniemy element EventTracks do UI, dane wyświetlane w stworzonym DataGrid pokażą tylko sesje powiązane z wybranym wydarzeniem, a nie wszystkie dostępne w bazie danych sesje.
Przeciągnij element EventTracks (ten będący składową Event) do projektu UI. Umieść nowy DataGrid w lewym dolnym rogu widoku. Pozwól na zmianę rozmiaru widoku (resize).
</sdk:Page.Resources> <Grid x:Name="LayoutRoot"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
Jeśli przyjrzymy sie teraz plikowi XAML, zauważymy, ze Web Developer nie dodał kolejnej DomainDataSource, a CollectionViewSource, który odnosi sie do istniejacej DomainDataSource. W ten sposób pokazując dane odnoszące sie do bieżącego wydarzenia. Jeszcze nie korzystamy z nowej metody pobierającej dane, dlatego nie ma jeszcze danych o sesjach w nowym DataGrid.
<sdk:Page.Resources> <CollectionViewSource x:Key="eventEventTracksViewSource" Source="{Binding Path=Data.EventTracks, ElementName=eventDomainDataSource}" /> </sdk:Page.Resources>
W następnej (części 3) zajmiemy się obsługą użytkowników oraz walidacją danych.