4. Szablon akcji
Wróćmy do architektury aplikacji ASP.NET MVC:
![]() |
W poprzednim rozdziale przyjrzeliśmy się procesowi, w wyniku którego żądanie [1] trafia do kontrolera i akcji [2a], które je przetworzą – mechanizm ten nazywamy routingiem. Przedstawiliśmy również różne odpowiedzi, jakie akcja może zwrócić do przeglądarki. Jak dotąd omówiliśmy akcje, które nie wykorzystywały przekazanego im żądania. Żądanie [1] zawiera różne informacje, które ASP.NET i MVC przekazują akcji w postaci szablonu. Nie należy mylić tego terminu z szablonem M widoku V [2c], który jest generowany przez akcję:
![]() |
- żądanie klienta HTTP trafia do [1];
- w [2] informacje zawarte w żądaniu zostaną przekształcone w szablon akcji [3] – często, choć niekoniecznie, jest to klasa – który posłuży jako dane wejściowe dla akcji [4];
- w [4] akcja, na podstawie tego modelu, wygeneruje odpowiedź. Będzie ona składać się z dwóch elementów: widoku V [6] oraz modelu M tego widoku [5];
- widok V [6] wykorzysta swój szablon M [5] do wygenerowania odpowiedzi HTTP przeznaczonej dla klienta.
W modelu MVC akcja [4] stanowi część C (kontroler), model widoku [5] to M, a widok [6] to V.
W niniejszym rozdziale omówiono mechanizmy powiązania między informacjami przekazywanymi w żądaniu, które z natury są ciągami znaków, a modelem akcji, który może być klasą posiadającą właściwości różnych typów.
4.1. Inicjalizacja parametrów akcji
Dodajemy do istniejącego rozwiązania nowy projekt [1], oparty na projektach ASP.NET i MVC:
![]() |
![]() |
- w [2], nazwa nowego projektu;
- w [3, 4] wybieramy projekt bazowy ASP.NET MVC;
- w [5] – nowy projekt.
Nowy projekt zostanie ustawiony jako projekt startowy rozwiązania.
Tak jak w punkcie 3.1, tworzymy kontroler o nazwie [First] [1]:
![]() |
W tym kontrolerze tworzymy następującą akcję o nazwie [Action01]:
using System.Web.Mvc;
namespace Exemple_02.Controllers
{
public class FirstController : Controller
{
// Akcja01
public ContentResult Action01(string nom)
{
return Content(string.Format("Contrôleur=First, Action=Action01, nom={0}", nom));
}
}
}
Nowością jest wiersz 8: metoda [Action01] posiada parametr. W tym rozdziale skupiamy się na różnych sposobach inicjalizacji parametrów akcji. Powyższy parametr [nom] jest inicjalizowany po kolei następującymi wartościami:
Request.Form["nom"] | parametr o nazwie [nom] przesłany przez polecenie POST |
RouteData.Values["nom"] | element o nazwie [nom] z URL |
Request.QueryString["nom"] | parametr o nazwie [nom] wysłany przez polecenie GET |
Request.Files["nom"] | przesłany plik o nazwie [nom] |
Przyjrzyjmy się tym różnym przypadkom. Wprowadźmy bezpośrednio w przeglądarce adres URL [/First/Action01?nom=someone]. Otrzymujemy następującą odpowiedź:
![]() |
Żądanie HTTP wysłane przez przeglądarkę miało następującą postać:
- wiersz 1: żądanie to GET. Żądane URL zawiera parametr [nom]. Po stronie serwera żądanie trafia do akcji [Action01], która ma następujący podpis:
public ContentResult Action01(string nom)
Aby nadać wartość parametrowi „nom”, akcja ASP.NET MVC próbuje kolejno i w podanej kolejności wartości Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"]. Zatrzymuje się, gdy tylko znajdzie jakąkolwiek wartość. Parametr [nom] zawarty w URL z GET został umieszczony przez framework w Request.QueryString["nom"]. To właśnie tą wartością [someone] zostanie zainicjowany parametr [nom] z pliku [Action01]. Następnie wykonywany jest kod z pliku [Action01]:
return Content(string.Format("Contrôleur=First, Action=Action01, nom={0}", nom), "text/plain", Encoding.UTF8);
Ten kod zawiera odpowiedź wysłaną do klienta:
![]() |
Uwaga: mechanizm powiązania parametrów nie rozróżnia wielkości liter. Jeśli więc nasza akcja jest zdefiniowana jako:
public ContentResult Action01(string NOM)
a przekazany parametr to [?NoM=zébulon], powiązanie zostanie poprawnie nawiązane. Parametr [NOM] z [Action01] otrzyma wartość [zébulon].
Teraz poprośmy o ten sam parametr URL, używając POST. W tym celu wykorzystujemy aplikację [Advanced Rest Client]:
![]() |
- w przypadku pliku [1] zostanie wywołany plik URL;
- w [2] zostanie użyte polecenie POST;
- w przypadku [3] – parametry z POST.
Wyślijmy to zapytanie i przyjrzyjmy się logom HTTP. Zapytanie HTTP wygląda następująco:
![]() |
- w [1], POST;
- w [2] – parametry z POST. Technicznie rzecz biorąc, zostały one wysłane za nagłówkami HTTP, po pustej linii sygnalizującej koniec tych nagłówków;
- w [3] – uzyskana odpowiedź. Parametr [nom] z POST został poprawnie odzyskany. Wśród wypróbowanych wartości dla parametru „nom” – Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"], to pierwsza z nich zadziałała.
Teraz zmieńmy domyślną trasę w pliku [App_Start/RouteConfig]. Obecnie ta trasa wygląda następująco:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Zmieńmy ją na:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{nom}",
defaults: new { controller = "Home", action = "Index", nom = UrlParameter.Optional }
);
- w wierszu 3 nazwaliśmy [nom] trzecim elementem trasy;
- w wierszu 4 ten element został zadeklarowany jako opcjonalny.
Teraz skompilujmy ponownie aplikację i wywołajmy URL [/First/Action01/zébulon] bezpośrednio w przeglądarce. Otrzymujemy następującą odpowiedź:
![]() |
Wśród wypróbowanych wartości parametru „name”: Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"], zadziałała druga z nich.
Wykonajmy to samo zapytanie z POST i [Advanced Rest Client]:
![]() |
- w przypadku [1] nadaliśmy wartość elementowi {nazwa} ścieżki;
- w przypadku [2] dodajemy parametr [nom] do wysyłanego żądania;
- uzyskana odpowiedź ma postać [3].
Wśród wypróbowanych wartości dla parametru [nom], Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"], dwie były prawidłowe – dwie pierwsze. Zastosowano pierwszą z nich.
4.2. Sprawdź poprawność parametrów akcji
Jeśli akcja posiada parametr o nazwie [p], ASP.NET oraz MVC będą próbowały przypisać do niego jedną z wartości: Request.Form["p"], RouteData.Values["p"], Request.QueryString["p"], Request.Files["p"]. Pierwsze trzy wartości to ciągi znaków. Jeśli parametr [p] nie jest typu [string], mogą wystąpić problemy.
Utwórzmy następującą nową akcję:
// Akcja02
public ContentResult Action02(int age)
{
string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}", RouteData.Values["controller"], RouteData.Values["action"],age);
return Content(texte, "text/plain", Encoding.UTF8);
}
- W wierszu 2 akcja [Action02] przyjmuje parametr o nazwie [age] typu int. Pobierany ciąg znaków musi dać się przekonwertować na typ int.
Wywołajmy akcję URL [http://localhost:55483/First/Action02?age=21]. Otrzymujemy następującą stronę:
![]() |
Wywołajmy URL [http://localhost:55483/First/Action02?age=21x]. Otrzymujemy następującą stronę:
![]() |
Tym razem otrzymaliśmy stronę błędu. Warto przyjrzeć się nagłówkom HTTP wysłanym przez serwer w tym przypadku:
- wiersz 1: serwer odpowiedział kodem [500 Internal Server Error] i wysłał stronę HTML (wiersz 3) o rozmiarze 12438 bajtów (wiersz 5), aby wyjaśnić możliwe przyczyny tego błędu.
Teraz utwórzmy następującą akcję [Action03]:
// Akcja03
public ContentResult Action03(int? age)
{
...
}
[Action03] jest identyczne z [Action02], z tą różnicą, że zmieniono typ parametru [age] na int?, co oznacza liczbę całkowitą lub null.
Wywołajmy URL i [http://localhost:55483/First/Action03?age=21x]. Otrzymujemy następującą stronę:
![]() |
ASP.NET MVC nie zdołał przekonwertować [21x] na typ int. Następnie przypisał parametrowi [age] wartość null, zgodnie z możliwościami jego typu int?. Można jednak sprawdzić, czy parametr otrzymał wartość z zapytania, czy nie.
Tworzymy następującą nową akcję [Action04]:
// Akcja04
public ContentResult Action04(int? age)
{
bool valide = ModelState.IsValid;
string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}, valide={3}", RouteData.Values["controller"], RouteData.Values["action"], age, valide);
return Content(texte, "text/plain", Encoding.UTF8);
}
- wiersz 2: zachowano typ [int?]. Pozwala to w szczególności, aby żądanie nie podawało parametru [age], który wówczas otrzymuje wartość null;
- wiersz 4: sprawdzamy, czy model akcji jest prawidłowy. Model akcji składa się z zestawu wszystkich jej parametrów, w tym przypadku [age]. Model jest prawidłowy, jeśli wszystkie parametry otrzymały wartość z żądania lub wartość null, o ile pozwala na to typ parametru;
- wiersz 5: dodajemy wartość zmiennej [valide] do tekstu wysyłanego do klienta.
Wywołajmy URL [http://localhost:55483/First/Action04?age=21x]. Otrzymujemy następującą stronę:
![]() |
ASP.NET MVC nie zdołał przekonwertować [21x] na typ int. Następnie przypisał wartość null do parametru [age], zgodnie z możliwościami jego typu int?. Wystąpiły jednak błędy konwersji, co pokazuje wartość [valide].
Możliwe jest wyświetlenie komunikatu o błędzie związanego z nieudaną konwersją. Przyjrzyjmy się następującej nowej akcji:
// Akcja05
public ContentResult Action05(int? age)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}, valide={3}, erreurs={4}", RouteData.Values["controller"], RouteData.Values["action"], age, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Nowością jest wiersz 4. Wywołuje się w nim prywatną metodę [getErrorMessagesFor], do której przekazywany jest stan modelu akcji. Zwraca ona ciąg znaków zawierający komunikaty wszystkich błędów, które wystąpiły. Metoda ta wygląda następująco:
private string getErrorMessagesFor(ModelStateDictionary état)
{
List<String> erreurs = new List<String>();
string messages = string.Empty;
if (!état.IsValid)
{
foreach (ModelState modelState in état.Values)
{
foreach (ModelError error in modelState.Errors)
{
erreurs.Add(getErrorMessageFor(error));
}
}
foreach (string message in erreurs)
{
messages += string.Format("[{0}]", message);
}
}
return messages;
}
- wiersz 1: rzeczywisty parametr [ModelState] przekazany do metody jest typu [ModelStateDictionary];
- wiersz 3: lista komunikatów o błędach, początkowo pusta;
- wiersz 5: sprawdzamy, czy stan przekazany jako parametr jest prawidłowy. Jeśli nie, to agregujemy wszystkie komunikaty o błędach w jeden ciąg znaków;
- wiersz 7: typ [ModelStateDictionary] posiada właściwość [Values], która jest zbiorem typów [ModelState]. Na każdy element modelu przypada jeden [ModelState]. Na przykład:
- ModelState["age"]: stan modelu akcji dla parametru [age],
- ModelState["age"].Errors: zbiór błędów dla tego parametru. Błędy mają postać [ModelError],
- ModelState["age"].Errors[i].ErrorMessage: ewentualny komunikat o błędzie nr i dla parametru [age] szablonu
- ModelState["age"].Errors[i].Exception: wyjątek błędu nr i z kolekcji błędów dotyczącej parametru [age],
- ModelState["age"].Errors[i].Exception.InnerException: przyczyna tego wyjątku,
- ModelState["age"].Errors[i].Exception.InnerException.Message: komunikat dotyczący przyczyny wyjątku;
- wiersz 9: przeglądamy kolekcję [Errors] dla konkretnego obiektu [ModelState];
- wiersz 11: pobieramy komunikat o błędzie z konkretnego obiektu [ModelError] i dodajemy go do listy komunikatów o błędach z wiersza 3;
- wiersze 14–17: elementy listy komunikatów o błędach są łączone w jeden ciąg znaków.
Metoda [getErrorMessageFor] z wiersza 11 wygląda następująco:
private string getErrorMessageFor(ModelError error)
{
if (error.ErrorMessage != null && error.ErrorMessage.Trim() != string.Empty)
{
return error.ErrorMessage;
}
if (error.Exception != null && error.Exception.InnerException == null && error.Exception.Message != string.Empty)
{
return error.Exception.Message;
}
if (error.Exception != null && error.Exception.InnerException != null && error.Exception.InnerException.Message != string.Empty)
{
return error.Exception.InnerException.Message;
}
return string.Empty;
}
- wiersz 1: odbierany jest typ [ModelError], który zawiera błąd dotyczący jednego z elementów modelu akcji. Komunikat o błędzie jest pobierany z trzech różnych miejsc:
- w pliku [ModelError].ErrorMessage, wiersze 3–6;
- w pliku [ModelError].Exception.Message, wiersze 7–10;
- w pliku [ModelError].Exception.InnerException.Message, wiersze 11–14;
Podczas testów zauważono, że komunikat o błędzie występuje w tych trzech miejscach w zależności od rodzaju elementu modelu. Musi istnieć reguła, która pozwala bezbłędnie uzyskać komunikat o błędzie powiązany z danym elementem modelu, ale nie znam jej. Szukam go więc w różnych miejscach, w których mogę go znaleźć, i to w określonej kolejności. Gdy tylko zostanie znaleziony niepusty komunikat, jest on zwracany.
Wywołajmy URL [http://localhost:55483/First/Action05?age=21x]. Otrzymujemy następującą stronę:
![]() |
4.3. Akcja z wieloma parametrami
Rozważmy następującą nową akcję:
// Akcja06
public ContentResult Action06(double? poids, int? age)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], poids, age, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- wiersz 2: mamy dwa parametry: [poids] i [age].
Opisane wcześniej zasady mają teraz zastosowanie do obu parametrów. Oto kilka przykładów wykonania:
![]() |
![]() |
4.4. Wykorzystanie klasy jako szablonu akcji
Zdefiniujmy klasę, która będzie szablonem dla akcji. Umieścimy ją w folderze [Models] [1].
![]() |
Jej kod będzie wyglądał następująco:
namespace Exemple_02.Models
{
public class ActionModel01
{
public double? Poids { get; set; }
public int? Age { get; set; }
}
}
Nasza klasa posiada jako właściwości automatyczne dwa parametry [Poids] i [Age], które omówiliśmy wcześniej. Klasa ta będzie parametrem wejściowym akcji [Action07]:
// Akcja07
public ContentResult Action07(ActionModel01 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- wiersz 2: model akcji jest instancją typu [ActionModel01].
Wróćmy do tych samych dwóch przykładów, co poprzednio:
![]() |
![]() |
Należy zauważyć, że przypisywanie parametrów nie rozróżnia wielkości liter. Parametry zapytania to [age] i [poids]. Zostały one przypisane do właściwości [Age] i [Poids] klasy [ModelAction01].
Ponadto do tej pory korzystaliśmy z zapytań HTTP i [GET]. Pokażmy, że zapytania [POST] zachowują się w ten sam sposób. W tym celu ponownie skorzystajmy z aplikacji [Advanced Rest Client]:
![]() |
- w [1], żądana URL;
- w [2] zostanie to wykonane za pomocą polecenia POST;
- w [3] parametry z POST.
Otrzymujemy tę samą odpowiedź, co w przypadku GET:
![]()
4.5. Model akcji z ograniczeniami ważności – 1
W przypadku poprzedniego modelu:
namespace Exemple_02.Models
{
public class ActionModel01
{
public double? Poids { get; set; }
public int? Age { get; set; }
}
}
parametry [poids] i [age] mogą nie występować w zapytaniu. W takim przypadku właściwości [Poids] i [Age] przyjmują wartość [null] i nie zgłaszany jest żaden błąd. Można by chcieć przekształcić szablon w następujący sposób:
namespace Exemple_02.Models
{
public class ActionModel01
{
public double Poids { get; set; }
public int Age { get; set; }
}
}
W wierszach 5 i 6 właściwości [Poids] i [Age] nie mogą już przyjmować wartości [null]. Zobaczmy, co się dzieje z tym nowym szablonem, gdy parametry [poids] i [age] nie występują w zapytaniu.
![]() |
Nie wystąpiły żadne błędy, a właściwości [Poids] i [Age] zachowały swoje wartości początkowe: 0. ASP.NET MVC:
- utworzono instancję modelu za pomocą operatora new ActionModel01. W tym momencie właściwości [Poids] i [Age] otrzymały wartość 0;
- nie przypisał żadnych wartości do tych dwóch właściwości, ponieważ nie istniał żaden parametr o tej nazwie.
Pierwszy model pozwala nam sprawdzić brak parametru: wówczas odpowiednia właściwość przyjmuje wartość [null]. Drugi model nie pozwala nam na to. Możliwe jest dodanie innych ograniczeń walidacji poza samym typem parametrów. Teraz je przedstawimy.
Rozważmy następujący nowy model akcji:
![]() |
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
public class ActionModel02
{
[Required]
[Range(1, 200)]
public double? Poids { get; set; }
[Required]
[Range(1, 150)]
public int? Age { get; set; }
}
}
- wiersz 6: wskazuje, że pole [Poids] jest wymagane;
- wiersz 7: wskazuje, że pole [Poids] musi mieścić się w przedziale [1,200];
- wiersz 9: wskazuje, że pole [Age] jest wymagane;
- wiersz 7: wskazuje, że pole [Age] musi mieścić się w przedziale [1,150];
Akcją wykorzystującą ten szablon będzie następująca akcja [Action08]:
// Akcja08
public ContentResult Action08(ActionModel02 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- wiersz 2: akcja otrzymuje instancję szablonu [ActionModel02];
Przeprowadźmy kilka testów:
![]() |
![]() |
![]() |
![]() |
Błędy zostały prawidłowo wykryte. Teraz zmodyfikujmy model w następujący sposób:
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
public class ActionModel02
{
[Required]
[Range(1, 200)]
public double Poids { get; set; }
[Required]
[Range(1, 150)]
public int Age { get; set; }
}
}
W wierszach 8 i 11 właściwości nie mogą już przyjmować wartości [null]. Skompilujmy kod i ponownie przeprowadźmy test bez parametrów:
![]() |
Brak parametrów spowodował, że właściwości [Poids] i [Age] zachowały wartość nadaną podczas instancjonowania modelu: 0. Następnie następuje walidacja. Atrybut [Required] jest wówczas spełniony. Widać, że powyższy komunikat o błędzie dotyczy atrybutu [Range]. Aby więc sprawdzić obecność parametru, powiązana właściwość musi to być nullable, tj. musi móc przyjąć wartość null.
Wróćmy do początkowego modelu [ActionModel02] i rozważmy akcję, której model składa się z instancji [ActionModel02] oraz typu [DateTime] nullable:
// Akcja09
public ContentResult Action09(ActionModel02 modèle, DateTime? date)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, date={4}, valide={5}, erreurs={6}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, date, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Przeprowadźmy kilka testów:
![]() |
Nie przekazano żadnych parametrów do akcji. Atrybuty [Required] właściwości [Poids] i [Age] spełniły swoją rolę. Data otrzymała wartość null i nie zgłoszono żadnego błędu.
Teraz przekazujemy nieprawidłowe parametry:
![]() |
Teraz przekazujemy prawidłowe wartości:
![]() |
Przyjrzyjmy się innym ograniczeniom poprawności. Nowy wzorzec działania wygląda następująco:
![]() |
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
public class ActionModel03
{
[Required(ErrorMessage = "Le paramètre email est requis")]
[EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
public string Email { get; set; }
[Required(ErrorMessage = "Le paramètre jour est requis")]
[RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
public string Jour { get; set; }
[Required(ErrorMessage = "Le paramètre info1 est requis")]
[MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
public string Info1 { get; set; }
[Required(ErrorMessage = "Le paramètre info2 est requis")]
[MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
public string Info2 { get; set; }
[Required(ErrorMessage = "Le paramètre info3 est requis")]
[MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
[MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
public string Info3 { get; set; }
}
}
- wiersz 6: atrybut [Required], tym razem z komunikatem o błędzie, który sami ustalamy;
- wiersz 7: atrybut [EMailAddress] wymaga, aby pole [Email] zawierało adres e-mail w prawidłowym formacie;
- wiersz 11: atrybut [RegularExpression] wymaga, aby pole [Jour] zawierało ciąg składający się z jednej lub dwóch cyfr. Pierwszym parametrem jest wyrażenie regularne, które musi zostać sprawdzone w polu;
- wiersz 15: atrybut [MaxLength] wymaga, aby pole [Info1] miało maksymalnie 4 znaki;
- wiersz 19: atrybut [MinLength] określa, że pole [Info2] musi zawierać co najmniej 2 znaki;
- wiersze 23–24: atrybuty [MaxLength] i [MinLength] łącznie wymagają, aby pole [Info3] miało dokładnie 4 znaki;
Akcja [Action10] będzie korzystać z następującego wzorca:
// Akcja10
public ContentResult Action10(ActionModel03 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("email={0}, jour={1}, info1={2}, info2={3}, info3={4}, erreurs={5}",
modèle.Email, modèle.Jour, modèle.Info1, modèle.Info2, modèle.Info3, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Przeprowadźmy kilka testów z tą akcją.
Najpierw bez parametrów:
![]() |
Następnie z nieprawidłowymi parametrami:
![]() |
Następnie z poprawnymi parametrami:
![]() |
4.6. Model akcji z ograniczeniami poprawności – 2
Przedstawiamy kolejne ograniczenia integralności. Nowym modelem akcji będzie następująca klasa [ActionModel04]:
![]() |
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
public class ActionModel04
{
[Required(ErrorMessage="Le paramètre url est requis")]
[Url(ErrorMessage="URL invalide")]
public string Url { get; set; }
[Required(ErrorMessage = "Le paramètre info1 est requis")]
public string Info1 { get; set; }
[Required(ErrorMessage = "Le paramètre info2 est requis")]
[Compare("Info1",ErrorMessage="Les paramètres info1 et info2 doivent être identiques")]
public string Info2 { get; set; }
[Required(ErrorMessage = "Le paramètre cc est requis")]
[CreditCard(ErrorMessage = "Le paramètre cc n'est pas un n° de carte de crédit valide")]
public string Cc { get; set; }
}
}
- wiersz 8: wymaga, aby pole z adnotacją miało prawidłową wartość URL;
- wiersz 13: wymaga, aby właściwości [Info1] i [Info2] miały tę samą wartość;
- wiersz 16: wymaga, aby pole z adnotacją było prawidłowym numerem karty kredytowej.
Akcja wykorzystująca ten szablon będzie wyglądała następująco:
// Akcja11
public ContentResult Action11(ActionModel04 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("URL={0}, Info1={1}, Info2={2}, CC={3},erreurs={4}",
modèle.Url, modèle.Info1, modèle.Info2, modèle.Cc, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Aby przetestować akcję [Action11], używamy aplikacji [Advanced Rest Client]:
![]() |
- w [1] – URL z akcji [Action11];
- w [2], ta URL będzie wymagana wraz z POST;
- w [3] wybiera się zakładkę [Form];
- w [4] należy wprowadzić wartości czterech wymaganych parametrów. Ta inicjalizacja jest funkcją oferowaną przez [ARC]. Rzeczywiste wysłane parametry można sprawdzić w zakładce [Raw] [5];
![]() |
- w [6] – parametry z POST.
W odpowiedzi na to zapytanie otrzymujemy następującą odpowiedź:
![]() |
Przekażmy nieprawidłowe parametry:
![]() |
Otrzymujemy wówczas następującą odpowiedź:
4.7. Model akcji z ograniczeniami poprawności – 3
Czasami dostępne ograniczenia integralności nie są wystarczające. W takim przypadku można je stworzyć samodzielnie. Można w szczególności wykorzystać model implementujący interfejs [IValidatableObject]. W tym przypadku dodajemy własne weryfikacje modelu w metodzie [Validate] tego interfejsu. Przyjrzyjmy się przykładowi. Nowym modelem akcji będzie następująca klasa [ActionModel05]:
![]() |
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
public class ActionModel05 : IValidatableObject
{
[Required(ErrorMessage = "Le paramètre taux est requis")]
public double? Taux { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> résultats = new List<ValidationResult>();
bool ok = Taux < 4.2 || Taux > 6.7;
if (!ok)
{
résultats.Add(new ValidationResult("Le paramètre taux doit être < 4.2 ou > 6.7", new string[] { "Taux" }));
}
return résultats;
}
}
}
- wiersz 6: model implementuje interfejs [IValidatableObject];
- wiersz 10: metoda [Validate] tego interfejsu. Zwraca ona zbiór elementów typu [ValidationResult]. Ten typ hermetyzuje błędy, które chcemy zgłosić;
- wiersz 9: prawidłowa wartość to wartość <4,2 lub > 6,7;
- wiersz 12: tworzymy pustą listę elementów typu [ValidationResult];
- wiersz 13: sprawdzana jest poprawność właściwości [Taux];
- wiersze 14–17: jeśli właściwość [Taux] jest nieprawidłowa, dodaje się element typu [ValidationResult] do listy wyników. Pierwszym parametrem jest komunikat o błędzie. Drugim parametrem, opcjonalnym, jest zbiór właściwości, których dotyczy ten błąd.
Akcja wykorzystująca ten szablon będzie wyglądała następująco:
// Akcja12
public ContentResult Action12(ActionModel05 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Oto przykład wykonania:
![]() |
4.8. Szablon akcji typu Tabela lub Lista
Rozważmy następującą akcję [Action13]:
// Akcja13
public ContentResult Action13(string[] data)
{
string strData = "";
if (data != null && data.Length != 0)
{
strData = string.Join(",", data);
}
string texte = string.Format("data=[{0}]", strData);
return Content(texte, "text/plain", Encoding.UTF8);
}
- wiersz 2: model akcji składa się z tabeli o nazwie [string]. Pozwala nam on pobrać parametr o nazwie [data], który może występować wielokrotnie w parametrach zapytania, np. jako [?data=data1&data=data2&data=data3]. Różne parametry zapytania o nazwie [data] zostaną umieszczone w tablicy [data] modelu akcji. Sytuacja ta ma miejsce w przypadku list z wieloma opcjami do wyboru. Przeglądarka wysyła wówczas różne wartości wybrane przez użytkownika, używając tej samej nazwy parametru.
Oto przykład:
![]() |
Szablonem może być również lista:
// Akcja14
public ContentResult Action14(List<int> data)
{
string erreurs = getErrorMessagesFor(ModelState);
string strData = "";
if (data != null && data.Count != 0)
{
strData = string.Join(",", data);
}
string texte = string.Format("data=[{0}], erreurs=[{1}]", strData, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Szablon jest tutaj listą liczb całkowitych (wiersz 2). Oto pierwsze wykonanie:
![]() |
oraz drugie:
![]() |
4.9. Filtrowanie szablonu akcji
Czasami dysponujemy szablonem, ale chcemy, aby tylko niektóre elementy szablonu były inicjowane przez zapytanie HTTP. Rozważmy następujący szablon akcji [ActionModel06]:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_02.Models
{
[Bind(Exclude = "Info2")]
public class ActionModel06
{
[Required(ErrorMessage = "Le paramètre [info1] est requis")]
public string Info1 { get; set; }
public string Info2 { get; set; }
}
}
- wiersze 9–10: parametr [info1] jest obowiązkowy;
- wiersz 6: parametr [info2] z wiersza 12 jest wykluczony z powiązania zapytania HTTP z jego szablonem.
Działanie będzie wyglądało następująco: [Action15]:
// Akcja 15
public ContentResult Action15(ActionModel06 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("valide={0}, info1={1}, info2={2}, erreurs={3}", ModelState.IsValid, modèle.Info1, modèle.Info2, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Oto przykładowe wykonanie:
![]() |
- w [1]: parametr [info2] jest przekazywany do URL;
- w [2]: właściwość [Info2] modelu akcji pozostała pusta.
4.10. Rozszerzenie modelu powiązania danych
Wróćmy do architektury wykonania akcji:
![]() |
Klasa akcji jest instancjonowana na początku żądania klienta i niszczona po jego zakończeniu. Nie może więc służyć do przechowywania danych między dwoma żądaniami, nawet jeśli jest wywoływana wielokrotnie. Można chcieć przechowywać dwa rodzaje danych:
- dane współdzielone przez wszystkich użytkowników aplikacji internetowej. Są to zazwyczaj dane tylko do odczytu. Do wdrożenia tego współdzielenia danych wykorzystywane są trzy pliki:
- [Web.Config]: plik konfiguracyjny aplikacji
- [Global.asax, Global.asax.cs]: umożliwia zdefiniowanie klasy, zwanej globalną klasą aplikacji, której czas życia pokrywa się z czasem działania aplikacji, a także menedżerów dla niektórych zdarzeń tej samej aplikacji.
Globalna klasa aplikacji pozwala zdefiniować dane, które będą dostępne dla wszystkich zapytań wszystkich użytkowników.
- dane współdzielone przez żądania tego samego klienta. Dane te są przechowywane w obiekcie zwanym sesją. Termin „sesja klienta” odnosi się w tym przypadku do pamięci klienta. Wszystkie żądania danego klienta mają dostęp do tej sesji. Mogą one zapisywać w niej informacje oraz je odczytywać.
![]() |
Powyżej przedstawiono rodzaje pamięci, do których ma dostęp akcja:
- pamięć aplikacji, która w większości przypadków zawiera dane tylko do odczytu i jest dostępna dla wszystkich użytkowników;
- pamięć konkretnego użytkownika, czyli sesja, która zawiera dane do odczytu i zapisu i jest dostępna dla kolejnych żądań tego samego użytkownika;
- nie przedstawiono powyżej, istnieje pamięć żądania lub kontekst żądania. Żądanie użytkownika może być przetwarzane przez kilka kolejnych akcji. Kontekst żądania pozwala akcji 1 przekazać informacje do akcji 2.
Przyjrzyjmy się pierwszemu przykładowi ilustrującemu te różne rodzaje pamięci:
Najpierw modyfikujemy plik [Web.config] w projekcie [Exemple-02] w następujący sposób:
<appSettings>
<add key="webpages:Version" value="2.0.0.0" />
...
<add key="infoAppli1" value="infoAppli1"/>
</appSettings>
Dodajemy wiersz 4, który przy kluczu [infoAppli1] przypisuje wartość [infoAppli1]. Będzie to nasz zapis o zasięgu [Application]: będzie on dostępny dla wszystkich zapytań wszystkich użytkowników.
Następnie modyfikujemy metodę [Application_Start] w pliku [Global.asax]. Metoda ta jest wykonywana tylko raz podczas uruchamiania aplikacji. W tym miejscu należy wykorzystać plik [Web.config]:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// inicjalizacja aplikacji
Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
}
Dodajemy wiersz 10. Wykonuje on dwie czynności:
- pobiera wartość klucza [infoAppli1] z pliku [Web.config] za pomocą klasy [System.Configuration.ConfigurationManager];
- zapisuje ją w słowniku [HttpApplication.Application], powiązanym z kluczem [infoAppli1]. Wszystkie akcje mają dostęp do tego słownika.
W tym samym pliku [Gloabal.asax] dodaje się następującą metodę [Session_Start]:
protected void Session_Start()
{
// inicjalizacja licznika
Session["compteur"] = 0;
}
Metoda [Session_Start] jest wykonywana dla każdego nowego użytkownika. Czym jest nowy użytkownik? Użytkownik jest „śledzony” za pomocą tokenu sesji. Token ten jest:
- tworzony przez serwer WWW i wysyłany do nowego użytkownika w nagłówkach HTTP pierwszej odpowiedzi, jaką otrzymuje;
- przesyłany z powrotem przez przeglądarkę użytkownika przy każdym nowym żądaniu, które wysyła. Pozwala to serwerowi rozpoznać użytkownika i zarządzać dla niego pamięcią zwaną sesją użytkownika.
Serwer WWW rozpoznaje, że ma do czynienia z nowym użytkownikiem, gdy ten nie prześle mu tokenu sesji. Serwer tworzy wówczas token dla niego.
W wierszu 4 powyżej umieszczamy w sesji użytkownika licznik, który będzie zwiększany przy każdym żądaniu tego użytkownika. Ilustruje to pamięć powiązaną z danym użytkownikiem. Klasa [Session] jest używana jako słownik (wiersz 4).
Po wykonaniu tej czynności piszemy następującą akcję [Action16]:
// Akcja 16
public ContentResult Action16()
{
// pobieranie kontekstu żądania HTTP
HttpContextBase contexte = ControllerContext.HttpContext;
// pobieranie informacji o zakresie aplikacji
string infoAppli1 = contexte.Application["infoAppli1"] as string;
// oraz informacje o zakresie sesji
int? compteur = contexte.Session["compteur"] as int?;
compteur++;
contexte.Session["compteur"] = compteur;
// odpowiedź dla klienta
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
- wiersz 5: pobieramy kontekst aktualnie przetwarzanej prośby HTTP. Kontekst ten zapewni nam dostęp do danych z zakresów [Application] i [Session];
- wiersz 7: pobieramy informacje z zakresu [Application];
- wiersz 9: pobieramy licznik z sesji;
- wiersze 10–11: jest on zwiększany, a następnie ponownie umieszczany w sesji;
- wiersze 13–14: obie informacje są wysyłane do klienta.
Oto przykłady wykonania:
[Action16] jest żądane po raz pierwszy jako [1], a następnie strona jest odświeżana dwukrotnie jako [F5] i [2]:
![]() |
W przypadku [2] klient wysłał łącznie trzy żądania. Za każdym razem udało mu się pobrać licznik zaktualizowany przez poprzednie żądanie.
Aby zasymulować drugiego użytkownika, używamy drugiej przeglądarki do wysłania tego samego żądania URL:
![]() |
W przypadku [3] drugi użytkownik prawidłowo pobiera te same informacje o zasięgu co w przypadku [Application], ale posiada własny licznik zasięgu [Session].
Wróćmy do kodu akcji [Action16]:
// Akcja 16
public ContentResult Action16()
{
// pobieramy kontekst żądania HTTP
HttpContextBase contexte = ControllerContext.HttpContext;
// pobieramy informacje z zakresu aplikacji
string infoAppli1 = contexte.Application["infoAppli1"] as string;
// oraz informacje dotyczące zakresu sesji
int? compteur = contexte.Session["compteur"] as int?;
compteur++;
contexte.Session["compteur"] = compteur;
// odpowiedź dla klienta
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
Jednym z celów frameworka ASP.NET MVC jest umożliwienie testowania kontrolerów i akcji w izolacji, bez serwera WWW. Jednak w wierszu 5 widać, że kontekst żądania HTTP jest niezbędny do pobrania informacji z zakresu [Application] oraz z zakresu [Session]. Proponujemy utworzenie nowej akcji [Action17], która otrzymywałaby dane z zakresów [Application] i [Session] jako parametry:
// Akcja 17
public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
{
// pobierane są informacje z zakresu aplikacji
string infoAppli1 = applicationData.InfoAppli1;
// oraz te z zakresu sesji
int compteur = sessionData.Compteur++;
// odpowiedź dla klienta
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
Kod nie zawiera już żadnych zależności od zapytania HTTP. Dzięki temu można go testować niezależnie od serwera WWW.
Zobaczmy, jak to zrobić. Najpierw musimy utworzyć klasy [ApplicationModel] i [SessionModel], które będą odpowiednio enkapsulować dane z zakresu [Application] i [Session]. Są to następujące klasy:
![]() |
namespace Exemple_02.Models
{
public class ApplicationModel
{
public string InfoAppli1 { get; set; }
}
}
namespace Exemple_02.Models
{
public class SessionModel
{
public int Compteur { get; set; }
public SessionModel()
{
Compteur = 0;
}
}
}
Następnie musimy zmodyfikować metody [Application_Start] i [Session_Start] w pliku [Global.asax]:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// inicjalizacja aplikacji – przypadek 1
Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
// inicjalizacja aplikacji – przypadek 2
ApplicationModel data=new ApplicationModel();
data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
Application["data"] = data;
}
protected void Session_Start()
{
// inicjalizacja licznika – przypadek 1
Session["compteur"] = 0;
// inicjalizacja licznika – przypadek 2
Session["data"] = new SessionModel();
}
}
- wiersz 14: tworzona jest instancja metody [ApplicationModel];
- wiersz 15: jest ona inicjowana;
- wiersz 16: i umieszczona w słowniku [Application], powiązana z kluczem [data]. [Application] jest właściwością klasy [HttpApplication] z wiersza 1;
- wiersz 24: tworzona jest instancja klasy [SessionModel] i umieszczana w słowniku klasy [Session], powiązana z kluczem [data]. [Session] jest właściwością klasy [HttpApplication] z wiersza 1;
Jeśli opieramy się na tym, co widzieliśmy do tej pory, sygnatura
public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
oznacza, że zapytanie HTTP przetwarzane przez akcję powinno zawierać parametry o nazwach [applicationData] i [sessionData]. Tak jednak nie będzie. Musimy utworzyć nowy model powiązania danych, aby w przypadku, gdy akcja otrzyma jako parametr typ:
- [ApplicationModel], dostarczone zostaną jej dane o zakresie [Application] i kluczu [data];
- [SessionModel], pod warunkiem że zostaną mu przekazane dane o zakresie [Session] i kluczu [data].
W tym celu należy utworzyć klasy implementujące interfejs [IModelBinder].
Zaczynamy od utworzenia folderu o nazwie [Infrastructure] w projekcie [Exemple-02]:
![]() |
W tym folderze tworzymy następującą klasę [ApplicationModelBinder]:
using System.Web.Mvc;
namespace Exemple_02.Infrastructure
{
public class ApplicationModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// przekazywanie danych zakresu [Application]
return controllerContext.RequestContext.HttpContext.Application["data"];
}
}
}
- wiersz 5: klasa ta implementuje interfejs [IModelBinder]. Aby zrozumieć jej kod, należy wiedzieć, że będzie ona wywoływana za każdym razem, gdy akcja będzie miała parametr typu [ApplicationModel]. To powiązanie [ApplicationModel] --> [ApplicationModelBinder] zostanie utworzone podczas uruchamiania aplikacji, w metodzie [Application_Start] klasy [Global.asax];
- wiersz 7: jedyna metoda interfejsu [IModelBinder];
- wiersz 7: parametr typu [ControllerContext] zapewnia nam dostęp do aktualnie przetwarzanej zapytania HTTP;
- wiersz 7: parametr typu [ModelBindingContext] zapewnia dostęp do informacji o modelu, który ma zostać utworzony, w tym przypadku typu [ApplicationModel];
- wiersz 7: wynikiem [BindModel] jest obiekt, który zostanie przypisany do powiązanego parametru, w tym przypadku parametru typu [ApplicationModel];
- wiersz 10: wystarczy zwrócić obiekt o zakresie [Application] i kluczu [data].
Klasa [SessionModelBinder] opiera się na tym samym schemacie:
using System.Web.Mvc;
namespace Exemple_02.Infrastructure
{
public class SessionModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// przekazywanie danych z zakresu [Session]
return controllerContext.HttpContext.Session["data"];
}
}
}
Pozostaje nam tylko powiązać każdy z modeli [XModel] z jego odpowiednikiem binder i [XModelBinder]. Odbywa się to w metodzie [Application_Start] modelu [Global.asax]:
protected void Application_Start()
{
....
// inicjalizacja aplikacji – przypadek 2
ApplicationModel data=new ApplicationModel();
data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
Application["data"] = data;
// modele powiązań
ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
- wiersz 9: gdy akcja będzie miała parametr typu [ApplicationModel], zostanie wywołana metoda [ApplicationModelBinder.Bind]. Wiadomo, że zwraca ona dane o zakresie [Application] powiązane z kluczem [data];
- wiersz 10: to samo dotyczy typu [SessionModel].
Wróćmy do naszej akcji [Action17]:
// Akcja 17
public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
{
// pobieramy informacje z zakresu aplikacji
string infoAppli1 = applicationData.InfoAppli1;
// oraz z zakresu sesji
sessionData.Compteur++;
int compteur = sessionData.Compteur;
// odpowiedź dla klienta
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
- wiersz 2: gdy zostanie wywołana akcja [Action17], otrzyma ona jako
- pierwszym parametrem: dane zakresu [Application] powiązane z kluczem [data],
- jako drugi parametr: dane zakresu [Session] powiązane z kluczem [data];
Oba te dane mogą być dowolnie złożone i obejmować w jednym przypadku wszystkie dane zakresu [Application], a w drugim wszystkie dane zakresu [Session].
Oto przykład wykonania akcji [Action17]:
![]() |
4.11. Późne powiązanie szablonu akcji
Napisaliśmy następującą akcję [Action12]:
// Akcja 12
public ContentResult Action12(ActionModel05 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
W tle akcja ASP.NET MVC:
- tworzy instancję typu [ActionModel05], wykorzystując jej konstruktor bez parametrów;
- inicjuje ją danymi z żądania, które mają tę samą nazwę (bez względu na wielkość liter) co jedna z właściwości [ActionModel05].
Czasami takie zachowanie nam nie odpowiada. Dzieje się tak zwłaszcza wtedy, gdy chcemy użyć konkretnego konstruktora modelu akcji. Można wtedy postąpić w następujący sposób:
// Akcja 18
public ContentResult Action18()
{
ActionModel05 modèle = new ActionModel05();
TryUpdateModel(modèle);
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- wiersz 2: akcja nie otrzymuje już parametrów. Nie ma więc już automatycznego powiązania danych;
- wiersz 4: samodzielnie tworzymy instancję modelu akcji. W tym miejscu można użyć innego konstruktora;
- wiersz 5: inicjujemy model danymi z żądania. Zadanie to wykonują ASP.NET i MVC. Robią to w taki sam sposób, jak gdyby model był przekazany jako parametr;
- wiersz 6: znajdujemy się teraz w takiej samej sytuacji jak w akcji [Action12].
Oto przykład wykonania:
![]() |
4.12. Conclusion
Wróćmy do architektury aplikacji ASP.NET MVC:
![]() |
Żądanie [1] zawiera różne informacje, które ASP.NET MVC przekazuje [2a] do akcji w postaci szablonu, który nazwaliśmy szablonem akcji.
![]() |
- żądanie klienta HTTP dociera do [1];
- w [2] informacje zawarte w żądaniu są przekształcane w model akcji [3];
- w [4] akcja, na podstawie tego szablonu, wygeneruje odpowiedź. Będzie ona składać się z dwóch elementów: widoku V [6] oraz szablonu M tego widoku [5];
- widok V [6] wykorzysta swój szablon M [5] do wygenerowania odpowiedzi HTTP przeznaczonej dla klienta.
W modelu MVC akcja [4] stanowi część C (kontroler), model widoku [5] to M, a widok [6] to V.
W niniejszym rozdziale omówiono mechanizmy powiązania między informacjami przekazywanymi w żądaniu, które z natury są ciągami znaków, a modelem akcji, który może być klasą posiadającą właściwości różnych typów. Zobaczyliśmy również, że możliwe jest sprawdzenie poprawności modelu przekazanego do akcji. Na koniec omówiliśmy, jak rozszerzyć ten model na dane o zakresach [Session] i [Application].
Teraz zajmiemy się końcowym etapem przetwarzania zapytania [1]: utworzeniem widoku [6] i jego modelu [5]. Oba te elementy są generowane przez akcję [4].
























































