Skip to content

5. Widok i jego szablon

5.1. Introduction

Wróćmy do architektury aplikacji ASP.NET MVC:

W poprzednim rozdziale przeanalizowaliśmy, w jaki sposób ASP.NET MVC przedstawiał informacje z zapytania [1] do akcji [2a] w postaci szablonu, który mógł zawierać ograniczenia walidacyjne. Szablon ten był przekazywany jako dane wejściowe do akcji i nazwaliśmy go szablonem akcji. Teraz skupimy się na najczęstszym wyniku akcji, czyli typie [ViewResult], który odpowiada widokowi V [3] wraz z jego modelem M [2c]. Model ten nazwiemy modelem widoku V – nie należy go mylić z modelem akcji, który właśnie omówiliśmy. Jeden stanowi dane wejściowe akcji, drugi – dane wyjściowe.

Zacznijmy od utworzenia nowego projektu o nazwie [Exemple-03] [1], nadal w ramach tego samego rozwiązania, opartego na podstawowym typie ASP.NET MVC:

Utwórzmy kontroler o nazwie [First] [2]. Kod wygenerowany dla tego kontrolera wygląda następująco:


using System.Web.Mvc;

namespace Exemple_03.Controllers
{
  public class FirstController : Controller
  {
    public ActionResult Index()
    {
      return View();
    }

  }
}
  • wiersze 7–10: utworzono akcję o nazwie [Index]. Typ wyniku metody [Index] odpowiada typowi klasy [ActionResult], od której wywodzi się większość możliwych wyników akcji;
  • wiersz 9: metoda [View] klasy [Controller] (wiersz 5) zwraca typ [ViewResult], który wywodzi się z [ActionResult]. Metoda ta dopuszcza wiele przeciążenia. Przyjrzymy się kilku z nich. Najważniejsze z nich to:
 
  • pierwszym parametrem jest nazwa widoku. Jeśli nie zostanie podana, używany jest widok o tej samej nazwie co akcja generująca [ViewResult], który będzie wyszukiwany w folderze [/Views/{controller}], gdzie {controller} to nazwa kontrolera;
  • drugi to szablon widoku. Jeśli go nie ma, widok nie ma szablonu.

Poniższa metoda [Index]:


public ActionResult Index()
    {
      return View();
}

żąda wyświetlenia widoku [/Views/First/Index.cshtml] . Nie przekazuje mu żadnego szablonu. Utwórzmy plik [1] w folderze [/Views/First]:

a następnie utwórzmy w nim widok [Index] [2]:

Jako nazwę widoku podajemy [3]. Widok ten zostaje utworzony jako [4]. Wygenerowany kod wygląda następująco:


@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        
    </div>
</body>
</html>

Mamy tu do czynienia z klasycznym kodem HTML, z wyjątkiem wierszy 1–3, które są kodem C#. Program zarządzający widokami nazywany jest silnikiem widoków. Zajmuje się on przetwarzaniem wszystkiego, co nie jest kodem HTML, na kod HTML. Ostatecznie to właśnie ten kod zostanie wysłany do klienta. Silnik widoków nosi tutaj nazwę [Razor]. Umożliwia on wstawianie kodu C# do widoku. [Razor] zinterpretuje ten kod C# i wygeneruje na jego podstawie kod HTML. Oto kilka podstawowych zasad dotyczących wstawiania kodu C# do widoku:

  • przejście z HTML do C# następuje po napotkaniu znaku @ (wiersz 1). Jeśli znak ten rozpoczyna blok kodu, należy umieścić nawiasy klamrowe (wiersze 1 i 3). Jeśli rozpoczyna zmienną, której wartość chcemy pobrać, wystarczy wpisać @zmienna;
  • przejście z języka C# do formatu HTML następuje po napotkaniu znaku < (wiersz 5). Czasami konieczne jest wymuszenie tej zmiany, zwłaszcza gdy na stronie umieszczamy tekst zwykły bez tagu HTML. W takim przypadku do umieszczenia tekstu używamy tagu <text>: <text>tutaj tekst zwykły</text>.

Wiersz 2 powyżej wskazuje, że widok [Index] nie ma strony wzorcowej.

Zmodyfikujmy widok w następujący sposób:


@{
  Layout = null;
  string vue = "Index";
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Index</title>
</head>
<body>
  <div>
    <h3>Vue @vue</h3>
  </div>
</body>
</html>
  • wiersz 3: definiuje zmienną C#;
  • wiersz 15: wyświetla wartość tej zmiennej.

Teraz wywołajmy widoki URL i [/First/Index]:

 

Otrzymany kod HTML wygląda następująco:

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Index</title>
</head>
<body>
  <div>
    <h3>Vue Index</h3>
  </div>
</body>
</html>

Jest to czysty dokument HTML. Cały kod C# zniknął.

5.2. Użyj [ViewBag], aby przekazać informacje do widoku

Tworzymy nową akcję o nazwie [Action01] powiązaną z widokiem [Action01.cshtml]:

Akcja [Action01] wygląda następująco:


    // Akcja01
    public ViewResult Action01()
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View();
}
  • wiersz 4: wykorzystuje się właściwość [ViewBag] kontrolera. Jest to obiekt dynamiczny, do którego można dodawać właściwości, tak jak zrobiono to w wierszu 4. Cechą charakterystyczną tego obiektu jest to, że jest on również dostępny dla widoku. Jest to zatem sposób na przekazanie mu informacji;
  • wiersz 5: wywoływany jest domyślny widok akcji. Jest to widok [/First/Action01.cshtml]. Nie przekazano mu żadnego szablonu.

Widok [Action01.cshtml] wygląda następująco:


@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action01</title>
</head>
<body>
  <div>
    <h4>@ViewBag.info</h4>
  </div>
</body>
</html>
  • wiersz 14: wyświetlana jest właściwość [ViewBag.info].

Przetestujmy to. Żądamy wyświetlenia URL i [/First/Action01]:

 

5.3. Wykorzystanie silnie typowanego szablonu do przekazywania informacji do widoku

Wadą poprzedniej metody jest to, że nie pozwala ona na wykrycie błędów przed wykonaniem. Jeśli więc widok [Action01.cshtml] wykorzystuje kod


<h4>@ViewBag.Info</h4>

, pojawi się błąd, ponieważ właściwość [Info] nie istnieje. Ta utworzona przez akcję [Action01] nosi nazwę [info]. Aby uniknąć tej niedogodności, można zatem zastosować model silnie typowany.

W jednym z omówionych wcześniej przykładów akcja wyglądała następująco:


    // 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);
}

Akcja [Action10] przekazywała klientowi sześć informacji (adres e-mail, dzień, informacja 1, informacja 2, informacja 3, błędy) w postaci ciągu znaków. Przekażemy te informacje w modelu widoku [ViewModel01]. Ponieważ model ten przejmuje informacje z [ActionModel03], utworzymy go jako pochodną tej klasy.

Zaczynamy od skopiowania projektu [ActionModel03] z projektu [Exemple-02] do bieżącego projektu [Exemple-03]:

i zmieniamy jego przestrzeń nazw, aby przejąć tę z projektu [Exemple-03]:


using System.ComponentModel.DataAnnotations;
namespace Exemple_03.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 2: nowa przestrzeń nazw;

Następnie tworzymy klasę [ViewModel01]:

Kod klasy [ViewModel01] wygląda następująco:


namespace Exemple_03.Models
{
  public class ViewModel01 : ActionModel03
  {
    public string Erreurs { get; set; }
  }
}
  • wiersz 3: klasa dziedziczy po [ActionModel03], a tym samym po właściwościach [Email, Jour, Info1, Info2, Info3];
  • wiersz 5: dodajemy do niej właściwość [Erreurs].

Teraz tworzymy akcję [Action02], która:

  • przyjmuje jako dane wejściowe szablon akcji [ActionModel03];
  • i zwraca jako wynik model widoku [ViewModel01].

Jej kod wygląda następująco:


    // Akcja 02
    public ViewResult Action02(ActionModel03 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      return View(new ViewModel01(){Email=modèle.Email, Jour=modèle.Jour, Info1=modèle.Info1, Info2=modèle.Info2, Info3=modèle.Info3, Erreurs=erreurs});
}
  • wiersz 1: [Action02] otrzymuje szablon akcji [ActionModel03]. Zwraca wynik typu [ViewResult];
  • wiersz 4: błędy związane z wzorcem akcji [ActionModel03] są agregowane w ciągu znaków [erreurs]. Metoda [getErrorMessagesFor] została opisana na stronie 65 i została dołączona do kontrolera [First] w nowym projekcie;
  • wiersz 5: metoda [View] jest wywoływana z jednym parametrem. Jest to model widoku. Widok ten nie został określony. Zostanie zatem użyty domyślny widok [/Views/First/Action02]. Model widoku [ViewModel01] jest instancjonowany i inicjowany przy użyciu pięciu informacji z modelu akcji [ActionModel03] oraz informacji [erreurs] utworzonej w wierszu 4.

Teraz tworzymy widok [/First/Action02.cshtml]:

Jego kod wygląda następująco:


@model Exemple_03.Models.ViewModel01
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action02</title>
</head>
<body>
  <h3>Informations du modèle de vue</h3>
  <ul>
    <li>Email : @Model.Email</li>
    <li>Jour : @Model.Jour</li>
    <li>Info1 : @Model.Info1</li>
    <li>Info2 : @Model.Info2</li>
    <li>Info3 : @Model.Info3</li>
    <li>Erreurs : @Model.Erreurs</li>
  </ul>
</body>
</html>
  • nowość znajduje się w wierszu 1. Notacja [@model] określa typ szablonu widoku. Szablon ten jest następnie odwołany przez notację [@Model] (wiersze 16–21);
  • wiersze 15–22: informacje o szablonie są wyświetlane na liście.

Przyjrzyjmy się kilku przykładom wykonania akcji [Action02].

Najpierw bez parametrów:

 

następnie z nieprawidłowymi parametrami:

a następnie z poprawnymi parametrami:

W tym przykładzie szablon widoku [ViewModel01] przejmuje informacje z szablonu akcji [ActionModel03]. Tak się często zdarza. Można wtedy użyć jednego szablonu, który będzie służył zarówno jako szablon akcji, jak i widoku. Tworzymy nowy szablon [ActionModel04]:

  

który będzie wyglądał następująco:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_03.Models
{
  [Bind(Exclude="Erreurs")]
  public class ActionModel04
  {
    // ---------------------- Działanie --------------------------------
    [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; }

    // ---------------------- widok --------------------------------
    public string Erreurs { get; set; }
  }
}
  • wiersze 8–28: model akcji wraz z ograniczeniami integralności. Pola te będą również częścią widoku;
  • wiersz 31: właściwość charakterystyczna dla modelu widoku. Została ona wykluczona z modelu akcji poprzez adnotację w wierszu 5.

Tworzymy następującą nową akcję [Action03]:


    // Akcja03
    public ViewResult Action03(ActionModel04 modèle)
    {
      modèle.Erreurs = getErrorMessagesFor(ModelState);
      return View(modèle);
}
  • wiersz 2: [Action03] otrzymuje model akcji typu [ActionModel04];
  • wiersz 5: i zwraca ten sam szablon jako szablon widoku;
  • wiersz 4: uzupełniony o informację [Erreurs];

Pozostaje nam tylko utworzyć widok [/First/Action03.cshtml]:

  • w [1]: kliknij prawym przyciskiem myszy w kodzie [Action03], a następnie wybierz [Ajouter une vue];
  • w [2]: nazwa widoku proponowana domyślnie;
  • w [3]: należy wskazać, że tworzymy widok silnie typowany;
  • w [4]: z listy rozwijanej należy wybrać odpowiednią klasę, w tym przypadku klasę [ActionModel04];
  • w polu [5]: utworzony widok.

Przypisujemy widokowi [Action03] ten sam kod, co widokowi [Action02]. Zmieniają się jedynie szablon widoku (wiersz 1) i tytuł strony (wiersz 11):


@model Exemple_03.Models.ActionModel04
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action03</title>
</head>
<body>
  <h3>Informations du modèle de vue</h3>
  <ul>
    <li>Email : @Model.Email</li>
    <li>Jour : @Model.Jour</li>
    <li>Info1 : @Model.Info1</li>
    <li>Info2 : @Model.Info2</li>
    <li>Info3 : @Model.Info3</li>
    <li>Erreurs : @Model.Erreurs</li>
  </ul>
</body>
</html>

Teraz wywołajmy akcję [Action03] bez parametrów:

 

Wyniki są takie same jak poprzednio. Często stosuje się ten sam szablon zarówno dla akcji, jak i dla widoku, ponieważ szablon widoku często przejmuje informacje z szablonu akcji. Wykorzystuje się wówczas szerszy szablon, który może być używany zarówno przez akcję, jak i przez generowany przez nią widok. Należy zadbać o wykluczenie z powiązania danych informacji, które nie należą do modelu akcji. W przeciwnym razie dobrze poinformowany użytkownik mógłby zainicjować części modelu widoku bez naszej wiedzy.

5.4. [Razor] – pierwsze kroki

Przedstawimy teraz kilka elementów widoków [Razor], głównie instrukcje foreach i if.

Załóżmy, że chcemy wyświetlić listę osób w tabeli HTML. Szablon widoku mógłby wyglądać następująco: [ViewModel02]:


namespace Exemple_03.Models
{
  public class ViewModel02
  {
    public Personne[] Personnes { get; set; }
    public ViewModel02()
    {
      Personnes = new Personne[] { new Personne { Nom = "Pierre", Age = 44 }, new Personne { Nom = "Pauline", Age = 12 } };
    }
  }

  public class Personne
  {
    public string Nom { get; set; }
    public int Age { get; set; }
  }
}
  • widokem modelu jest klasa [ViewModel02], wiersze 3–10;
  • wiersz 5: model posiada tablicę osób typu [Personne] zdefiniowaną w wierszach 12–16;
  • wiersze 6–10: konstruktor modelu inicjuje właściwość [Personnes] z wiersza 5 za pomocą tablicy zawierającej dwie osoby.

Akcja generująca ten model na wyjściu będzie wyglądała następująco: [Action04]:


    // Akcja04
    public ViewResult Action04()
    {
      return View(new ViewModel02());
}
  • wiersz 2: akcja nie ma szablonu wejściowego;
  • wiersz 4: przechodzi do swojego widoku domyślnego, czyli instancji szablonu [ViewModel02], który właśnie zdefiniowaliśmy.

Widok [Action04.cshtml] wyświetli model [ViewModel02]:

Kod widoku [Action04.cshtml] jest następujący:


@model Exemple_03.Models.ViewModel02
@using Exemple_03.Models

@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action04</title>
</head>
<body>
  <table border="1">
    <thead>
      <tr>
        <th>Nom</th>
        <th>Age</th>
      </tr>
    </thead>
    <tbody>
      @foreach (Personne p in Model.Personnes)
      {
        <tr>
          <td>@p.Nom</td>
          <td>@p.Age</td>
        </tr>
      }
    </tbody>
  </table>
</body>
</html>
  • wiersz 1: szablon widoku;
  • wiersz 2: import przestrzeni nazw klasy [Personne] użytej w wierszu 24;
  • wiersze 16–32: tablica HTML, która wyświetla osoby z modelu;
  • wiersz 24: początek kodu C# jest oznaczony znakiem @. Instrukcja [foreach] będzie przechodzić cyklicznie przez wszystkie osoby z modelu;
  • wiersze 26–27: znak < kończy kod C# i rozpoczyna kod HTML. Następnie ponownie pojawia się znak @, aby przejść do kodu C# i zapisać imię osoby. Potem ponownie pojawia się znak <, który powoduje przejście do trybu HTML;
  • wiersz 28: zapisywany jest wiek osoby.

Wykonanie akcji [Action04] daje następujący wynik:

 

Inne elementy widoku mogą być zasilane z kolekcji: listy, rozwijane lub nie, przyciski opcji, pola wyboru. Rozważmy następujący nowy przykład, który wyświetla listę rozwijaną.

Szablon [ViewModel05] będzie wyglądał następująco:


namespace Exemple_03.Models
{
  public class ViewModel05
  {
    public Personne2[] Personnes { get; set; }
    public int SelectedId { get; set; }

    public ViewModel05()
    {
      Personnes = new Personne2[] { 
        new Personne2 { Id = 1, Prénom = "Pierre", Nom = "Martino" }, 
        new Personne2 { Id = 2, Prénom = "Pauline", Nom = "Pereiro" }, 
        new Personne2 { Id = 3, Prénom = "Jacques", Nom = "Alfonso" } };
      SelectedId = 2;
    }
  }

  public class Personne2
  {
    public int Id { get; set; }
    public string Nom { get; set; }
    public string Prénom { get; set; }
  }
}
  • wiersz 18: klasa [Personne2] z trzema właściwościami;
  • wiersz 3: szablon widoku [ViewModel05];
  • wiersz 5: lista osób do wyświetlenia w rozwijanej liście w postaci [Prénom Nom];
  • wiersz 6: [Id] osoby do wyboru z listy rozwijanej;
  • wiersze 8–16: konstruktor, który tworzy tablicę zawierającą trzy osoby (wiersze 10–13) i ustala identyfikator [Id] osoby, która ma być zaznaczona.

Widok [Action05.cshtml] wyświetli ten szablon:

Jego kod wygląda następująco:


@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models

@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action05</title>
</head>
<body>
  <select>
    @foreach (Personne2 p in Model.Personnes)
    {
      string selected = "";
      if (p.Id == Model.SelectedId)
      {
        selected = "selected=\"selected\"";
      }
      <option value="@p.Id" @selected>@p.Prénom @p.Nom</option>
    }
  </select>
</body>
</html>

Cechy listy rozwijanej HTML zostały przedstawione w punkcie 2.5.2.6. Przypomnijmy je:

Combo
<select size="1" name="cmbValeurs">
<option value="1">opcja1</option>
<option selected="selected" value="2">opcja 2</option>
<option value="3">opcja 3</option>
</select>
 
tag HTML
<select size=".." name="..">
<option [selected="selected"] value=”v”>...</option>
...
</select>
wyświetla na liście teksty zawarte między znacznikami <option>...</option>
atrybuty
name="cmbValeurs": nazwa kontrolki.
size="1": liczba widocznych elementów listy. size="1" sprawia, że lista działa jak pole kombi.
selected="selected": jeśli to słowo kluczowe występuje dla elementu listy, element ten pojawia się jako zaznaczony na liście. W naszym przykładzie powyżej element listy choix2 pojawia się jako zaznaczony element pola kombi, gdy jest ono wyświetlane po raz pierwszy.
value=”v”: jeśli element zostanie wybrany przez użytkownika, to właśnie ta wartość [v] zostanie przesłana na serwer. W przypadku braku tego atrybutu na serwer zostanie przesłany wyświetlony i zaznaczony tekst.

Kod w wierszach 17–25 generuje tagi <option>, które umieszczane są w tagu <select> w wierszu 16.

  • wiersz 17: przeglądamy listę osób z modelu;
  • wiersz 20: sprawdzamy, czy bieżąca osoba jest tą, która ma zostać zaznaczona. Jeśli tak, przygotowujemy tekst selected="selected", który ma zostać wstawiony do tagu <option>;
  • wiersz 24: zapisywany jest tag <option>.

Wywołajmy akcję [Action05]:

  • w [1,2] osoby są wyświetlane w postaci [Prénom Nom];
  • W przypadku kodu [1,2] wybraną osobą jest ta, dla której wartość [Id] wynosi 2.

Przyjrzyjmy się teraz kodowi źródłowemu HTML powyższej strony:


<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action05</title>
</head>
<body>
  <select>
      <option value="1" >Pierre Martino</option>
      <option value="2" selected=&quot;selected&quot;>Pauline Pereiro</option>
      <option value="3" >Jacques Alfonso</option>
  </select>
</body>
</html>
  • wiersze 10–12: trzy tagi <option> wygenerowane przez kod [Razor];
  • wiersz 11: rzeczywiście wybrano osobę o identyfikatorze [Id]=2.

Te dwa powyższe przykłady wystarczą nam. Podczas pisania widoku [Razor] należy oprzeć się pokusie umieszczania w nim logiki. Kod C# pozwalałby nam na to. Jednak w modelu MVC logika musi znajdować się w akcji lub w niższych warstwach [Metier, DAO], a nie w widoku. Nawet przestrzegając modelu MVC, w widoku może znaleźć się dużo logiki służącej do obliczania wartości pośrednich. Może to oznaczać, że zastosowany model nie jest wystarczająco szczegółowy. Model ten powinien zawierać wartości końcowe, których potrzebuje widok, aby nie musiał ich samodzielnie obliczać. Dobry widok to taki, w którym logika jest ograniczona do minimum, a struktura widoku HTML pozostaje przejrzysta. Jeśli wstawi się zbyt dużo kodu C#, struktura HTML może stać się nieczytelna.

W powyższym przykładzie lista rozwijana mogłaby być używana przez użytkownika, a my chcielibyśmy wtedy wiedzieć, którą osobę wybrał. W tym celu potrzebujemy formularza.

5.5. Formularz – pierwsze kroki

Formularz wyświetlany użytkownikowi będzie wyglądał następująco:

 

Szablonem widoku będzie szablon [ViewModel05], z którego korzystaliśmy już wcześniej. Akcja wyświetlająca ten widok będzie wyglądała następująco:


    // Akcja06-GET
    [HttpGet]
    public ViewResult Action06()
    {
      return View("Action06Get",new ViewModel05());
}
  • wiersz 2: akcja może zostać wywołana wyłącznie za pomocą polecenia HTTP GET;
  • wiersz 5: widok [/First/Action06Get.cshtml] zostanie wyświetlony na podstawie instancji typu [ViewModel05].

Widok [/First/Action06Get.cshtml] będzie wyglądał następująco:


@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models

@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action06-GET</title>
</head>
<body>
  <h3>Action06 - GET</h3>
  <p>Choisissez une personne</p>
  <form method="post" action="/First/Action06">
    <select name="personneId">
      @foreach (Personne2 p in Model.Personnes)
      {
        string selected = "";
        if (p.Id == Model.SelectedId)
        {
          selected = "selected=\"selected\"";
        }
        <option value="@p.Id" @selected>@p.Prénom @p.Nom</option>
      }
    </select>
    <input name="valider" type="submit" value="Valider" />
  </form>
</body>
</html>

Główne zmiany są następujące:

  • wiersz 18: aby przeglądarka mogła przesłać informacje wprowadzone przez użytkownika, potrzebujemy formularza. Ogranicza go tag <form> w wierszach 18 i 31.

Tag <form> o wartości HTML został omówiony w punkcie 2.5.2.1. Przypomnijmy jego cechy:

formularz

<form method="post" action="FormulairePost.aspx">
tag HTML
<form name="..." method="..." action="...">...</form>
atrybuty
name="frmexemple": nazwa formularza – opcjonalne
method="..." : metoda używana przez przeglądarkę do wysyłania do serwera WWW wartości zebranych w formularzu
action="..." : adres, na który zostaną wysłane wartości zebrane w formularzu.
Formularz internetowy jest otoczony tagami <form>...</form>. Formularz może mieć nazwę (name="xx"). Dotyczy to wszystkich elementów kontrolnych, które można znaleźć w formularzu. Celem formularza jest zebranie informacji podanych przez użytkownika za pomocą klawiatury lub myszy oraz wysłanie ich do adresu URL serwera internetowego. Którego? Tego, na który odwołuje się atrybut action="URL". Jeśli ten atrybut nie występuje, informacje zostaną wysłane do serwera dokumentu, w którym znajduje się formularz. Klient sieciowy może wykorzystać dwie różne metody o nazwach POST i GET do wysyłania danych na serwer WWW. Atrybut method="méthode", przy czym method jest równe GET lub POST, w tagu <form> wskazuje przeglądarce metodę, jaką należy zastosować do wysłania informacji zebranych w formularzu do serwera URL określonego przez atrybut action="URL". Gdy atrybut method nie jest określony, domyślnie stosowana jest metoda GET.
  • wiersz 18: widać, że wartości z formularza zostaną przesłane do URL [/First/Action06] za pomocą polecenia HTTP POST;
  • wiersz 30: formularz musi zawierać przycisk typu [submit]. To właśnie on uruchamia wysyłanie wprowadzonych wartości do elementu URL, określonego przez atrybut [action] w tagu <form>.

Co dokładnie prześle przeglądarka, gdy użytkownik kliknie przycisk [Valider]? Zostało to wyjaśnione w paragrafie 2.5.3.1. Przypomnijmy, co tam powiedziano:


element kontrolny HTML


wizualny


zwracana(-e) wartość(-i)

<input type="radio" value="Tak" name="R1"/>Tak
<input type="radio" name="R1" value="nie" checked="checked"/>Nie
R1=Tak
– wartość atrybutu value przycisku opcji zaznaczonego przez użytkownika.
<input type="checkbox" name="C1" value="jeden"/>1
<input type="checkbox" name="C2" value="dwa" checked="checked"/>2
<input type="checkbox" name="C3" value="trzy"/>3
C1=jeden
C2=dwa
– wartości atrybutów value pól zaznaczonych przez użytkownika
<input type="text" name="txtSaisie" size="20" value="kilka słów"/>
txtSaisie=programowanie+Web
- tekst wpisany przez użytkownika w polu wprowadzania danych. Spacje zostały zastąpione znakiem +
<input type="password" name="txtMdp" size="20" value="unMotDePasse"/>
txtMdp=tojesttajemnica
– tekst wpisany przez użytkownika w polu wprowadzania danych
<textarea rows="2" name="areaSaisie" cols="20">
wiersz1
wiersz 2
wiersz 3
</textarea>
areaSaisie=podstawy+programowania+%0D%0A
programowania+internetowego
– tekst wpisany przez użytkownika w polu wprowadzania danych. %OD%OA to znacznik końca linii. Spacje zostały zastąpione znakiem +
<select size="1" name="cmbValeurs">
<option value='1'>opcja1</option>
<option selected="selected" value='2'>opcja2</option>
<option value='3'>opcja3</option>
</select>
cmbWartości=3
- atrybut [value] elementu wybranego przez użytkownika
<select size="3" name="lst1">
<option selected="selected" value='1'>lista1</option>
<option value='2'>lista2</option>
<option value='3'>lista3</option>
<option value='4'>lista4</option>
<option value='5'>lista5</option>
</select>
lst1=3
- atrybut [value] elementu wybranego przez użytkownika
<select size="3" name="lst2" multiple="multiple">
<option selected="selected" value='1'>lista1</option>
<option value='2'>lista2</option>
<option selected="selected" value='3'>lista3</option>
<option value='4'>lista4</option>
<option value='5'>lista5</option>
</select>
lst2=1
lst2=3
- atrybuty [value] elementów wybranych przez użytkownika
<input type="submit" value="Wyślij" name="cmdRenvoyer"/>
 
cmdRenvoyer=Wyślij
- nazwa i atrybut value przycisku, który posłużył do wysłania danych formularza na serwer
<input type="hidden" name="secret" value="uneValeur"/>
 
secret=wartość
- atrybut value pola ukrytego

W naszym formularzu mamy dwa elementy, które mogą wysyłać wartość:


    <select name="personneId">
...
</select>

oraz


<input name="valider" type="submit" value="Valider" />

Jeśli użytkownik wybierze osobę nr 2, wartości zostaną przesłane w następującej postaci:

personneId=2&valider=Valider

Nazwy parametrów odpowiadają atrybutom [name] tagów, których dotyczy POST. Bez tego atrybutu tagi nie przekazują żadnej wartości. Tak więc w powyższym przykładzie można by pominąć atrybut name="valider" przycisku [submit]. Przesyłaną wartością jest atrybut [value] przycisku. W tym przypadku informacja ta nas nie interesuje. Czasami formularze zawierają kilka przycisków typu [submit]. W takim przypadku ważne jest, aby wiedzieć, który przycisk został kliknięty. Należy więc nadać atrybut [name] poszczególnym przyciskom.

Tag <select> składa się z ciągu tagów <option>:


    <select name="personneId">
        <option value="1" >Pierre Martino</option>
        <option value="2" selected=&quot;selected&quot;>Pauline Pereiro</option>
        <option value="3" >Jacques Alfonso</option>
</select>

Przesyłana jest wartość atrybutu [value] wybranej opcji. W przypadku braku tego atrybutu przesyłany jest tekst wyświetlany przez opcję, na przykład [Pierre Martino].

Ciąg znaków

personneId=2&valider=Valider

zostanie opublikowany w następującej opcji: URL [/First/Action06]:


    // Akcja06-POST
    [HttpPost]
    public ViewResult Action06(ActionModel06 modèle)
    {
      return View("Action06Post",modèle);
}

Być może pamiętamy, że mieliśmy już akcję o numerze [Action06]:


    // Akcja06-GET
    [HttpGet]
    public ViewResult Action06()
    {
      return View("Action06Get",new ViewModel05());
}

Możliwe jest istnienie dwóch akcji o tej samej nazwie, pod warunkiem, że nie przetwarzają one tych samych poleceń HTTP:

  • [Action06] z wiersza 3 obsługuje akcję POST (wiersz 2);
  • [Action06] z wiersza c obsługuje akcję GET (wiersz b).

Akcja [Action06], która obsługuje POST, otrzyma następujący ciąg parametrów:

personneId=2&valider=Valider

Potrzebujemy modelu akcji, aby zawrzeć te wartości. Będzie to następujący model [ActionModel06]:


using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
  public class ActionModel06
  {
    [Required(ErrorMessage = "Le paramètre [personneId] est requis")]
    public int PersonneId { get; set; }

    [Required(ErrorMessage = "Le paramètre [valider] est requis")]
    public string Valider { get; set; }
  }
}

Akcja [Action06] odbiera ten szablon i przekazuje go bez zmian do następującego widoku [Action06Post] (wiersz 5 akcji):


@model Exemple_03.Models.ActionModel06

@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action06Post</title>
</head>
<body>
  <h3>Action06 - POST</h3>
  Valeurs postées :
  <ul>
    <li>ID de la personne sélectionnée : @Model.PersonneId</li>
    <li>Commande utilisée : @Model.Valider</li>
  </ul>
</body>
</html>

Szablon jest wyświetlany w wierszach 18 i 19.

Spójrzmy na przykład:

W [1] wybieramy trzecią osobę z [Id], której wartość wynosi 3. W [2] wysyłamy formularz. W [3] widoczne są otrzymane wartości. W [4,5] widać, że wywołano tę samą procedurę URL – raz przez GET i [4], a drugie przez POST i [5]. Nie widać tego w URL.

W widoku wyświetlanym po POST można by chcieć wyświetlić nom i prénom wybranej osoby zamiast jej numeru. Należy zatem zmodyfikować widok POST i jego szablon.

Aby obsłużyć ten przypadek, tworzymy akcję [Action07]. Akcja ta będzie musiała wykorzystać sesję użytkownika do przechowywania w niej listy osób. Będziemy kierować się wzorcem omówionym w paragrafie 4.10, który pozwala na włączenie danych o zakresie [Application] i [Session] do wzorca akcji.

Szablonem sesji będzie następująca klasa [SessionModel]:


namespace Exemple_03.Models
{
  public class SessionModel
  {
    public Personne2[] Personnes { get; set; }
  }
}
  • wiersz 2: sesja zapamięta listę osób wyświetlanych w liście rozwijanej;

Musimy powiązać poprzedni typ [SessionModel] z binderem, który nazwiemy [SessionModelBinder]. Będzie on taki sam, jak ten opisany na stronie 82:

  

using System.Web.Mvc;

namespace Exemple_03.Infrastructure
{
  public class SessionModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // zwracamy dane z zakresu [Session]
      return controllerContext.HttpContext.Session["data"];
    }
  }
}

Powiązanie między szablonem [SessionModel] a jego szablonami binder i [SessionModelBinder] odbywa się w szablonie [Global.asax]:


public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      ...

      // modele powiązań
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
    }
    // Sesja
    public void Session_Start()
    {
      Session["data"] = new SessionModel();
    }
  }
  • wiersz 8: powiązanie modelu z jego binderem odbywa się w pliku [Application_Start];
  • wiersz 13: instancja typu [SessionModel] jest wprowadzana do sesji powiązanej z kluczem [data].

Po wykonaniu tych czynności akcja [Action07] wygląda następująco:


    // Akcja07-GET
    [HttpGet]
    public ViewResult Action07(SessionModel session)
    {
      ViewModel05 modèleVue = new ViewModel05();
      session.Personnes= modèleVue.Personnes;
      return View("Action07Get", modèleVue);
}
  • wiersz 3: akcja pobiera typ [SessionModel], a więc dane o zasięgu [Session] powiązane z kluczem [data];
  • wiersz 5: tworzymy szablon widoku;
  • wiersz 6: umieszczamy w sesji tablicę osób. Będzie ona potrzebna w następnym zapytaniu, dotyczącym POST. Protokół HTTP jest protokołem bezstanowym. Aby zachować dane między zapytaniami, należy użyć sesji. Sesja jest przypisana do konkretnego użytkownika i jest zarządzana przez serwer WWW;
  • wiersz 7: wyświetlany jest widok [Action07Get.cshtml]. Wygląda on następująco:

@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
...
<body>
  <h3>Action07 - GET</h3>
  <p>Choisissez une personne</p>
  <form method="post" action="/First/Action07">
....
  </form>
</body>
</html>

Jest on identyczny z omówionym już widokiem [Action06Get.cshtml]. Główna różnica dotyczy wiersza 7: widok URL, do którego zostaną przesłane wartości z formularza. Zostaną one przetworzone przez następującą akcję [Action07]:


    // Akcja07-POST
    [HttpPost]
    public ViewResult Action07(SessionModel session, ActionModel06 modèle)
    {
      Personne2 personne = session.Personnes.Where(p => p.Id == modèle.PersonneId).First<Personne2>();
      string strPersonne = string.Format("{0} {1}", personne.Prénom, personne.Nom);
      return View("Action07Post", (object)strPersonne);
}
  • wiersz 3: przesłane wartości są umieszczane w szablonie akcji [ActionModel06], który był już wcześniej używany (poniżej):

using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
  public class ActionModel06
  {
    [Required(ErrorMessage = "Le paramètre [personneId] est requis")]
    public int PersonneId { get; set; }

    [Required(ErrorMessage = "Le paramètre [valider] est requis")]
    public string Valider { get; set; }
  }
}
  • wiersz 3: pierwszym parametrem jest dany z zakresu [Session] powiązany z kluczem [data];
  • wiersz 5: zapytanie LINQ pozwala odzyskać osobę posiadającą klucz [Id], który został opublikowany;
  • wiersz 6: tworzymy ciąg znaków, który ma zostać wyświetlony przez widok [Action07Post] (wiersz 8);
  • wiersz 7: aby wywołać właściwą funkcję [View], należy zmienić typ [string] na [object].

Widok [Action07Post.cshtml] wygląda następująco:


@model string

@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action07-Post</title>
</head>
<body>
  <h3>Action07-POST</h3>
  Vous avez sélectionné [@Model].
</body>
</html>
  • wiersz 1: szablon ma typ [string];
  • wiersz 16: wyświetlany jest ciąg znaków.

Oto przykładowe wyniki działania:

5.6. Formularz – kompletny przykład

W punkcie 2.5.2.1 omówiliśmy następujący formularz HTML:

1
 

Przeanalizujemy akcję [Action08Get], która wyświetla (GET) ten formularz, oraz akcję [Action08Post], która przetwarza (POST) wartości wprowadzone przez użytkownika. Jest to klasyczny schemat.

Szablon widoku [1] przedstawiony powyżej będzie instancją klasy [ViewModel08]. Klasa ta będzie jednocześnie:

  • modelem widoku generowanego przez GET w ramach akcji [Action08Get];
  • szablonem akcji [Action08Post] dla żądania POST.

5.6.1. Szablon zakresu [Application]

Założymy, że elementy wyświetlane przez przyciski opcji, pola wyboru i różne listy są danymi z zakresu [Application]. Jest to częsty przypadek. Informacje te pochodzą z pliku konfiguracyjnego lub bazy danych, które są wykorzystywane podczas uruchamiania aplikacji w metodzie [Application_Start] klasy [Global.asax]. Metoda ta przebiega w następujący sposób:


    protected void Application_Start()
    {
....

      // modele wiązania
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
      ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());

      // dane zakresu [Application]
      Application["data"] = new ApplicationModel();
}
  • wiersz 7: typ [ApplicationModel], który wkrótce opiszemy, jest powiązany z łącznikiem danych [ApplicationModelBinder], który przedstawiliśmy już na stronie 82;
  • wiersz 10: instancja typu [ApplicationModel] jest zapisana w słowniku aplikacji, powiązana z kluczem [data].

Klasa [ApplicationModel] służy do enkapsulacji wszystkich danych z zakresu [Application]. W tym przypadku będzie ona enkapsulować dane, które ma wyświetlić formularz:


namespace Exemple_03.Models
{
  public class ApplicationModel
  {
    // kolekcje do wyświetlenia w formularzu
    public Item[] RadioButtonFieldItems { get; set; }
    public Item[] CheckBoxesFieldItems { get; set; }
    public Item[] DropDownListFieldItems { get; set; }
    public Item[] SimpleChoiceListFieldItems { get; set; }
    public Item[] MultipleChoiceListFieldItems { get; set; }

    // inicjalizacja pól i kolekcji
    public ApplicationModel()
    {
      RadioButtonFieldItems = new Item[]{
        new Item {Value="1",Label="oui"},
        new Item {Value="2", Label="non"}
      };
      CheckBoxesFieldItems = new Item[]{
        new Item {Value="1",Label="1"},
        new Item {Value="2", Label="2"},
        new Item {Value="3", Label="3"}
      };
      DropDownListFieldItems = new Item[]{
        new Item {Value="1",Label="choix1"},
        new Item {Value="2", Label="choix2"},
        new Item {Value="3", Label="choix3"}
      };
      SimpleChoiceListFieldItems = new Item[]{
        new Item {Value="1",Label="liste1"},
        new Item {Value="2", Label="liste2"},
        new Item {Value="3", Label="liste3"},
        new Item {Value="4", Label="liste4"},
        new Item {Value="5", Label="liste5"}
      };
      MultipleChoiceListFieldItems = new Item[]{
        new Item {Value="1",Label="liste1"},
        new Item {Value="2", Label="liste2"},
        new Item {Value="3", Label="liste3"},
        new Item {Value="4", Label="liste4"},
        new Item {Value="5", Label="liste5"}
      };
    }
    // element kolekcji
    public class Item
    {
      public string Label { get; set; }
      public string Value { get; set; }
    }

  }
}
  • wiersze 45–49: elementy różnych kolekcji formularza. [Label] to tekst wyświetlany przez element formularza, a [Value] to wartość przesyłana przez ten element po jego zaznaczeniu;
  • wiersz 6: kolekcja wyświetlana przez przycisk opcji;
  • wiersz 7: kolekcja wyświetlana przez pola wyboru;
  • wiersz 8: kolekcja wyświetlana przez listę rozwijaną;
  • wiersz 9: kolekcja wyświetlana przez listę z jednym wyborem;
  • wiersz 10: kolekcja wyświetlana przez listę z możliwością wielokrotnego wyboru;
  • wiersze 13–43: kolekcje te są inicjowane przez konstruktor bezparametrowy klasy.

Różne kolekcje będą zasilać następujący formularz:

5.6.2. Szablon akcji [Action08Get]

Powyższy formularz zostanie wyświetlony przez następującą akcję [Action08Get]:


    // Action08-GET
    [HttpGet]
    public ViewResult Action08Get(ApplicationModel application)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View("Formulaire", new ViewModel08(application));
}
  • wiersz 2: [Action08Get] odpowie wyłącznie na polecenie [GET];
  • wiersz 3: otrzymuje jako parametr szablon aplikacji, który właśnie opisaliśmy;
  • wiersz 5: inicjuje informację w kontenerze dynamicznym [ViewBag];
  • wiersz 6: wyświetla widok [/First/Formulaire.cshtml] wraz z modelem [ViewModel08]. Model ten będzie odpowiadał formularzowi przedstawionemu wcześniej. W tym celu przekazujemy do konstruktora model aplikacji, który definiuje elementy do wyświetlenia.

5.6.3. Szablon widoku [Formulaire]

Klasa [ViewModel08] będzie szablonem formularza. Klasa ta wygląda następująco:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;

namespace Exemple_03.Models
{
  public class ViewModel08
  {
    // pola wprowadzania danych
    public string RadioButtonField { get; set; }
    public string[] CheckBoxesField { get; set; }
    public string TextField { get; set; }
    public string PasswordField { get; set; }
    public string TextAreaField { get; set; }
    public string DropDownListField { get; set; }
    public string SimpleChoiceListField { get; set; }
    public string[] MultipleChoiceListField { get; set; }

    // kolekcje do wyświetlenia w formularzu
    public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
    public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
    public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
    public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
    public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }

    // konstruktory
    public ViewModel08()
    {
    }

    public ViewModel08(ApplicationModel application)
    {
      // inicjalizacja kolekcji
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // inicjalizacja pól
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}
  • w formularzu występują dwa rodzaje elementów: te, które są wyświetlane, oraz te, które służą do wprowadzania danych;
  • wiersze 20–24 definiują elementy do wyświetlenia. Są to różne kolekcje formularza. Znajdują się one w szablonie aplikacji (wiersze 34–38);
  • wiersze 10–17: definiują pola wprowadzania danych w formularzu;
  • wiersz 10: [RadioButtonField] pobierze wartość przesłaną przez kolejne wiersze formularza:

        <!-- przyciski opcji -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
<input type="radio" name="RadioButtonField" value="1" />oui              
<input type="radio" name="RadioButtonField" value="2" checked=&quot;checked&quot;/>non              
          </td>
</tr>

W wierszach 5 i 6 należy zwrócić uwagę, że atrybut [name] obu przycisków opcji jest nazwą właściwości, która zostanie zainicjowana. W przesłanych danych znajdziemy ciąg znaków w postaci:


param1=val1&RadioButtonField=2&param2=val2

jeśli użytkownik zaznaczył opcję o nazwie [non]. W rzeczywistości to atrybut [value] zaznaczonej opcji jest wysyłany.

  • wiersz 11: [CheckBoxesField] pobierze wartości przesłane w kolejnych wierszach formularza:

        <!-- pola wyboru -->
        <tr>
          <td>Cases à cocher</td>
          <td>
<input type="checkbox" name="CheckBoxesField" value="1" />1              
<input type="checkbox" name="CheckBoxesField" value="2" checked=&quot;checked&quot;/>2              
<input type="checkbox" name="CheckBoxesField" value="3" />3              
</td>

W wierszach 5 i 6 należy zwrócić uwagę, że atrybut [name] pól wyboru jest nazwą właściwości, która zostanie zainicjowana. W przesłanych danych znajdziemy ciąg znaków w postaci:


param1=val1&CheckBoxesField=2&CheckBoxesField=3&param2=val2

jeśli użytkownik zaznaczył pola wyboru o nazwach [2] i [3]. Przesyłany jest atrybut [value] z zaznaczonych opcji. Ponieważ może być wysyłanych wiele parametrów o tej samej nazwie, [CheckBoxesField] jest tablicą wartości, a nie pojedynczą wartością. Jeśli żadne pole nie jest zaznaczone, parametr [CheckBoxesField] nie pojawi się w wysyłanym ciągu znaków, a właściwość o tej samej nazwie w modelu nie zostanie zainicjowana. Może to być kłopotliwe, jak zobaczymy.

  • wiersz 12: [TextField] pobierze wartość przesłaną przez kolejne wiersze formularza:

          <!-- pól tekstowych jednowierszowych -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="quelques mots" size="30" />
            </td>
</tr>

W wierszu 5 atrybut [name] pola wprowadzania danych jest nazwą właściwości, która zostanie zainicjowana. W przesłanych danych znajdziemy ciąg znaków o postaci:


param1=val1&TextField=abcdef&param2=val2

jeśli użytkownik wpisał [abcdef] w polu wprowadzania danych.

  • wiersz 13: [PasswordField] pobierze wartość przesłaną w kolejnych wierszach formularza:

        <!-- pole wprowadzania hasła -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="secret" size="30" />
          </td>
</tr>

W wierszu 5 atrybut [name] pola wprowadzania danych jest nazwą właściwości, która zostanie zainicjowana. W przesłanych danych znajdzie się ciąg znaków w postaci:


param1=val1&PasswordField=abcdef&param2=val2

jeśli użytkownik wpisał [abcdef] w polu wprowadzania danych.

  • wiersz 14: [TextAreaField] pobierze wartość przesłaną w kolejnych wierszach formularza:

        <!-- pole tekstowe wielowierszowe -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">ligne1
ligne2</textarea>
          </td>
</tr>

W wierszu 5 atrybut [name] pola wprowadzania danych jest nazwą właściwości, która zostanie zainicjowana. W przesłanych danych znajdzie się ciąg znaków o postaci:


param1=val1&TextAreaField=abcdef%0D%OAhijk&param2=val2

jeśli użytkownik wpisał w polu wprowadzania danych [abcdef], a następnie znak końca linii i [ijk].

  • wiersz 15: [DropDownListField] pobierze wartość przesłaną w kolejnych wierszach formularza:

        <!-- lista rozwijana -->
        <tr>
          <td>Liste déroulante</td>
          <td>
            <select name="DropDownListField">
<option value="1" >choix1</option>
<option value="2" selected=&quot;selected&quot;>choix2</option>
<option value="3" >choix3</option>
            </select>
</tr>

W wierszu 5 atrybut [name] tagu <select> jest nazwą właściwości, która zostanie zainicjowana. W przesłanych danych znajdzie się ciąg znaków w postaci:


param1=val1&DropDownListField=1&param2=val2

jeśli użytkownik wybrał opcję [choix1]. Przesyłany jest atrybut [value] wybranej opcji.

  • wiersz 16: [SingleChoiceListField] pobierze wartość przesłaną w kolejnych wierszach formularza:

        <!-- lista z jednym wyborem -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
            <select name="SimpleChoiceListField" size="3">
<option value="1" >liste1</option>
<option value="2" >liste2</option>
<option value="3" selected=&quot;selected&quot;>liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>

            </select>
</tr>

W wierszu 5 atrybut [name] tagu <select> stanowi nazwę właściwości, która zostanie zainicjowana. To właśnie atrybut [size="3"] powoduje, że nie wyświetla się lista rozwijana. W przesłanych danych znajdziemy ciąg znaków o postaci:


param1=val1&SimpleChoiceListField=3&param2=val2

jeśli użytkownik wybrał opcję [liste3]. Wysyłany jest atrybut [value] wybranej opcji. Parametr [SingleChoiceListField] może nie występować w wysyłanym ciągu znaków, jeśli nie wybrano żadnego elementu.

  • wiersz 17: [MultipleChoiceListField] pobierze wartości przesłane przez kolejne wiersze formularza:

        <!-- lista wielokrotnego wyboru -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
            <select name="MultipleChoiceListField" size="3" multiple="multiple">
<option value="1" selected=&quot;selected&quot;>liste1</option>
<option value="2" >liste2</option>
<option value="3" selected=&quot;selected&quot;>liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>
            </select>
</tr>

Wiersz 5: atrybut [name] w tagu <select> to nazwa właściwości, która zostanie zainicjowana. To właśnie atrybut [size="3"] sprawia, że nie ma listy rozwijanej, a atrybut [multiple] umożliwia użytkownikowi zaznaczenie wielu elementów poprzez przytrzymanie klawisza [Ctrl]. W przesłanych danych znajdziemy ciąg znaków o postaci:


param1=val1&MultipleChoiceListField=1&MultipleChoiceListField=3&param2=val2

jeśli użytkownik wybrał opcje [liste1] i [liste3]. Przesyłany jest atrybut [value] wybranych opcji. Ponieważ może zostać przesłanych kilka parametrów o tej samej nazwie, [MultipleChoiceListField] jest tablicą wartości, a nie pojedynczą wartością. Jeśli żadne pole nie jest zaznaczone, parametr [MultipleChoiceListField] nie pojawi się w przesłanym ciągu znaków, a właściwość o tej samej nazwie w modelu nie zostanie zainicjowana.

Różne pola wprowadzania danych przedstawione powyżej otrzymają wartości przesłane przez formularz. Można je również zainicjować przed wysłaniem formularza. Tak właśnie zrobiono w tym przypadku:


      // inicjalizacja pól
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };

Gdyby wartości te zostały uzyskane po przesłaniu formularza o kodzie POST, oznaczałoby to, że użytkownik:

  • wiersz 2: zaznaczył opcję [non] przy przycisku opcji;
  • wiersz 3: zaznaczył opcję [2] w polach wyboru;
  • wiersz 4: wpisał [quelques mots] w polu wprowadzania danych;
  • wiersz 5: wpisał [secret] jako hasło;
  • wiersz 6: wpisałem [ligne1\nligne2] w polu wielowierszowym;
  • wiersz 7: wybrano opcję [choix2] z listy rozwijanej;
  • wiersz 8: wybrano opcję [liste3] z listy z jednym wyborem;
  • wiersz 9: wybrano opcje [liste1] i [liste3] z listy wielokrotnego wyboru;

Załóżmy, że nastąpiła operacja POST i chcemy odesłać formularz w stanie, w jakim został wypełniony. Tak właśnie postępuje się w szczególności w przypadku odsyłania użytkownikowi błędnego formularza. Formularz ten jest odsyłany w stanie, w jakim został wypełniony.

5.6.4. Widok [Formulaire]

Widok [/First/Formulaire.cshtml] wyświetla formularz:


@model Exemple_03.Models.ViewModel08
@using Exemple_03.Models
@{
  Layout = null;
}
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Formulaire</title>
</head>
<body>
  <form method="post" action="Action08Post">
    <h2>Formulaire ASP.NET MVC</h2>
    <h3>Affiché par : @ViewBag.info</h3>
    <table>
      <thead></thead>
      <tbody>
        <!-- przyciski opcji -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
            @foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
            {
              string strChecked = item.Value == @Model.RadioButtonField ? "checked=\"checked\"" : "";
              <input type="radio" name="RadioButtonField" value="@item.Value" @strChecked/>@item.Label
              <text/>
            }
          </td>
        </tr>
...
      </tbody>
    </table>
    <input type="submit" value="Valider" />
  </form>
</body>
</html>
  • wiersz 1: [ViewModel08] to szablon formularza;
  • wiersz 12: tag <form> formularza. Formularz ten zostanie przesłany metodą [POST] (atrybut method) do URL [/First/Action08Post] (atrybut action);
  • wiersz 33: przycisk typu [submit] służący do wysłania formularza;
  • wiersze 22–27: wyświetlają przyciski opcji:
  
  • wiersz 22: przechodzimy przez kolekcję wyświetlaną przez przycisk opcji;
  • wiersz 24: przycisk, który ma atrybut [value] o wartości właściwości [RadioButtonField], musi być zaznaczony. W tym celu musi posiadać atrybut [checked="checked"];
  • wiersz 25: generowanie tagu <input type="radio"> o wartości [@item.Value] i etykiecie [@item.Label];
  • wiersz 26: tag <text/> nie jest rozpoznawanym tagiem HTML. Znajduje się on tam dla [Razor]. Po napotkaniu tego tagu [Razor] wygeneruje znak końca linii. Nie ma to wpływu na wyświetlany formularz, ale ma wpływ na wygenerowany kod HTML. Tagi <input type="radio"> znajdują się wówczas w dwóch różnych wierszach zamiast w jednym. Zwiększa to czytelność kodu, gdy w przeglądarce wyświetla się kod źródłowy wyświetlanej strony;

Przeanalizujmy pozostałe elementy widoku:


        <!-- pola wyboru -->
        <tr>
          <td>Cases à cocher</td>
          <td>
            @{
              foreach (ApplicationModel.Item item in @Model.CheckBoxesFieldItems)
              {
                string strChecked = @Model.CheckBoxesField.Contains(item.Value) ? "checked=\"checked\"" : "";
              <input type="checkbox" name="CheckBoxesField" value="@item.Value" @strChecked/>@item.Label
              <text/>
              }
            }
</td>
  • wiersz 6: przeglądamy kolekcję wyświetlaną za pomocą pól wyboru;
  • wiersz 8: należy zaznaczyć pole, którego atrybut [value] ma jedną z wartości właściwości [CheckBoxesField]. W tym celu musi ono posiadać atrybut [checked="checked"]. Używamy wyrażenia LINQ, które pozwala sprawdzić, czy dana wartość znajduje się w tablicy;
  • wiersz 25: generowanie tagu <input type="checkbox"> o wartości [@item.Value] i etykiecie [@item.Label];

<!-- pól tekstowych jednowierszowych -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="@Model.TextField" size="30" />
            </td>
          </tr>
        <!-- pole do wprowadzania hasła -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="@Model.PasswordField" size="30" />
          </td>
        </tr>
        <!-- pole tekstowe wielowierszowe -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">@Model.TextAreaField</textarea>
          </td>
        </tr>
  • wiersze 5, 12: przypisuje się atrybutowi [value] tagu wartość z szablonu;
  • wiersz 19: to samo, ale z inną składnią.

        <!-- lista rozwijana -->
        <tr>
          <td>Liste déroulante</td>
          <td>
            <select name="DropDownListField">
              @{
                foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
                {
                  string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>
  • wiersz 7: przeglądamy kolekcję wyświetlaną przez listę rozwijaną;
  • wiersz 9: należy wówczas wybrać opcję, której atrybut [value] ma wartość właściwości [DropDownListField]. W tym celu musi ona posiadać atrybut [selected="selected"];
  • wiersz 25: generowanie tagu <option value="valeur">libellé</option> o wartości [@item.Value] i etykiecie [@item.Label];

        <!-- lista z jednym wyborem -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
            <select name="SimpleChoiceListField" size="3">
              @{
                foreach (ApplicationModel.Item item in @Model.SimpleChoiceListFieldItems)
                {
                  string strChecked = item.Value == @Model.SimpleChoiceListField ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>

Wyjaśnienie jest takie samo jak w przypadku listy rozwijanej.


        <!-- lista z możliwością wyboru wielu opcji -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
            <select name="MultipleChoiceListField" size="3" multiple="multiple">
              @{
                foreach (ApplicationModel.Item item in @Model.MultipleChoiceListFieldItems)
                {
                  string strChecked = @Model.MultipleChoiceListField.Contains(item.Value) ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>
  • wiersz 7: przeglądamy kolekcję wyświetlaną na liście;
  • wiersz 9: należy wybrać opcję, której atrybut [value] ma jedną z wartości właściwości [MultipleChoiceListField]. W tym celu musi ona posiadać atrybut [selected="selected"]. Stosuje się wyrażenie LINQ, które pozwala sprawdzić, czy dana wartość znajduje się w tablicy;
  • wiersz 10: generowanie tagu libellé/option z wartością [@item.Value] i etykietą [@item.Label];

5.6.5. Przetwarzanie pola POST z formularza

Zauważyliśmy, że formularz zostanie przesłany do akcji [Action08Post]:


  <form method="post" action="Action08Post">

Akcja [Action08Post] wygląda następująco:


    // Action08-POST
    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      return View("Formulaire", modèle);
}
  • wiersz 3: szablon aplikacji jest podany jako parametr wraz z przesłanymi wartościami. Są one dostępne w typie [FormCollection]. Wartość przesłanego parametru [RadioButtonField] uzyskuje się za pomocą wyrażenia posted[" RadioButtonField"]. W ten sposób otrzymujemy ciąg znaków lub wskaźnik null. Jeśli wpiszemy posted[" CheckBoxesField"], otrzymamy tablicę ciągów znaków lub wskaźnik null;
  • dlaczego więc nie wpisać:

public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)

Są dwa powody:

  • po pierwsze, framework utworzy instancję modelu [ViewModel08] za pomocą konstruktora bez parametrów, co spowoduje, że kolekcje modelu nie zostaną zainicjowane;
  • drugi polega na tym, że chcemy kontrolować, co trafia do modelu. Wiemy, że istnieją cztery możliwe źródła danych dla tego modelu: parametry obiektu GET, obiektu POST, używanej trasy oraz pliku uploadé. W tym przypadku chcemy zainicjować model wyłącznie wartościami przesłanymi.
  • wiersz 6: tworzymy instancję modelu przy użyciu odpowiedniego konstruktora;
  • wiersz 7: inicjujemy go wartościami przesłanymi w POST. Po tej operacji model odpowiada danym wprowadzonym przez użytkownika;
  • wiersz 8: ponownie wyświetlamy formularz. Użytkownik zobaczy go w stanie, w jakim został wypełniony.

Spójrzmy na przykład:

W [2] wynik [POST] dokładnie odzwierciedla dane wprowadzone w [1].

5.6.6. Obsługa anomalii w POST

Wspomnieliśmy, że jeśli żadna wartość nie została zaznaczona ani wybrana w polach [CheckBoxesField, SimpleChoiceListField, MultipleChoiceListField], to odpowiadające im parametry nie znalazły się w wysłanym ciągu znaków, a zatem właściwości o tych samych nazwach w modelu nie zostały zainicjowane.

Rozważmy następujący przykład:

  • w polu [1] nie zaznaczono żadnego pola wyboru;
  • w [2], [POST] zwraca zaznaczone pole wyboru.

Wyjaśnienie jest następujące:

  • ponieważ żadne pole nie jest zaznaczone, parametr [CheckBoxesField] nie znajduje się wśród wysyłanych wartości;
  • akcja [Action08Post] przebiega w następujący sposób:

    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = ...
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      return View("Formulaire", modèle);
}
  • wiersz 5: tworzony jest egzemplarz szablonu formularza. Jednak używany konstruktor przypisuje tablicę ["2"] do właściwości [CheckBoxesField];
  • wiersz 6: wartości przesłane są zapisywane w szablonie. Ponieważ parametr [CheckBoxesField] nie znajduje się wśród przesłanych wartości, właściwość o tej samej nazwie nie zostaje przypisana. Zachowuje zatem swoją wartość ["2"], co powoduje, że podczas wyświetlania pole nr 2 jest zaznaczone, podczas gdy nie powinno być.

Problem ten można rozwiązać na różne sposoby. Zdecydowaliśmy się rozwiązać go w kodzie akcji [Action08Post]:


// Akcja08-POST
    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      // przetwarzanie wartości nieprzesłanych
      if (posted["CheckBoxesField"] == null)
      {
        modèle.CheckBoxesField = new string[] { };
      }
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // wyświetlanie formularza
      return View("Formulaire", modèle);
    }
  • wiersze 9–20: sprawdzamy, czy określone parametry zostały przesłane, czy nie. Jeśli nie, inicjujemy je wartością odpowiadającą braku wprowadzania danych przez użytkownika. Test nie został przeprowadzony dla listy rozwijanej, która zawsze ma zaznaczony element, co nie ma miejsca w przypadku pozostałych list.

Zachęcamy czytelników do przetestowania tej nowej wersji.

5.7. Wykorzystanie metod specjalizujących się w generowaniu formularzy

5.7.1. Nowy formularz

Tworzymy nowy formularz [Formulaire2.cshtml], który wygeneruje formularz identyczny z poprzednim:

Wróćmy do kodu użytego do wygenerowania listy rozwijanej w formularzu:


        <!-- lista rozwijana -->
        <tr>
          <td>Liste déroulante</td>
          <td>
            <select name="DropDownListField">
              @{
                foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
                {
                  string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>

Kod ten ma dwie wady:

  • najważniejszą z nich jest to, że ze względu na złożoność kodu tracimy z oczu charakter komponentu, w tym przypadku listy rozwijanej;
  • wiersz 5: jeśli popełnimy błąd w nazwie właściwości modelu, która ma być użyta jako atrybut [name], zauważymy to dopiero podczas wykonywania kodu.

ASP.NET MVC oferuje wyspecjalizowane metody o nazwie [HTML Helpers], które – jak sama nazwa wskazuje – mają na celu ułatwienie generowania HTML, zwłaszcza w przypadku formularzy. Dzięki tym klasom poprzednia lista rozwijana wygląda następująco:


        <!-- lista rozwijana -->
        <tr>
          <td>Liste déroulante</td>
          <td>@Html.DropDownListFor(m => m.DropDownListField,
           new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
          </td>
</tr>

Lista rozwijana jest generowana w wierszach 4–5. Kod jest znacznie mniej skomplikowany. Kod HTML wygenerowany dla listy rozwijanej wygląda następująco:


        <!-- lista rozwijana -->
        <tr>
          <td>Liste déroulante</td>
          <td><select id="DropDownListField" name="DropDownListField"><option value="1">choix1</option>
<option selected="selected" value="2">choix2</option>
<option value="3">choix3</option>
</select></td>
</tr>
  • wiersz 4: atrybut [name] jest poprawny;
  • wiersze 4–6: opcje zostały poprawnie wygenerowane, a wybrano właściwą opcję.

Wróćmy do kodu, który wygenerował te wiersze HTML:


@Html.DropDownListFor(m => m.DropDownListField, new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
  • pierwszym parametrem jest funkcja lambda (tak się nazywa), gdzie m oznacza model widoku, a m.DropDowListField jest właściwością tego modelu. Generator kodu HTML wykorzysta nazwę tej właściwości do wygenerowania atrybutów [id] i [name] elementu [select], który zostanie wygenerowany. W przypadku użycia nieistniejącej właściwości błąd pojawi się już na etapie kompilacji, a nie dopiero podczas wykonywania kodu. Stanowi to ulepszenie w stosunku do poprzedniego rozwiązania, w którym błędy nazewnictwa były wykrywane dopiero podczas wykonywania kodu;
  • drugi parametr służy do wskazania kolekcji elementów, które będą zasilać listę rozwijaną. Klasa [SelectList] pozwala na utworzenie tej kolekcji:
    • jej pierwszy parametr to dowolna kolekcja elementów. W tym przypadku mamy kolekcję typu [Item];
    • jej drugi parametr to właściwość elementów, która dostarczy wartość tagu <option>. W tym przypadku jest to właściwość [Value] klasy [Item];
    • trzecim parametrem jest właściwość elementów, która dostarczy etykietę tagu <option>. W tym przypadku jest to właściwość [Label] klasy [Item];
  • aby ustalić, która opcja powinna zostać wybrana (atrybut selected), framework postępuje tak samo jak my: porównuje wartość opcji z aktualną wartością właściwości [DropDownListField].

Przyjrzyjmy się teraz innym metodom, z których możemy skorzystać:

Przyciski opcji

Nowy kod wygląda następująco:


        <!-- przyciski opcji -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
            @{
    foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
    {
              @Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)@item.Label
              <text/>
    }
            }
          </td>
</tr>

Wygenerowany kod HTML wygląda następująco:


        <!-- przyciski opcji -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
<input id="RadioButtonField" name="RadioButtonField" type="radio" value="1" />oui              
<input checked="checked" id="RadioButtonField" name="RadioButtonField" type="radio" value="2" />non              
          </td>
</tr>

Zastosowana metoda to [Html.RadioButtonFor]:

@Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)
  • pierwszym parametrem jest właściwość modelu, która zostanie powiązana z przyciskiem opcji (atrybut [name]);
  • drugim parametrem jest wartość, która ma zostać przypisana do przycisku opcji (atrybut [value]).

Pola wyboru

Kod zmienia się w następujący sposób:


        <!-- pola wyboru -->
        <tr>
          <td>Cases à cocher</td>
          <td>
            @{
              @Html.CheckBoxFor(m=>m.CheckBoxField1) @Model.CheckBoxesFieldItems[0].Label
              @Html.CheckBoxFor(m=>m.CheckBoxField2) @Model.CheckBoxesFieldItems[1].Label
              @Html.CheckBoxFor(m=>m.CheckBoxField3) @Model.CheckBoxesFieldItems[2].Label
            }
</td>

Metodą używaną do generowania pól wyboru jest [Html.CheckBoxFor]:

Html.CheckBoxFor(m=>m.Propriété)

Parametr ten jest właściwością logiczną modelu, która zostanie powiązana z polem wyboru. Jeśli wartość wynosi [Propriété=true], pole zostanie zaznaczone. Jeśli wartość wynosi [Propriété=false], pole nie zostanie zaznaczone. W każdym przypadku atrybut [value] przyjmuje wartość true. Wygenerowany kod HTML jest następujący:


<input id="Propriété" name="Propriété" type="checkbox" value="true" />
<input name="Propriété" type="hidden" value="false" />
  • wiersz 1: pole wyboru z atrybutem [value="true"];
  • wiersz 2: ukryte pole (type=hidden) o tej samej nazwie [Propriété] co pole wyboru z atrybutem [value="false"]. Dlaczego występują dwa elementy o tej samej nazwie [input]? Istnieją dwa przypadki:
  • pole wyboru w wierszu 1 jest zaznaczone. Wówczas wysyłany ciąg parametrów to Właściwość=true&Właściwość=false (wiersze 1 i 2). Ponieważ właściwość [Propriété] oczekuje tylko jednej wartości, można by pomyśleć, że framework przypisuje wartość [true] do [Propriété]. Wystarczyłoby, gdyby framework wykonał operację logicznego „OR” między otrzymanymi wartościami, aby to osiągnąć;
  • pole wyboru w wierszu 1 nie jest zaznaczone. W związku z tym wysłany ciąg parametrów to Właściwość=false (tylko wiersz 2), a zatem właściwość [Propriété] otrzymuje wartość [false], co jest poprawne (pole wyboru nie zostało zaznaczone).

Pole wprowadzania danych jednowierszowe

Nowy kod wygląda następująco:


          <!-- pólko tekstowe jednowierszowe -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              @Html.TextBoxFor(m => m.TextField, new { size = "30" })
            </td>
</tr>

Wygenerowany kod HTML wygląda następująco:


          <!-- pólko tekstowe jednowierszowe -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input id="TextField" name="TextField" size="30" type="text" value="quelques mots" />
            </td>
</tr>

Zastosowano następującą metodę:


@Html.TextBoxFor(m => m.TextField, new { size = "30" })
  • pierwszy parametr określa właściwość modelu powiązanego z polem wprowadzania danych. Nazwa tej właściwości zostanie wykorzystana w atrybutach [name] i [id] wygenerowanego tagu <input>, a jej wartość zostanie przypisana do atrybutu [value];
  • drugi parametr to klasa anonimowa określająca niektóre atrybuty wygenerowanego tagu HTML, w tym przypadku atrybut [size].

Pole wprowadzania hasła

Nowy kod wygląda następująco:


        <!-- pole wprowadzania hasła -->
        <tr>
          <td>Mot de passe</td>
          <td>
            @Html.PasswordFor(m => m.PasswordField, new { size = "15" })
          </td>
</tr>

Wygenerowany kod HTML wygląda następująco:


        <!-- pole do wprowadzania hasła -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input id="PasswordField" name="PasswordField" size="15" type="password" />
          </td>
</tr>

Zastosowana metoda jest następująca:


@Html.PasswordFor(m => m.PasswordField, new { size = "15" })

Działanie jest analogiczne do działania metody [Html.TexBoxFor].

Pole wprowadzania danych wielowierszowe

Nowy kod brzmi następująco:


        <!-- pole do wprowadzania tekstu wielowierszowego -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            @Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
          </td>
</tr>

Wygenerowany kod HTML wygląda następująco:


        <!-- pole do wprowadzania tekstu wielowierszowego -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea cols="30" id="TextAreaField" name="TextAreaField" rows="5">
ligne1
ligne2</textarea>
          </td>
</tr>

Zastosowano następującą metodę:


@Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })

Działanie jest analogiczne do działania metody [Html.TexBoxFor].

Lista z jednym wyborem

Nowy kod jest następujący:


        <!-- lista z jednym wyborem -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
          @Html.DropDownListFor(m => m.SimpleChoiceListField, new SelectList(@Model.SimpleChoiceListFieldItems, "Value", "Label"), new { size = "3" })
</tr>

a wygenerowany kod HTML to:


        <!-- lista z jednym wyborem -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
          <select id="SimpleChoiceListField" name="SimpleChoiceListField" size="3">
<option value="1">liste1</option>
<option value="2">liste2</option>
<option selected="selected" value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</tr>

Omówiliśmy już metodę [Html.DropDownListFor]. Jedyną różnicą jest tutaj trzeci parametr, który służy do określenia atrybutu [size] innego niż 1. To właśnie ta cecha powoduje przejście z listy rozwijanej [size=1] na listę prostą.

Lista wielokrotnego wyboru

Nowy kod wygląda następująco:


        <!-- lista z możliwością wyboru wielu opcji -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
          @Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
</tr>

a wygenerowany kod HTML wygląda następująco:


        <!-- lista z możliwością wyboru wielu opcji -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
          <select id="MultipleChoiceListField" multiple="multiple" name="MultipleChoiceListField" size="5">
<option selected="selected" value="1">liste1</option>
<option value="2">liste2</option>
<option selected="selected" value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</tr>

Metoda


@Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })

działa tak samo jak metoda [Html.DropDownListFor], z tą różnicą, że generuje listę z możliwością wielokrotnego wyboru. Wybrane opcje to te, których wartości (atrybut value) znajdują się w tabeli [MultipleChoiceListField].

Tag <form> można również wygenerować za pomocą metody:


  @using (Html.BeginForm("Action09Post", "First"))
  {
...
  }

Wygenerowany kod HTML wygląda następująco:


<form action="/First/Action09Post" method="post">    
    ...
</form>

Metoda


Html.BeginForm("Action09Post", "First")

ma jako pierwszy parametr nazwę akcji, a jako drugi parametr nazwę kontrolera.

5.7.2. Akcje i model

Formularz zostanie wygenerowany przez następującą akcję [Action09Get]:


    // Action09-GET
    [HttpGet]
    public ViewResult Action09Get(ApplicationModel application)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View("Formulaire2", new ViewModel09(application));
}

Widok generowany w wierszu 6 to [Formulaire2] powiązany z następującym modelem [ViewModel09]:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;

namespace Exemple_03.Models
{
  public class ViewModel09
  {
    // pola wprowadzania danych
    public string RadioButtonField { get; set; }
    public bool CheckBoxField1 { get; set; }
    public bool CheckBoxField2 { get; set; }
    public bool CheckBoxField3 { get; set; }
    public string TextField { get; set; }
    public string PasswordField { get; set; }
    public string TextAreaField { get; set; }
    public string DropDownListField { get; set; }
    public string SimpleChoiceListField { get; set; }
    public string[] MultipleChoiceListField { get; set; }

    // kolekcje do wyświetlenia w formularzu
    public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
    public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
    public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
    public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
    public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }

    // konstruktory
    public ViewModel09()
    {
    }

    public ViewModel09(ApplicationModel application)
    {
      // inicjalizacja kolekcji
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // inicjalizacja pól
      RadioButtonField = "2";
      CheckBoxField2 = true;
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}

[ViewModel09] różni się od [ViewModel08] sposobem obsługi pól wyboru. Zamiast tabeli zawierającej trzy pola wyboru zastosowano trzy oddzielne pola wyboru (wiersze 11–13).

Formularz zostanie przetworzony przez następującą akcję [Action09Post]:


    // Action09-POST
    [HttpPost]
    public ViewResult Action09Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel09 modèle = new ViewModel09(application);
      TryUpdateModel(modèle, posted);
      // przetwarzanie wartości, które nie zostały przesłane
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // wyświetlanie formularza
      return View("Formulaire2", modèle);
}

Akcja [Action09Post] jest identyczna z akcją [Action08Post] z wyjątkiem dwóch punktów:

  • wiersz 18: zamiast widoku [Formulaire] używany jest widok [Formulaire2];
  • nie ma już obsługi pól wyboru, które nie zostały zaznaczone. Jest to teraz poprawnie obsługiwane przez metodę [Html.CheckBoxFor].

5.8. Generowanie formularza na podstawie metadanych modelu

Istnieją inne metody generowania formularza niż te opisane powyżej. Jedna z nich polega na przypisaniu do pola w modelu informacji, które pozwolą frameworkowi MVC określić, jaki tag wprowadzania danych ma wygenerować. Informacje te nazywane są metadanymi.

Rozważmy następujący model widoku [ViewModel10]:


using System;
using System.ComponentModel.DataAnnotations;
using System.Drawing;

namespace Exemple_03.Models
{
  public class ViewModel10
  {
    [Display(Name="Text")]
    [DataType(DataType.Text)]
    public string Text { get; set; }

    [Display(Name = "TextArea")]
    [DataType(DataType.MultilineText)]
    public string MultiLineText { get; set; }

    [Display(Name = "Number")]
    public int Number { get; set; }

    [Display(Name = "Decimal")]
    [UIHint("Decimal")]
    public double Decimal { get; set; }

    [Display(Name = "Tel")]
    [DataType(DataType.PhoneNumber)]
    public string Tel { get; set; }

    [Display(Name = "Date")]
    [DataType(DataType.Date)]
    public DateTime Date { get; set; }

    [Display(Name = "Time")]
    [DataType(DataType.Time)]
    public DateTime Time { get; set; }

    [Display(Name = "HiddenInput")]
    [UIHint("HiddenInput")]
    public string HiddenInput { get; set; }

    [Display(Name = "Boolean")]
    [UIHint("Boolean")]
    public bool Boolean { get; set; }

    [Display(Name = "Email")]
    [DataType(DataType.EmailAddress)]
    public string Email{ get; set; }

    [Display(Name = "Url")]
    [DataType(DataType.Url)]
    public string Url { get; set; }

    [Display(Name = "Password")]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [Display(Name = "Currency")]
    [DataType(DataType.Currency)]
    public double Currency { get; set; }

    [Display(Name = "CreditCard")]
    [DataType(DataType.CreditCard)]
    public string CreditCard { get; set; }

    // konstruktor
    public ViewModel10()
    {
      Text = "tra la la";
      MultiLineText = "ligne1\nligne2";
      Number = 4;
      Decimal = 10.2;
      Tel = "0617181920";
      Date = DateTime.Now;
      Time = DateTime.Now;
      HiddenInput = "caché";
      Boolean = true;
      Email = "x@y.z";
      Url = "http://istia.univ-angers.fr";
      Password = "mdp";
      Currency = 4.2;
      CreditCard = "0123456789012345";
    }
  }
}

Metadane składają się z tagów [Display, DataType, UIHint].

Ten szablon widoku zostanie wygenerowany przez następującą akcję: [Action10Get]:


    // Akcja10-GET
    [HttpGet]
    public ViewResult Action10Get()
    {
      return View(new ViewModel10());
}

W powyższym wierszu 5 domyślny widok akcji [/First/Action10Get.cshtml ] otrzymuje polecenie wyświetlenia szablonu widoku typu [ViewModel10]. Widok ten wygląda następująco:


@model Exemple_03.Models.ViewModel10

@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action10Get</title>
</head>
<body>
  <h3>Formulaire ASP.NET MVC - 2</h3>
  @using (Html.BeginForm("Action10Post", "First"))
  {
    <table>
      <thead>
        <tr>
          <th>LabelFor</th>
          <th>EditorFor</th>
          <th>DisplayFor</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.LabelFor(m => m.Text)</td>
          <td>@Html.EditorFor(m => m.Text)</td>
          <td>@Html.DisplayFor(m => m.Text)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.MultiLineText)</td>
          <td>@Html.EditorFor(m => m.MultiLineText)</td>
          <td>@Html.DisplayFor(m => m.MultiLineText)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Number)</td>
          <td>@Html.EditorFor(m => m.Number)</td>
          <td>@Html.DisplayFor(m => m.Number)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Decimal)</td>
          <td>@Html.EditorFor(m => m.Decimal)</td>
          <td>@Html.DisplayFor(m => m.Decimal)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Tel)</td>
          <td>@Html.EditorFor(m => m.Tel)</td>
          <td>@Html.DisplayFor(m => m.Tel)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Date)</td>
          <td>@Html.EditorFor(m => m.Date)</td>
          <td>@Html.DisplayFor(m => m.Date)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Time)</td>
          <td>@Html.EditorFor(m => m.Time)</td>
          <td>@Html.DisplayFor(m => m.Time)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.HiddenInput)</td>
          <td>@Html.EditorFor(m => m.HiddenInput)</td>
          <td>@Html.DisplayFor(m => m.HiddenInput)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Boolean)</td>
          <td>@Html.EditorFor(m => m.Boolean)</td>
          <td>@Html.DisplayFor(m => m.Boolean)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Email)</td>
          <td>@Html.EditorFor(m => m.Email)</td>
          <td>@Html.DisplayFor(m => m.Email)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Url)</td>
          <td>@Html.EditorFor(m => m.Url)</td>
          <td>@Html.DisplayFor(m => m.Url)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Password)</td>
          <td>@Html.EditorFor(m => m.Password)</td>
          <td>@Html.DisplayFor(m => m.Password)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Currency)</td>
          <td>@Html.EditorFor(m => m.Currency)</td>
          <td>@Html.DisplayFor(m => m.Currency)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.CreditCard)</td>
          <td>@Html.EditorFor(m => m.CreditCard)</td>
          <td>@Html.DisplayFor(m => m.CreditCard)</td>
        </tr>
      </tbody>
    </table>
    <input type="submit" value="Valider" />
  }
</body>
</html>

Dla każdej z właściwości modelu stosujemy metodę:

  • Html.LabelFor, aby wyświetlić wartość metadanych [DisplayName] danej właściwości;
  • Html.EditorFor w celu wygenerowania tagu HTML służącego do wprowadzenia wartości właściwości. Metoda ta wykorzystuje metadane właściwości [DataType] i [UIHint];
  • Html.DisplayFor w celu wyświetlenia wartości właściwości zgodnie z formatem określonym przez metadane [DataType].

Oto przykład wykonania w przeglądarce Chrome:

Image

W zależności od używanej przeglądarki mogą wyświetlać się różne strony. Generowany widok wykorzystuje bowiem nowe tagi wprowadzone w wersji 5 HTML, zwanej HTML5. Nie wszystkie przeglądarki obsługują jeszcze tę wersję. W powyższym przykładzie przeglądarka Chrome obsługuje ją częściowo.

5.8.1. [POST] formularza

Kod [POST] formularza jest przetwarzany przez następującą akcję [Action10Post]:


    // Akcja10-POST
    [HttpPost]
    public ContentResult Action10Post(ViewModel10 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, valide={2}, erreurs={3}", RouteData.Values["controller"], RouteData.Values["action"], ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • wiersz 3: akcja [Action10Post] ma jako model wejściowy przesłany formularz;
  • wiersz 5: pobierane są błędy walidacji tego formularza;
  • wiersz 6: przygotowuje się tekstową odpowiedź dla klienta;
  • wiersz 7: wysyła się ją.

Przeanalizujmy teraz po kolei właściwości modelu [ViewModel10] i zobaczmy, jak powiązane metadane wpływają na wygenerowany model HTML oraz na walidację pól wprowadzania danych.

5.8.2. Właściwość [Text]

Definicja


    [Display(Name="Text")]
    [DataType(DataType.Text)]
    public string Text { get; set; }
...
Text = "tra la la";

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Text)</td>
        <td>@Html.EditorFor(m => m.Text)</td>
        <td>@Html.DisplayFor(m => m.Text)</td>
</tr>

Element wizualny

 

HTML wygenerowano


      <tr>
        <td><label for="Text">Text</label></td>
        <td><input class="text-box single-line" id="Text" name="Text" type="text" value="tra la la" /></td>
        <td>tra la la</td>
</tr>

Komentarze

  • metoda [Html.LabelFor] wygenerowała tag <label> w wierszu 2. Wartość atrybutu [for] to nazwa właściwości parametru metody [Html.LabelFor]

public string Text { get; set; }

Tekst wyświetlany między początkiem a końcem znacznika to tekst metadanych


[Display(Name="Text")]

Metoda [Html.LabelFor] zawsze działa w ten sposób. Nie będziemy do tego wracać w przypadku pozostałych właściwości.

  • Metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3. Należy zauważyć, że posiada ona atrybut [class], który przypisuje do tego tagu klasę CSS [text-box single-line]. Atrybuty [id] i [name] mają jako wartość nazwę [Text] właściwości „parametr” metody [Html.EditorFor]. Atrybut [type] przyjął wartość [text] z powodu metadanych

[DataType(DataType.Text)]
  • metoda [Html.DisplayFor] wygenerowała tekst w wierszu 4. Jest to wartość właściwości parametru metody [Html.DisplayFor ]. Na działanie tej metody wpływają metadane

[DataType(DataType.Text)]

, co powoduje, że wartość jest wyświetlana jako tekst nieformatowany.

5.8.3. Właściwość [MultiLineText]

Definicja


    [Display(Name = "TextArea")]
    [DataType(DataType.MultilineText)]
public string MultiLineText { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.MultiLineText)</td>
        <td>@Html.EditorFor(m => m.MultiLineText)</td>
        <td>@Html.DisplayFor(m => m.MultiLineText)</td>
</tr>

Element wizualny

 

HTML wygenerowano


      <tr>
        <td><label for="MultiLineText">TextArea</label></td>
        <td><textarea class="text-box multi-line" id="MultiLineText" name="MultiLineText">
ligne1
ligne2</textarea></td>
        <td>ligne1
ligne2</td>
</tr>

Komentarze

  • Metoda [Html.EditorFor] wygenerowała tag <textarea> w wierszu 3. Należy zauważyć, że posiada ona atrybut [class], który przypisuje do tego tagu klasę CSS [text-box multi-line]. Atrybuty [id] i [name] mają jako wartość nazwę [MultiLineText] właściwości „parametr” metody [Html.EditorFor]. Tak jest zawsze. Nie będziemy już o tym wspominać. Wygenerowany tag to <textarea> z powodu metadanych

[DataType(DataType.MultilineText)]

, która określała, że właściwość ta jest tekstem wielowierszowym.

  • Metoda [Html.DisplayFor] wygenerowała tekst z wierszy 4–5. Jest to wartość właściwości parametru metody [Html.DisplayFor ].

5.8.4. Właściwość [Number]

Definicja


    [Display(Name = "Number")]
public int Number { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Number)</td>
        <td>@Html.EditorFor(m => m.Number)</td>
        <td>@Html.DisplayFor(m => m.Number)</td>
</tr>

Obraz

 

HTML wygenerowano


<tr>
        <td><label for="Number">Number</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-number="Le champ Number doit être un nombre." data-val-required="Le champ Number est requis." id="Number" name="Number" type="number" value="4" /></td>
        <td>4</td>
      </tr>

Komentarze

  • Metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [number]. Najwyraźniej po prostu dlatego, że właściwość ma typ [int]. Atrybuty [data-val], [data-val-number] i [data-val-required] są atrybutami nierozpoznawanymi przez HTML5. Są one wykorzystywane przez framework JavaScript do walidacji danych po stronie klienta;
  • metoda [Html.DisplayFor] wygenerowała tekst w wierszu 4, czyli wartość właściwości.

Walidacja

Atrybuty [data-x] mają wpływ na walidację danych po stronie klienta. Oto dwa przykłady:

Wprowadzamy nieprawidłową liczbę i sprawdzamy poprawność:

 

W powyższym przypadku walidacja odbyła się po stronie klienta. Formularz nie zostanie wysłany, dopóki błąd nie zostanie skorygowany.

Inny przykład: nie wpisano nic:

W przypadku kodu [1] powyżej, kod [Action10Post] sygnalizuje błąd. Być może pamiętamy, że takie zachowanie uzyskaliśmy już wcześniej, stosując atrybut [Required] na właściwości, która miała być sprawdzana (patrz strona 69), w tym przypadku właściwości [Number]. Tutaj nie musieliśmy tego robić.

5.8.5. Właściwość [Decimal]

Definicja


    [Display(Name = "Decimal")]
    [UIHint("Decimal")]
public double Decimal { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Decimal)</td>
        <td>@Html.EditorFor(m => m.Decimal)</td>
        <td>@Html.DisplayFor(m => m.Decimal)</td>
</tr>

Element wizualny

 

HTML wygenerowano


      <tr>
        <td><label for="Decimal">Decimal</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-number="Le champ Decimal doit être un nombre." data-val-required="Le champ Decimal est requis." id="Decimal" name="Decimal" type="text" value="10,20" /></td>
        <td>10,20</td>
</tr>

Komentarze

  • Metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [text]. Pozostałe atrybuty są identyczne z tymi wygenerowanymi dla poprzedniej właściwości [Number]. Metadane:

[UIHint("Decimal")]

sprawia, że wartość właściwości jest wyświetlana z dwoma miejscami po przecinku w przypadku obu metod: [Html.EditorFor] i [Html.DisplayFor]

Walidacja

W przeciwieństwie do poprzedniego przypadku po stronie klienta nie zgłaszany jest żaden błąd walidacji. Błąd jest zgłaszany wyłącznie przez akcję [Action10Post]. Również w tym przypadku wymagana jest liczba dziesiętna, bez konieczności nadawania jej atrybutu [Required].

5.8.6. Właściwość [Tel]

Definicja


    [Display(Name = "Tel")]
    [DataType(DataType.PhoneNumber)]
public string Tel { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Tel)</td>
        <td>@Html.EditorFor(m => m.Tel)</td>
        <td>@Html.DisplayFor(m => m.Tel)</td>
</tr>

Element wizualny

 

HTML wygenerowano


      <tr>
        <td><label for="Tel">Tel</label></td>
        <td><input class="text-box single-line" id="Tel" name="Tel" type="tel" value="0617181920" /></td>
        <td>0617181920</td>
</tr>

Komentarze

  • metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [tel]. Wartość ta została wygenerowana na podstawie metadanych:

[DataType(DataType.PhoneNumber)]

Typ [tel] dla tagu <input> jest nowością w stosunku do HTML5. Przeglądarka Chrome potraktowała go jako tag <input> o typie [text].

Walidacja

Nie zgłaszano żadnych błędów walidacji ani po stronie klienta, ani po stronie serwera. Można wprowadzić dowolną treść.

5.8.7. Właściwość [Date]

Definicja


    [Display(Name = "Date")]
    [DataType(DataType.Date)]
public DateTime Date { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Date)</td>
        <td>@Html.EditorFor(m => m.Date)</td>
        <td>@Html.DisplayFor(m => m.Date)</td>
</tr>

Element wizualny

 

HTML wygenerowano


      <tr>
        <td><label for="Date">Date</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-date="Le champ Date doit être une date." data-val-required="Le champ Date est requis." id="Date" name="Date" type="date" value="11/10/2013" /></td>
        <td>11/10/2013</td>
</tr>

Komentarze

  • metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [date]. Wartość ta została wygenerowana na podstawie metadanych:

[DataType(DataType.Date)]

Typ [date] dla tagu <input> jest nowością w stosunku do HTML5. Przeglądarka Chrome rozpoznaje go i umożliwia wprowadzenie daty za pomocą kalendarza. Ponadto wprowadzona data jest wyświetlana w formacie [jj/mm/aaaa], co oznacza, że Chrome dostosowuje format daty do formatu [locale] stosowanego przez przeglądarkę.

  • Metoda [Html.DisplayFor] również zapisała datę w formacie [jj/mm/aaaa], ponownie z powodu obecności metadanych [Date].

Walidacja

Po stronie klienta zgłaszana jest nieprawidłowa data [1], co uniemożliwia przesłanie pola formularza POST na serwer.

Brak daty nie jest sygnalizowany po stronie klienta, ale jest sygnalizowany po stronie serwera [2].

5.8.8. Właściwość [Time]

Definicja


    [Display(Name = "Time")]
    [DataType(DataType.Time)]
public DateTime Time { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Time)</td>
        <td>@Html.EditorFor(m => m.Time)</td>
        <td>@Html.DisplayFor(m => m.Time)</td>
</tr>

Element wizualny

 

HTML wygenerowano


      <tr>
        <td><label for="Time">Time</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-required="Le champ Time est requis." id="Time" name="Time" type="time" value="11:17" /></td>
        <td>11:17</td>
</tr>

Komentarze

  • metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [time]. Wartość ta została wygenerowana na podstawie metadanych:

[DataType(DataType.Time)]

Typ [time] dla tagu <input> jest nowością w stosunku do HTML5. Przeglądarka Chrome rozpoznaje go i umożliwia wprowadzenie godziny w formacie [hh:mm];

  • metoda [Html.DisplayFor] również zapisała godzinę w formacie [hh:mm], ponownie z powodu obecności metadanych [Time].

Weryfikacja

Technicznie nie jest możliwe wprowadzenie nieprawidłowej godziny. Brak godziny jest sygnalizowany po stronie serwera:

 

5.8.9. Właściwość [HiddenInput]

Definicja


    [Display(Name = "HiddenInput")]
    [UIHint("HiddenInput")]
public string HiddenInput { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.HiddenInput)</td>
        <td>@Html.EditorFor(m => m.HiddenInput)</td>
        <td>@Html.DisplayFor(m => m.HiddenInput)</td>
</tr>

Element wizualny

 

HTML wygenerowano


      <tr>
        <td><label for="HiddenInput">HiddenInput</label></td>
        <td>cach&#233;<input id="HiddenInput" name="HiddenInput" type="hidden" value="ukryte" /></td>
        <td>cach&#233;</td>
</tr>

Komentarze

  • Metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [hidden], tj. ukrytym (ale mimo to przesłanym) polem. Wartość ta została wygenerowana na podstawie metadanych:

[UIHint("HiddenInput")]
  • Metoda [Html.DisplayFor] zapisała wartość tego ukrytego pola.

5.8.10. Właściwość [Boolean]

Definicja


    [Display(Name = "Boolean")]
public bool Boolean { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Boolean)</td>
        <td>@Html.EditorFor(m => m.Boolean)</td>
        <td>@Html.DisplayFor(m => m.Boolean)</td>
</tr>

Obraz

 

HTML wygenerowano


      <tr>
        <td><label for="Boolean">Boolean</label></td>
        <td><input checked="checked" class="check-box" data-val="true" data-val-required="Le champ Boolean est requis." id="Boolean" name="Boolean" type="checkbox" value="true" /><input name="Boolean" type="hidden" value="false" /></td>
        <td><input checked="checked" class="check-box" disabled="disabled" type="checkbox" /></td>
</tr>

Komentarze

  • metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [checkbox], tj. pole wyboru. Wartość ta została wygenerowana, ponieważ właściwość ma typ logiczny:

public bool Boolean { get; set; }
  • metoda [Html.DisplayFor] wygenerowała wiersz 4, również pole wyboru (atrybut type), ale nieaktywne (atrybut disabled).

5.8.11. Właściwość [Email]

Definicja


    [Display(Name = "Email")]
    [DataType(DataType.EmailAddress)]
public string Email{ get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Email)</td>
        <td>@Html.EditorFor(m => m.Email)</td>
        <td>@Html.DisplayFor(m => m.Email)</td>
</tr>

Element wizualny

 

HTML wygenerowano


      <tr>
        <td><label for="Email">Email</label></td>
        <td><input class="text-box single-line" id="Email" name="Email" type="email" value="x@y.z" /></td>
        <td><a href="mailto:x@y.z">x@y.z</a></td>
</tr>

Komentarze

  • Metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [email]. Ten typ jest nowy w HTML5. Typ ten został wygenerowany na podstawie metadanych:

[DataType(DataType.EmailAddress)]

Wydaje się, że przeglądarka Chrome potraktowała ten typ jako typ [text].

  • Metoda [Html.DisplayFor] wygenerowała wiersz 4: link do adresu e-mail.

Walidacja

Po stronie klienta zgłoszono nieprawidłowy adres [1]:

Brak wprowadzonych danych nie powoduje żadnego błędu.

5.8.12. Właściwość [Url]

Definicja


    [Display(Name = "Url")]
    [DataType(DataType.Url)]
public string Url { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Url)</td>
        <td>@Html.EditorFor(m => m.Url)</td>
        <td>@Html.DisplayFor(m => m.Url)</td>
</tr>

Obraz

 

HTML wygenerowano


      <tr>
        <td><label for="Url">Url</label></td>
        <td><input class="text-box single-line" id="Url" name="Url" type="url" value="http://istia.univ-angers.fr" /></td>
        <td><a href="http://istia.univ-angers.fr">http://istia.univ-angers.fr</a></td>
</tr>

Komentarze

  • Metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [url]. Ten typ jest nowy w HTML5. Został on wygenerowany na podstawie metadanych:

[DataType(DataType.Url)]

Chrome wydaje się traktować ten typ jako typ [text].

  • Metoda [Html.DisplayFor] wygenerowała wiersz 4: odnośnik do URL.

Walidacja

Po stronie klienta zgłaszany jest nieprawidłowy typ URL: [1]:

Brak wprowadzonych danych nie powoduje żadnego błędu.

5.8.13. Właściwość [Password]

Definicja


    [Display(Name = "Password")]
    [DataType(DataType.Password)]
public string Password { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Password)</td>
        <td>@Html.EditorFor(m => m.Password)</td>
        <td>@Html.DisplayFor(m => m.Password)</td>
</tr>

Obraz

 

HTML wygenerowano


      <tr>
        <td><label for="Password">Password</label></td>
        <td><input class="text-box single-line password" id="Password" name="Password" type="password" value="mdp" /></td>
        <td>mdp</td>
</tr>

Komentarze

  • metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [password]. Typ ten został wygenerowany na podstawie metadanych:

[DataType(DataType.Password)]
  • metoda [Html.DisplayFor] wygenerowała wiersz 4.

5.8.14. Właściwość [Currency]

Definicja


    [Display(Name = "Currency")]
    [DataType(DataType.Currency)]
public double Currency { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.Currency)</td>
        <td>@Html.EditorFor(m => m.Currency)</td>
        <td>@Html.DisplayFor(m => m.Currency)</td>
</tr>

Obraz

 

HTML wygenerowano


      <tr>
        <td><label for="Currency">Currency</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-number="Le champ Currency doit être un nombre." data-val-required="Le champ Currency est requis." id="Currency" name="Currency" type="text" value="4,2" /></td>
        <td>4,20 €</td>
</tr>

Komentarze

  • metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [text];
  • metoda [Html.DisplayFor] wygenerowała wiersz 4, liczbę z dwoma miejscami po przecinku wraz ze symbolem waluty. Format ten został zastosowany ze względu na metadane:

[DataType(DataType.Currency)]

Walidacja

Po stronie serwera zgłaszana jest nieprawidłowa wartość [1] lub brak wartości [2]:

5.8.15. Właściwość [CreditCard]

Definicja


    [Display(Name = "CreditCard")]
    [DataType(DataType.CreditCard)]
public string CreditCard { get; set; }

Widok


      <tr>
        <td>@Html.LabelFor(m => m.CreditCard)</td>
        <td>@Html.EditorFor(m => m.CreditCard)</td>
        <td>@Html.DisplayFor(m => m.CreditCard)</td>
</tr>

Obraz

 

HTML wygenerowano


      <tr>
        <td><label for="CreditCard">CreditCard</label></td>
        <td><input class="text-box single-line" id="CreditCard" name="CreditCard" type="text" value="0123456789012345" /></td>
        <td>0123456789012345</td>
</tr>

Komentarze

  • Metoda [Html.EditorFor] wygenerowała tag <input> w wierszu 3 z atrybutem [type] typu [text]. Metoda [Html.DisplayFor] wygenerowała wiersz 4. Nie widać tutaj, co wnoszą metadane:

[DataType(DataType.CreditCard)]

Weryfikacja

Nie przeprowadzono żadnej weryfikacji ani po stronie klienta, ani po stronie serwera.

5.9. Walidacja formularza

Problem walidacji modelu akcji omówiliśmy już w punkcie 4.5 i kolejnych. Powracamy do tej kwestii w kontekście formularza:

  • jak sygnalizować użytkownikowi błędy wprowadzonych danych;
  • przeprowadzać walidację zarówno po stronie klienta, jak i po stronie serwera, aby szybciej sygnalizować użytkownikowi błędy.

5.9.1. Walidacja po stronie serwera

Rozważmy następujący model:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Net.Mail;

namespace Exemple_03.Models
{
  public class ViewModel11 : IValidatableObject
  {

    [Required(ErrorMessage = "Information requise")]
    [Display(Name = "Chaîne d'au moins quatre caractères")]
    [RegularExpression(@"^.{4,}$", ErrorMessage = "Information incorrecte")]
    public string Chaine1 { get; set; }

    [Display(Name = "Chaîne d'au plus quatre caractères")]
    [Required(ErrorMessage = "Information requise")]
    [RegularExpression(@"^.{1,4}$", ErrorMessage = "Information incorrecte")]
    public string Chaine2 { get; set; }

    [Required(ErrorMessage = "Information requise")]
    [Display(Name = "Chaîne de quatre caractères exactement")]
    [RegularExpression(@"^.{4,4}$", ErrorMessage = "Information incorrecte")]
    public string Chaine3 { get; set; }

    [Required(ErrorMessage = "Information requise")]
    [Display(Name = "Nombre entier")]
    public int Entier1 { get; set; }

    [Display(Name = "Nombre entier dans l'intervalle [1,100]")]
    [Required(ErrorMessage = "Information requise")]
    [Range(1, 100, ErrorMessage = "Information incorrecte")]
    public int Entier2 { get; set; }

    [Display(Name = "Nombre réel")]
    [Required(ErrorMessage = "Information requise")]
    public double Reel1 { get; set; }

    [Display(Name = "Nombre réel dans l'intervalle [10.2, 11.3]")]
    [Required(ErrorMessage = "Information requise")]
    [Range(10.2, 11.3, ErrorMessage = "Information incorrecte")]
    public double Reel2 { get; set; }

    [Display(Name = "Adresse mail")]
    [Required(ErrorMessage = "Information requise")]
    public string Email1 { get; set; }

    [Display(Name = "Date sous la forme dd/jj/aaaa")]
    [RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessage = "Information incorrecte")]
    [Required(ErrorMessage = "Information requise")]
    public string Regexp1 { get; set; }

    [Display(Name = "Date postérieure à celle d'aujourd'hui")]
    [Required(ErrorMessage = "Information requise")]
    [DataType(DataType.Date)]
    public DateTime Date1 { get; set; }

    // weryfikacja
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Data 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // E-mail 1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
      }
      // zwracamy listę błędów
      return résultats;
    }
  }
}

Ten wzorzec zostanie wyświetlony przez następujący widok [Action11Get.cshtml]:


@model Exemple_03.Models.ViewModel11
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action11Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body>
  <h3>Formulaire ASP.NET MVC – Validation 1</h3>
  @using (Html.BeginForm("Action11Post", "First"))
  {
    <table>
      <thead>
        <tr>
          <th>Type attendu</th>
          <th>Valeur saisie</th>
          <th>Message d'erreur</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine1)</td>
          <td>@Html.EditorFor(m => m.Chaine1)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine2)</td>
          <td>@Html.EditorFor(m => m.Chaine2)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine2)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine3)</td>
          <td>@Html.EditorFor(m => m.Chaine3)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine3)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Entier1)</td>
          <td>@Html.EditorFor(m => m.Entier1)</td>
          <td>@Html.ValidationMessageFor(m => m.Entier1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Entier2)</td>
          <td>@Html.EditorFor(m => m.Entier2)</td>
          <td>@Html.ValidationMessageFor(m => m.Entier2)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Reel1)</td>
          <td>@Html.EditorFor(m => m.Reel1)</td>
          <td>@Html.ValidationMessageFor(m => m.Reel1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Reel2)</td>
          <td>@Html.EditorFor(m => m.Reel2)</td>
          <td>@Html.ValidationMessageFor(m => m.Reel2)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Email1)</td>
          <td>@Html.EditorFor(m => m.Email1)</td>
          <td>@Html.ValidationMessageFor(m => m.Email1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Regexp1)</td>
          <td>@Html.EditorFor(m => m.Regexp1)</td>
          <td>@Html.ValidationMessageFor(m => m.Regexp1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Date1)</td>
          <td>@Html.EditorFor(m => m.Date1)</td>
          <td>@Html.ValidationMessageFor(m => m.Date1)</td>
        </tr>
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
</body>
</html>
  • wiersz 12: odwołujemy się do arkusza stylów [Site.css]. Zawiera on domyślnie klasy służące do wyróżniania błędów wprowadzonych w formularzu;
  • wiersze 18–25: tabela z trzema kolumnami:
    • kolumna 1 wyświetla tekst przy użyciu metody [Html.LabelFor],
    • kolumna 2 wyświetla wprowadzone dane przy użyciu metody [Html.EditorFor],
    • kolumna 3 wyświetla ewentualny błąd wprowadzenia danych za pomocą metody [Html.ValidationMessageFor];

Akcja [Action11Get] służy do wyświetlenia formularza:


    // Akcja11-GET
    [HttpGet]
    public ViewResult Action11Get()
    {
      return View("Action11Get", new ViewModel11());
}

Akcja [Action11Post] służy do ponownego wyświetlenia formularza wraz z ewentualnymi błędami wprowadzenia danych:


    // Akcja11-POST
    [HttpPost]
    public ViewResult Action11Post(ViewModel11 modèle)
    {
      return View("Action11Get", modèle);
}
  • wiersz 3: szablon [ViewModel11] jest tworzony, a następnie inicjowany z przesłanymi wartościami. W tym momencie mogą wystąpić błędy. Każdej błędnej właściwości P szablonu przypisany jest komunikat o błędzie. To właśnie ten komunikat pozwala uzyskać metodę [Html.ValidationMessageFor] formularza.

Oto przykład wykonania:

Oto kolejny przykład:

 

Należy zauważyć, że obie daty są błędne (dzisiaj jest 11.10.2013), ale błędy te nie są sygnalizowane. Błędy te są wykrywane przez metodę [Validate] modelu:


    // walidacja
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Data 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // E-mail 1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
      }
      // Regexp1
      try
      {
        DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Regexp1" }));
      }

      // wyświetlanie listy błędów
      return résultats;
}

Metoda [Validate] jest wykonywana dopiero po pomyślnym przejściu wszystkich walidacji atrybutów. Pokazuje to ostatni przykład:

 

5.9.2. Weryfikacja po stronie klienta

Wszystkie poprzednie walidacje odbywały się po stronie serwera. Konieczna jest zatem wymiana danych między klientem a serwerem, aby użytkownik mógł zauważyć swoje błędy. Walidacja po stronie klienta wykorzystuje kod JavaScript, aby jak najwcześniej, a w każdym razie przed uruchomieniem POST, poinformować użytkownika o błędach. Ta ostatnia operacja może nastąpić dopiero po skorygowaniu wszystkich wykrytych błędów.

Wracamy do poprzedniego szablonu [ViewModel11], ale teraz wyświetlamy go wraz z następującym widokiem [Action12Get.cshtml]:


@model Exemple_03.Models.ViewModel11
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action12Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js" ></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js" ></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js" ></script>
</head>
<body>
  <h3>Formulaire ASP.NET MVC - Validation 1</h3>
  @using (Html.BeginForm("Action11Post", "First"))
  {
    <table>
      <thead>
        <tr>
          <th>Type attendu</th>
          <th>Valeur saisie</th>
          <th>Message d'erreur</th>
        </tr>
      </thead>
      <tbody>
...
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
</body>
</html>

Uwaga: wiersz 13 – należy dostosować wersję pliku jQuery do wersji programu Visual Studio, z której Państwo korzystają (patrz poniżej).

Weryfikacja po stronie klienta wymaga obecności poniższego wiersza 3 w pliku [Web.config] aplikacji.


  <appSettings>
    ...
    <add key="ClientValidationEnabled" value="true" />
</appSettings>
  • wiersze 1–4: sekcja [appSettings] musi być bezpośrednim elementem podrzędnym sekcji [configuration] w pliku [Web.config];

Widok [Action12Get] jest identyczny z poprzednim widokiem [Action11Get], z wyjątkiem wierszy 13–15. Wiersze te zawierają skrypty JavaScript niezbędne do walidacji po stronie klienta. Skrypty te znajdują się w folderze [Scripts] projektu:

Każdy skrypt ma wersję standardową [.js] oraz wersję zminimalizowaną [min.js]. Ta ostatnia wersja jest lżejsza, ale nieczytelna. Stosuje się ją w środowisku produkcyjnym. Wersja czytelna jest używana na etapie rozwoju.

Widok [Action12Get.cshtml] zostanie wyświetlony przez następującą akcję [Action12Get]:


    // Akcja12-GET
    [HttpGet]
    public ViewResult Action12Get()
    {
      return View("Action12Get", new ViewModel11());
}

Wypełniony formularz zostanie przetworzony przez następującą akcję [Action12Post]:


    // Akcja12-POST
    [HttpPost]
    public ViewResult Action12Post(ViewModel11 modèle)
    {
      return View("Action12Get", modèle);
}

Zobaczmy, co to oznacza na przykładzie:

Gdy tylko wpiszemy znak w polu [1], pojawi się komunikat w polu [2], ponieważ oczekiwana wartość musi mieć co najmniej cztery znaki. W ten sposób walidacja odbywa się przy każdym nowo wpisanym znaku. Komunikat o błędzie znika po wpisaniu czwartego znaku. Po wykonaniu tej czynności zatwierdźmy formularz:

Komunikat URL [3] wskazuje, że komunikat [POST] nie został wygenerowany. Jednak kliknięcie przycisku [Valider] uruchomiło wszystkie walidacje po stronie klienta i pojawiły się nowe komunikaty o błędach.

Przyjrzyjmy się na przykład kodowi HTML wygenerowanemu przy pierwszym wprowadzeniu danych:


        <tr>
          <td><label for="Chaine1">Cha&#238;co najmniej czterech znaków</label></td>
          <td><input class="text-box single-line" data-val="true" data-val-regex="Information incorrecte" data-val-regex-pattern="^.{4,}$" data-val-required="Information requise" id="Chaine1" name="Chaine1" type="text" value="" /></td>
          <td><span class="field-validation-valid" data-valmsg-for="Chaine1" data-valmsg-replace="true"></span></td>
</tr>
  • w wierszu 3 znajduje się:
    • komunikat o błędzie w przypadku braku wprowadzonych danych [data-val-required],
    • komunikat o błędzie w przypadku błędnego wpisu [data-val-regex],
    • wyrażenie regularne dla wprowadzonego ciągu znaków [data-val-regex-pattern];
  • w wierszu 4 inne atrybuty [data-x] służące do wyświetlania ewentualnego komunikatu o błędzie;

Atrybuty [data-x] wygenerowanych tagów są wykorzystywane przez kod JavaScript, który osadziliśmy w widoku. Jeśli go nie ma, atrybuty te są po prostu ignorowane i nie ma wtedy walidacji po stronie klienta. Działa to tak samo jak w poprzednim przykładzie. Stąd nazwa [unobtrusive] dla tej techniki.

5.10. Obsługa linków nawigacyjnych i akcji

Aby zilustrować obsługę linków w widoku, utworzymy dwa następujące widoki:

  • w [1] i [2] mamy dwa linki nawigacyjne;
  • w widoku [3] znajduje się link akcji, który wysyła formularz. Nie służy on do nawigacji.

Strona 1 jest generowana przez następujący widok [Action16Get.cshtml]:


@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action16Get</title>
  <script>
    function postForm() {
      // pobieramy formularz z dokumentu
      var form = document.forms[0];
      // przesłanie
      form.submit();
    }
  </script>
</head>
<body>
  <h3>Navigation - page 1</h3>
  <h4>@ViewBag.info</h4>
  @using (Html.BeginForm("Action16Post", "Second"))
  {
    @Html.Label("data", "Tapez un texte")
    @Html.TextBox("data")
    <a href="javascript:postForm()">Valider</a>
  }
  <p>
    @Html.ActionLink("Page 2", "Action17Get", "Second")
  </p>
</body>
</html>
  • wiersz 22: informacja zainicjowana przez akcję, która wygeneruje widok;
  • wiersze 23–28: formularz;
  • wiersz 25: etykieta dla pola [data];
  • wiersz 26: pole wprowadzania danych o nazwie [data];
  • wiersz 27: link typu [submit]. Po kliknięciu na niego uruchamiana jest funkcja JavaScript [postForm] (atrybut href). Jest ona zdefiniowana w wierszach 12–17;
  • wiersz 14: pobierane jest odwołanie do pierwszego formularza w dokumencie, czyli tego z wiersza 23;
  • wiersz 16: formularz ten jest wysyłany. Ostatecznie wszystko przebiega tak, jakby kliknięto przycisk o nazwie [submit]. Formularz jest wysyłany do kontrolera i akcji określonych w wierszu 23;
  • wiersz 30: link nawigacyjny. Wygenerowany kod HTML wygląda następująco:

    <a href="/Second/Action17Get">Page 2</a>

Zastosowana metoda to ActionLink(Tekst, Akcja, Kontroler).

Strona 2 jest generowana przez następujący widok [Action17Get.cshtml]:


@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action17Get</title>
</head>
<body>
  <h3>Navigation - Page 2</h3>
  <h4>@ViewBag.info</h4>
  <p>
    @Html.ActionLink("Page 1", "Action16Get", "Second")
  </p>
</body>
</html>

Akcje generujące te widoki są następujące:


      // Akcja16-GET
      [HttpGet]
      public ViewResult Action16Get()
      {
        ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
        return View("Action16Get");
      }

      // Action16-POST
      [HttpPost]
      public ViewResult Action16Post(string data)
      {
        ViewBag.info = string.Format("Contrôleur={0}, Action={1}, Data={2}", RouteData.Values["controller"], RouteData.Values["action"], data);
        return View("Action16Get");
      }

      // Akcja17-GET
      [HttpGet]
      public ViewResult Action17Get()
      {
        ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
        return View();
}
  • w wierszu 6 akcja [Action16Get] generuje widok [Action16Get.cshtml], czyli stronę 1 z przykładu. Widok ten opiera się na szablonie [ViewBag] (wiersz 5);
  • w wierszu 19 akcja [Action17Get] generuje widok [Action17Get.cshtml], czyli stronę 2 z przykładu. Widok ten opiera się na szablonie [ViewBag] (wiersz 21);
  • wiersz 11: akcja [Action16Post] przetwarza POST z formularza widoku [Action16Get.cshtml]. Otrzymuje ona parametr o nazwie [data]. Przypomnijmy, że jest to nazwa pola wprowadzania danych w formularzu;
  • wiersz 13: do pola [ViewBag] wprowadzana jest informacja;
  • wiersz 14: wyświetlany jest widok [Action16Get.cshtml].

Zachęcamy czytelnika do przetestowania tego przykładu.