Skip to content

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ć:

1
2
3
GET /First/Action01?nom=someone HTTP/1.1
Host: localhost:55483
...
  • 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:

1
2
3
4
5
HTTP/1.1 500 Internal Server Error
...
Content-Type: text/html; charset=utf-8
...
Content-Length: 12438
  • 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:

Image

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].