Realizacja Aplikacji Internetowych 2012 laboratorium K. M. Ocetkiewicz Instalacja MVC 4: - http://www.asp.net/mvc - Visual Studio 2012 (także Web Express) zawiera wbudowane MVC 4 - można doinstalować MVC 4 do Visual Studio 2010 (także do Web Express 2010) (wymagana jest aktualizacja do Service Pack 1) - w przypadku problemów dotyczących NuGet należy doinstalować ten element (Tools -> Extension Manager -> Online Gallery -> NuGet package manager) - w laboratorium MVC 4 jest zainstalowane na maszynach wirtualnych RAI12 (z Visual Studio 2010 SP1) Nowy projekt: - ASP.NET MVC 4 Web Application - [Other Languages] Visual C# Web ASP.NET MVC 4 Web Application - okno dialogowe: - Empty project pusty projekt - Internet Application prosty szablon projektu wykorzystujący login i hasło do uwierzytelniania - Intranet Application prosty szablon projektu wykorzystujący Windows Authentication do uwierzytelniania
- Mobile Application prosty szablon dostosowany do urządzeń mobilnych - View engine silnik wykorzystywany do tworzenia widoków (stron HTML widzianych przez użytkownika; decyduje o tym, czy kod HTML będziemy uzupełniać wstawkami typu < %= wyrażenie %> (ASPX) czy @(wyrażenie) (Razor) - Use HTML5 semantic markup jeżeli zaznaczone, widoki będą domyslnie zawierały znaczniki semantyczne z HTML5 (np. <header>, <section> itp.) - Create Unit test project jeżeli chcemy utworzyć projekt z testami jednostkowymi - można uruchomić wygenerowany kod działa i zawiera prostą aplikację Zawartość projektu: - <projekt> \ Controllers katalog zawierający kontrolery, kliknięcie prawym przyciskiem myszy na Controllers, wybranie Add z menu kontekstowego prowadzi nas m.in. do polecenia dodania nowego kontrolera (Controller ) - <projekt> \ Views katalog zawierający widoki - widoki powiązane z kontrolerem o nazwie Test powinny znajdować się w katalogu <projekt> \ Views \ Test (ponieważ tam będą poszukiwane) - podkatalog Shared przeznaczony jest na widoki lub ich fragmenty dzielone przez wszystkich - _Layout.cshtml jest szablonem widoku wykorzystywanym przez wygenerowaną aplikację (dokładnie jest to <projekt> \ Shared \ _ViewStart.cshtml, który odwołuje się do _Layout.cshtml) - <projekt> \ Models miejsce przeznaczone na nasze modele - <projekt> \ Global.asax \ Global.asax.cs plik zawierający kod startowy naszej aplikacji; - w starszych wersjach MVC tu konfigurowaliśmy m.in. trasy - w MVC4 kod konfiguracji tras został przeniesiony do App_Start \ RouteConfig.cs Architektura MVC - model reprezentuje logikę naszej aplikacji; nie powinien nic wiedzieć ani o kontrolerach, ani o widokach nie powinno go interesować w jaki sposób użytkownik podejmuje działania ani to, co widzi użytkownik - widok to, co widzi użytkownik; choć widoki mogą zawierać kod, to powinien być on ograniczony do minimum (JavaScriptu nie traktujemy tu jako kodu widoku; chodzi o kod wykonywany po stronie serwera) - kontroler jest to element obsługujący polecenia użytkownika; komunikuje się z modelami i decyduje, jakie widoki zaprezentować użytkownikowi Schemat obsługi zapytania w MVC - w momencie uruchamiania aplikacji wołana jest metoda Application_Start z klasy MvcApplication w Global.asax.cs - metoda ta woła m.in. metodę RegisterRoutes, której celem jest skonfigurowanie tras - użytkownik generuje zapytanie i przesyła je do naszej aplikacji - aplikacja zestawia zapytanie ze zdefiniowanymi trasami i na tej podstawie wyznacza kontroler oraz akcję do wywołania wraz z ewentualnymi parametrami akcji UWAGA: każda trasa powinna co najmniej definiować kontroler i akcję którą należy podjąć - tworzony jest nowy obiekt kontrolera; jeżeli trasa stwierdziła, że zapytanie ma obsłużyć
kontroler Test, utworzony zostanie obiekt klasy TestController; czas życia kontrolera ograniczony jest do obsługi akcji, zatem nie należy przechowywać w nim żadnych danych UWAGA: jeżeli mówimy o kontrolerze Test, zawsze chodzi nam o klasę TestController, MVC wymaga, by klasy kontrolerów miały nazwę w formacie nazwacontroller - wywoływana jest odpowiednia akcja w kontrolerze; jeżeli trasa stwierdziła, że zapytanie powinna obsłużyć akcja Lista (z kontrolera Test) to zostanie wywołana metoda Lista w klasie TestController - wartość zwrócona z akcji decyduje o tym, co ma zostać zaprezentowane użytkownikowi - w typowych przypadkach, akcja komunikuje się z modelami, przygotowuje pewne dane i prezentuje te dane poprzez odpowiedni widok - wartość zwrócona z akcji jest analizowana przez aplikację, jeżeli ma to być widok, to jest on ładowany i parsowany, po czym jest przesyłany użytkownikowi UWAGA: kod w widoku (wstawki Razor-owe czy ASP) jest wykonywany po stronie serwera Routing - w trakcie startu aplikacji definiujemy trasy - trasy są dopasowywane do adresu zasobu (pomijając adres naszego serwisu), czyli przy adresie w oknie przeglądarki localhost/users/add dopasowywanym adresem będzie Users/Add - trasa może zostać dopasowana lub nie - jeżeli trasa zostanie dopasowana, to ona decyduje o kontrolerze i akcji - jeżeli trasa nie zostanie dopasowana, sprawdzana jest kolejna trasa - trasy dopasowywane są w kolejności ich utworzenia - produktem routingu jest zestaw par klucz, wartość; w wyniku routingu muszą zostać zdefiniowane co najmniej dwa klucze: controller i action - klucz controller zawiera nazwę kontrolera, który ma podjąć akcję o nazwie zapisanej w action - pozostałe pary klucz, wartość przekazywane są jako parametry do akcji; klucze są dopasowywane do nazw argumentów funkcji o nazwie zapisanej w action; dopasowanie to uwzględnia typy: parametr typu int otrzyma wartość liczbową, DateTime otrzyma datę itp.; niemożność konwersji typu nie powoduje niedopasowania trasy lecz skutkuje wyjątkiem podczas próby przekazania parametrów do akcji (np. /Dodaj/abc/def dopasowane do trasy /Dodaj/{p1/{p2 i prowadzące do akcji Dodaj(int p1, int p2) rzuci wyjątek mówiący o niemożności skonwertowania abc na liczbę) - aby dodać nową trasę należy wywołać metodę MapRoute obiektu RouteCollection w metodzie RouteConfig.MapRoutes; parametrami MapRoute są: - name nazwa trasy; każda trasa powinna mieć inną nazwę - url szablon URL-a to właśnie ten napis będzie dopasowywany do adresu z zapytania, UWAGA: szablon nie powinien rozpoczynać się znakiem / - defaults parametry zbiór parametrów do akcji; każda trasa powinna definiować co najmniej dwa parametry: controller (nazwę kontrolera) oraz action (akcję do wywołania); wszelkie pozostałe parametry zostaną przekazane jako parametry wywołania akcji - opcjonalnie: constraints ograniczenia, o nich później - np. routes.maproute("trasa", "Ludzie/Lista", new { controller = "Users", action = "List"); doda trasę o nazwie trasa ; jeżeli ta trasa zostanie dopasowana (czyli w momencie zapytania o Ludzie/Lista, wywołana zostanie metoda List z klasy UsersController.
- szablon URL-a może zawierać parametry parametr jest ciągiem znaków zawartym między nawiasami klamrowymi; parametr oznacza, że dany fragment z adresu zostanie zapamiętany jako parametr o podanej nazwie, np.: routes.maproute("modifieddefault", "{controller/{action", new { ); doda trasę o nazwie ModifiedDefault, która zostanie dopasowana do każdej trasy postaci napis1/napis2, przy czym napis1 będzie nazwą kontrolera, zaś napis2 nazwą akcji, czyli zapytanie o adres: Ludzie/Lista wywoła metodę Lista w klasie LudzieController Home/Index wywoła metodę Index w klasie HomeController nie podajemy tu jawnie nazwy kontrolera ani akcji, bo zostaną one wzięte z adresu jako parametry - możemy także podać wartości domyślne dla parametrów routes.maproute("default", "{controller/{action/{id", new { controller = "Home", action = "Index", id = UrlParameter.Optional ); dodaje trasę o nazwie Default z domyślnymi wartościami dla wszystkich parametrów (możemy także podać wartości domyślne tylko dla niektórych); taka trasa zostanie dopasowana np. do adresów: pusty adres (np. http://localhost/) wywołanie zostanie metoda Index (bez parametrów UrlParameter.Optional oznacza, że wartością domyślną jest brak parametru) z klasy HomeController Users wywołanie zostanie metoda Index (wartość domyślna dla action) bez parametrów (wartość domyślna dla id) klasy UsersController Home/List wywołanie zostanie metoda List (bez parametrów wartość domyślna dla Home/Edit/123 id) klasy HomeController wywołanie zostanie metoda Edit z parametrem id=123 z klasy HomeController - należy pamiętać, że trasy dopasowywane są w kolejności dodania, uwzględniając wartości domyślne, więc dla konfiguracji: routes.maproute("default", "{controller/{action/{id", new { controller = "Home", action = "Index", id = 555 ); routes.maproute("trasa", "Ludzie/Lista", new { controller = "Users", action = "List"); zapytanie o Ludzie/Lista wywoła metodę Lista z parametrem id=555 z klasy LudzieController zostanie dopasowana pierwsza trasa, natomiast w przypadku konfiguracji: routes.maproute("trasa", "Ludzie/Lista", new { controller = "Users", action = "List"); routes.maproute("default", "{controller/{action/{id", new { controller = "Home", action = "Index", id = 555 ); zapytanie o Ludzie/Lista wywoła metodę List (bez parametrów) z klasy UsersController - w przypadku użycia wartości domyślnych należy pamiętać, że wszystkie opuszczone parametry powinny być z prawej strony adresu (czyli dla trasy Default możemy pominąć id lub id i action ale nie możemy pominąć samego parametru action, - w przypadku użycia wartości domyślnych najlepiej rozdzielać parametry znakiem /, w
przypadku innych znaków (np. przecinka) MVC niestety sobie nie radzi Kontroler - nazwa klasy kontrolera musi kończyć się na Controller, - klasa musi dziedziczyć po klasie Controller Akcja - akcją jest każda publiczna metoda (zatem należy uważać na konfigurację routingu i publiczne metody w kontrolerze, aby nie udostępnić za dużo), - akcja nie powinna być statyczna ani przeciążona (poza pewnymi wyjątkami, ale o nich później), - akcjami nie są metody odziedziczone po klasach bazowych, - akcje nie mogą posiadać parametrów wyjściowych i referencyjnych (słowa kluczowe out i ref), - parametry akcji (jako metody) są pobierane z parametrów wynikających z trasy (zob. routing) oraz parametrów zapytania (parametry metody GET protokołu HTTP), - parametry zostaną w miarę możliwości dopasowane do typów parametrów akcji (np. napis będzie przetłumaczony na liczbę czy datę), niemożność dopasowania typu parametru skutkuje wyjątkiem - nadmiarowe parametry będą po cichu odrzucane (nie spowodują błędu), - brakujące parametry wywołania spowodują błąd (wyjątek) chyba, że podane są wartości domyślne lub parametr jest typu nullowalnego (np. int?), - jeżeli metoda nie ma być akcją, musimy nadać jej atrybut [NonAction], - od wartości zwracanej przez akcję zależy dalsze działanie aplikacji, - akcja zazwyczaj zwraca wynik typu ActionResult, - co można zwracać (między innymi): - View() polecenie wyświetlenia użytkownikowi widoku o takiej samej nazwie jak akcja (czyli Views \ nazwa_kontrolera \ nazwa_akcji), - View(nazwa_widoku) polecenie wyświetlenia użytkownikowi widoku o podanej nazwie - RedirectToAction(nazwa_akcji, nazwa_kontrolera, [opcjonalne_parametry]) przekierowanie użytkownika do akcji nazwa_akcji w kontrolerze nazwa_kontrolera, - Redirect(url) przekierowanie pod podany adres, - Content(tekst) zwrócenie tekstu tekst jako wynik, - zwrócenie nieobsługiwanego typu (np. string) spowoduje jego opakowanie w Content, - File(nazwa lub zawartość, mime_type) zwrócenie pliku o typie mime mime_type o podanej nazwie (gdy pierwszy parametr ma typ string; plik zostanie przeczytany z dysku) lub o podanej zawartości (gdy pierwszym parametrem jest tablica bajtów) - do komunikacji z widokiem służą obiekty: - ViewData słownik z kluczami będącymi napisami, - ViewBag obiekt dynamiczny o podobnym zastosowaniu, np.: ViewData[ wynik ] = 123; ViewData[ login ] = test ; ViewBag.wynik = 123; ViewBag.login = test ;
- atrybut akcji [HttpPost] powoduje, że reaguje ona tylko na zapytania typu POST, - atrybut akcji [HttpGet] powoduje, że reaguje ona tylko na zapytania typu GET, - przykład akcji: public ActionResult Dodaj(int p1, int p2) { ViewData[ wynik ] = p1 + p2; return View(); Widok - widok jest stroną HTML-ową ze wstawkami zawierającymi kod wykonywalny, - dla danej akcji możemy łatwo dodać odpowiadający jej widok klikając prawym przyciskiem myszy na nazwie akcji w jej definicji w oknie edytora i z menu kontekstowego wybierając Add View - dodać widok możemy także klikając w drzewie projektu prawym przyciskiem myszy na Views \ nazwa_kontrolera i wybierając Add -> View z menu kontekstowego - wstawiony kod jest wykonywany po stronie serwera, zanim użytkownik zobaczy widok - format wstawek zależy od wybranego silnika renderującego, - w przypadku ASPX wstawki należy otaczać tagami <%... %>, a konkretne wartości tagami <%=... %> np.: <% for(int i = 0; i < 10; i++) { %> wartość i=<%= i.tostring() %> <br> <% %>
- w przypadku Razor, wstawki rozpoczynają się od znaku @, np.: @for(int i = 0; i < 10; i++) { <span>wartość i=@i <br></span> skomplikowane wyrażenia należy otaczać nawiasami: wynik = @(a+b) znaczenie znaku @ zazwyczaj będzie wywnioskowane (np. w napisie adres@email.pl @ nie zostanie potraktowane jako znak rozpoczynający wyrażenie, o ile email nie jest obiektem; jednak jeżeli chcemy mieć pewność, możemy napisać adres@@email.pl, co nie pozostawi wątpliwości co do znaczenia znaku @; zawartość nieformatowanego tekstu wewnątrz wstawek musi być otoczona tagami HTML-owymi; jeżeli nie wynikają one z konstrukcji strony, możemy posłużyć się tagiem <text>, np.: @if(a < b) { <text>a jest mniejsze od b</text> else { <text> a jest niemniejsze od b</text> - w widoku widoczne są obiekty ViewData i ViewBag odczytujemy z nich wartości przesłane z akcji, np.: Wynik=<%= ViewData[ wynik ].ToString() %> Wynik=<%= ViewBag.wynik %> Wynik=@ViewData[ wynik ].ToString() Wynik=@ViewBag.wynik - UWAGA: ASPX nie podmienia domyślnie znaków specjalnych, czyli wynikiem <%= <b><i>text</i></b> %> będzie text Razor domyślnie podmienia znaki specjalne, wynikiem: @ <b><i>text</i></b> będzie <b><i>text</i></b> - widok zazwyczaj definiuje tylko pewne fragmenty strony (np. główną treść, stopkę) zaś cała otaczająca zawartość (menu, nagłówek, układ strony itp.) zdefiniowany jest we wspólnym dla wszystkich widoków szablonie, Routing ograniczenia parametrów dla tras: - na parametry wynikające z trasy możemy nałożyć ograniczenia - jeżeli po dopasowaniu trasy ograniczenia nie będą spełnione, trasa nie będzie dopasowana i zostanie podjęta próba dopasowania innych tras (np. jeżeli z dopasowania wynika że parametr p1 ma wartość abcd, a ograniczenie mówi, że p1 może zawierać tylko cyfry, takie dopasowanie zostanie odrzucone) - ograniczenia podajemy jako czwarty parametr do MapRoute w postaci obiektu z polami postaci nazwa_parametru=ograniczenie - ograniczenie może być napisem, wtedy opisuje on wyrażenie regularne i sprawdzana jest zgodność wartości parametru z wyrażeniem
- ograniczenie może być również obiektem, który implementuje interfejs IRouteConstraint; wykonywana wtedy jest metoda Match tego obiektu, i jeżeli zwraca ona True, parametr jest dopasowany, jeżeli False nie jest, np.: routes.maproute( dodawanie, Dodaj/{p1,{p2, new { controller = Adder, action = Dodaj, new { p1 = @ \d+, p2 = @ \d+ ); definiuje trasę, której parametry mogą być tylko liczbami (\d to to samo co [0123456789], @ przed napisem oznacza wyłączenie specjalnego znaczenia znaku \... public class Div10Test: IRouteConstraint { public bool Match(HttpContextBase httpcontext, Route route, string parametername, RouteValueDictionary values, RouteDirection routedirection) { int value; if (Int32.TryParse(values[parameterName].ToString(), out value)) { return value % 10 == 0; return false;... routes.maproute( dodawanie, Dodaj/{p1,{p2, new { controller = Adder, action = Dodaj, new { p1 = @ \d+, p2 = new Div10Test() ); definiuje podobną trasę, przy czym tu p2 musi być liczbą całkowitą podzielną przez 10; parametry do Match to: httpcontext obiekt definiujący kontekst route obiekt trasy, której dotyczy ograniczenie parametername nazwa parametru, który jest testowany (np. p1, p2) values słownik wartości parametrów trasy, czyli np. dict[ p1 ] jest wartością parametru p1 (jako object, więc trzeba jeszcze wziąć ToString()) routedirection wartość opisująca, czy wartość parametru pochodzi z adresu zapytania, czy z generowanego adresu Model - model jest zwykłą klasą - modele należy dodawać w katalogu Models (Add -> New item -> Class) - po modyfikacji modelu należy przebudować projekt, aby wszelkie zmiany zostały zauważone przez środowisko (np. aby były wyświetlane aktualne nazwy obiektów w okienkach z podpowiedziami)
Sesja - do przechowywania danych służy obiekt Session - jest on słownikiem z kluczami typu string i wartościami typu object, - obecność danego klucza w sesji można sprawdzić, przyrównując wartość dla tego klucza do null, np.: if(session[ login ] == null)... // brak loginu w sesji - metoda Session.Abandon() powoduje usunięcie całej zawartości i rozpoczęcie nowej sesji - pole SessionID przechowuje identyfikator sesji (jako napis) Silnie typowane widoki - silnie typowany widok to widok powiązany z pewnym modelem (klasą), - w takim widoku, oprócz ViewData i ViewBag istnieje także obiekt Model, którego typem jest typ widoku, - typ widoku określony jest w pierwszej linii kodu widoku, w Razorze odpowiada za to dyrektywa @model typ_widoku, w ASPX jest to odpowiedni tag, - w przypadku nietypowanych widoków Model też istnieje, ale ma typ object, więc przed użyciem trzeba samodzielnie go rzutować na odpowiedni typ, - model przekazujemy do widoku jako parametr, np.: public ActionResult Edit(int id) { Osoba o = PobierzZBazyDanychOsoba(id); return View(o); ;
- dodając silnie typowany widok, możemy określić szablon zawartości widoku (Scaffold template: Create, Edit, Delete, Details, List itp.); np. szablon Create wyświetli formularz pozwalający wprowadzić zawartość obiektu do dodania a szablon List wyświetli listę obiektów; w przypadku szablonów innych niż List typem obiektu jest obiekt podanej klasy, w przypadku List jest to IEnumerable na takich obiektach (lista potrzebuje kolekcji obiektów), - UWAGA: aby szablon zawartości poprawnie rozpoznał pola naszego modelu, muszą być one zdefiniowane jako właściwości, Formularze - jeżeli akcja ma obsługiwać formularz (np. widok typu Create czy Edit), potrzebujemy tak naprawdę dwóch akcji: - pierwsza z nich reaguje na zapytanie typu GET (jest to zapytanie o stronę z formularzem), - druga reaguje na zapytanie typu POST (zapytania wysłane w wyniku wysłania formularza), - obie te akcje powinny mieć taką samą nazwę (to jest ten wspomniany wcześniej wyjątek dopuszczający przeciążanie akcji), - akcja wyświetlająca formularz powinna mieć atrybut [HttpGet], - akcja reagująca na wysłanie formularza powinna mieć atrybut [HttpPost] (gotowy szablon wysyła formularze właśnie POST-em) oraz parametr takiego samego typu jak dodawany obiekt, - MVC przepisze zawartość pól formularza do obiektu i przekaże go jako parametr, [HttpGet] public ActionResult AddOsoba() { return View(); // widok z formularzem [HttpPost] public ActionResult AddOsoba(Models.Osoba o) { if(!jestpoprawne(o)) return View(); // to wyświetli widok z // formularzem... // dodanie o do bazy danych return View( Index, Home ); - możemy zablokować przekazanie wybranego parametru dodając do parametru akcji atrybut Bind z parametrem Exclude: [HttpPost] public ActionResult AddOsoba([Bind(Exclude= id )]Models.Osoba o) {... spowoduje, że parametr id z formularza zostanie zignorowany (nie zostanie przepisany do obiektu o). - UWAGA: jeżeli formularz zawiera także inne pola, możemy je pobrać ze słownika Request.Form
Prosta walidacja - do prostej walidacji służy obiekt ModelState, - walidacja, która wykorzystuje ten obiekt wykonuje odpowiednie testy i w przypadku błędu woła metodę AddModelError z parametrami klucz (sstring) i wartość (string), - po zakończeniu testów sprawdzamy ModelState.IsValid, które jest prawdziwe, tylko gdy nie dodano żadnych błędów, - w widoku możemy skorzystać z: - Html.ValidationSummary([ komunikat ]) wyświetla listę błędów (wartości z AddModelError), poprzedzając ją opcjonalnym komunikatem, - Html.ValidationMessage( klucz, komunikat ) wyświetla komunikat, jeżeli dodano błąd z kluczem klucz, - Html.ValidationMessageFor(model => model.pole) wyświetla komunikat który dodano z kluczem takim samym jak nazwa podanego w pole pola obiektu - np.: [HttpPost] public ActionResult AddOsoba(Models.Osoba o) { if(o.name.trim().length < 3) ModelState.AddModelError( imie, imię za krótkie ); if(o.wiek < 18) ModelState.AddModelError( wiek, za młody ); if(!modelstate.isvalid) return View(); // dane do poprawki... // dodanie o do bazy danych return View( Index, Home ); @Html.ValidationSummary() wyświetli np.: - imie za krótkie - za młody @Html.ValidationMessage( wiek, * ) wyświetli * jeżeli wiek był mniejszy niż 18 @Html.ValidationMessageFor(model => model.wiek) wyświetli za młody jeżeli wiek był mniejszy niż 18 - silnie typowane widoki zawierają już odpowiednie wywołania, wyświetlające błędy walidacji (zob. np. silnie typowany widok dla Create)