Instalacja MVC 3: - http://www.asp.net/mvc - Visual Studio 2010 zawiera wbudowane MVC 2 - wymagany jest.net 4.0, a więc co najmniej Windows XP SP3 (jeżeli ma się zainstalowane Visual Studio 2010, może być Express, to ma się spełnione wszystkie wymagania) - w laboratorium MVC 3 powinno być już zainstalowane Nowy projekt: - ASP.NET MVC 3 Web Application - [Other Languages] Visual C# Web ASP.NET MVC 3 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 - 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; tu konfigurujemy m.in. trasy 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 prezentuje użytkownikowi widoki 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 oraz ewentualne parametry 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 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 - każda trasa musi na wyjściu podać nazwę kontrolera i akcję - 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
- aby dodać nową trasę należy wywołać metodę MapRoute w MvcApplication.MapRoutes, jej parametrami są: - nazwa trasy - szablon URL-a to właśnie ten napis będzie dopasowywany do adresu z zapytania, UWAGA: szablon nie powinien rozpoczynać się znakiem / - 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 ograniczenia o tym później - np. routes.maproute("trasa", "Ludzie/Lista", new { controller = "Users", action = "List"); doda trasę o nazwie trasa ; jeżeli ta trasa zostanie dopasowana, 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 może sobie nie poradzić 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ę), - 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 - do komunikacji z widokiem służą obiekty: - ViewData słownik z kluczami będącymi napisami, - ViewBag obiekt dynamiczny, 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 nazwa_parametru=ograniczenie - ograniczenie może być napisem, wtedy napis opisuje 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 ctx, Route route, string paramname, RouteValueDictionary dict, RouteDirection dir) { int v; try { v = System.Int32.Parse(dict[paramname].ToString()); catch(exception e){ return false; if(v % 10!= 0) return false; else return true;... 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; istotne parametry do Match to: paramname nazwa parametru (np. p1, p2) dict słownik wartości parametrów trasy, czyli np. dict[ p1 ] jest wartością parametru p1 (jako object, więc trzeba jeszcz wziąć ToString()) Model - model jest zwykłą klasą, - modele należy dodawać w katalogu Models, - 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 obsługi sesji 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 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 przecież kolekcję 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 string klucz i string wartość, - 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, - np.: [HttpPost] public ActionResult AddOsoba(Models.Osoba o) { if(o.name.trim().length < 3) ModelState.AddModelError( imie, imie 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 ); uzyte w widoku: @Html.ValidationSummary() wyświetli np.: - imie za krótkie - za młody zaś @Html.ValidationMessage( wiek, * )wyświetli * jeżeli wiek był mniejszy niż 18