Skip to content

4. Das Modell einer Aktion

Kehren wir zur Architektur einer Anwendung ASP.NET MVC zurück:

Im vorigen Kapitel haben wir uns den Prozess angesehen, der die Anfrage [1] zum Controller und zur Aktion [2a] leitet, die sie verarbeiten werden – ein Mechanismus, der als Routing bezeichnet wird. Außerdem haben wir die verschiedenen Antworten vorgestellt, die eine Aktion an den Browser zurücksenden kann. Bislang haben wir Aktionen vorgestellt, die die ihnen übermittelte Anfrage nicht weiterverarbeitet haben. Eine Anfrage [1] enthält verschiedene Informationen, die ASP.NET und MVC der Aktion in Form einer Vorlage ([2a]) zur Verfügung stellen. Dieser Begriff ist nicht zu verwechseln mit der Vorlage M einer Ansicht V [2c], die von der Aktion erzeugt wird:

  • Die Anfrage HTTP des Kunden geht in [1] ein;
  • in [2] werden die in der Anfrage enthaltenen Informationen in die Aktionsvorlage [3] umgewandelt – häufig, aber nicht zwangsläufig eine Klasse –, die als Eingabe für die Aktion [4] dient;
  • In [4] generiert die Aktion anhand dieses Modells eine Antwort. Diese besteht aus zwei Komponenten: einer Ansicht V [6] und dem Modell M dieser Ansicht [5];
  • Die Ansicht V [6] verwendet ihr Modell M [5], um die für den Kunden bestimmte Antwort HTTP zu generieren.

Im Modell MVC ist die Aktion [4] Teil des C (Controllers), das Modell der Ansicht [5] ist das M und die Ansicht [6] ist das V.

In diesem Kapitel werden die Mechanismen der Verknüpfung zwischen den in der Anfrage übermittelten Informationen, bei denen es sich naturgemäß um Zeichenfolgen handelt, und dem Aktionsmodell, das eine Klasse mit Eigenschaften verschiedener Typen sein kann, behandelt.

4.1. Initialisierung der Aktionsparameter

Wir fügen der bestehenden Lösung ein neues Projekt [1] hinzu, das auf den Basisprojekten ASP.NET und MVC basiert:

  • in „[2]“, den Namen des neuen Projekts;
  • in [3, 4] wählen wir ein Basisprojekt ASP.NET MVC aus;
  • in [5] das neue Projekt.

Wir werden das neue Projekt zum Startprojekt der Lösung machen.

Wie bereits in Abschnitt 3.1 beschrieben, erstellen wir einen Controller mit dem Namen [First] [1]:

  

In diesem Controller erstellen wir die folgende Aktion „[Action01]“:


using System.Web.Mvc;

namespace Exemple_02.Controllers
{
  public class FirstController : Controller
  {
    // Aktion01
    public ContentResult Action01(string nom)
    {
      return Content(string.Format("Contrôleur=First, Action=Action01, nom={0}", nom));
    }

  }
}

Die Neuerung findet sich in Zeile 8: Die Methode [Action01] hat einen Parameter. In diesem Kapitel befassen wir uns mit den verschiedenen Möglichkeiten, die Parameter einer Aktion zu initialisieren. Der oben genannte Parameter [nom] wird der Reihe nach mit den folgenden Werten initialisiert:

Request.Form["nom"]
ein Parameter namens [nom], der von einem Befehl POST gesendet wird
RouteData.Values["nom"]
ein Element von URL mit dem Namen [nom]
Request.QueryString["nom"]
ein Parameter mit dem Namen [nom], gesendet durch einen Befehl GET
Request.Files["nom"]
eine hochgeladene Datei mit dem Namen [nom]

Betrachten wir diese verschiedenen Fälle. Rufen wir direkt im Browser die URL und [/First/Action01?nom=someone] auf. Wir erhalten folgende Antwort:

 

Die Anfrage des Browsers für HTTP lautete wie folgt:

1
2
3
GET /First/Action01?nom=someone HTTP/1.1
Host: localhost:55483
...
  • Zeile 1: Die Anfrage lautet GET. Die angeforderte URL enthält den Parameter [nom]. Auf der Serverseite trifft die Anfrage bei der Aktion [Action01] ein, die folgende Signatur hat:

public ContentResult Action01(string nom)

Um dem Parameter „name“ einen Wert zuzuweisen, probiert ASP.NET MVC nacheinander und in der angegebenen Reihenfolge die Werte Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"] nacheinander und in dieser Reihenfolge aus. Er stoppt, sobald er einen Wert gefunden hat. Der Parameter [nom], der in URL von GET eingebettet ist, wurde vom Framework in Request.QueryString["nom"] abgelegt. Mit diesem Wert [someone] wird der Parameter [nom] von [Action01] initialisiert. Anschließend wird der Code von [Action01] ausgeführt:


return Content(string.Format("Contrôleur=First, Action=Action01, nom={0}", nom), "text/plain", Encoding.UTF8);

Dieser Code liefert die an den Client gesendete Antwort:

 

Hinweis: Bei der Parameterverknüpfung wird die Groß-/Kleinschreibung nicht berücksichtigt. Wenn unsere Aktion also wie folgt definiert ist:


public ContentResult Action01(string NOM)

und der übergebene Parameter lautet [?NoM=zébulon], findet die Zuordnung dennoch statt. Der Parameter [NOM] von [Action01] erhält den Wert [zébulon].

Nun fordern wir denselben URL mit einem POST an. Dazu verwenden wir die Anwendung [Advanced Rest Client]:

  • in [1], die angeforderte URL;
  • in [2] wird der Befehl POST verwendet;
  • bei [3] die Parameter von POST.

Senden wir diese Anfrage ab und sehen wir uns die Protokolle von HTTP an. Die Anfrage HTTP lautet wie folgt:

  • in [1], das POST;
  • in [2] die Parameter von POST. Technisch gesehen wurden sie hinter den Headern von HTTP nach der Leerzeile gesendet, die das Ende dieser Header signalisiert;
  • in [3] die erhaltene Antwort. Der Parameter [nom] aus POST wird korrekt abgerufen. Bei den getesteten Werten für den Parameter „name“ Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"] hat der erste Wert funktioniert.

Nun ändern wir die Standardroute in [App_Start/RouteConfig]. Derzeit lautet diese Route wie folgt:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Ändern wir sie in:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{nom}",
          defaults: new { controller = "Home", action = "Index", nom = UrlParameter.Optional }
);
  • In Zeile 3 haben wir das dritte Element einer Route als „[nom]“ benannt;
  • Zeile 4: Dieses Element wird als optional deklariert.

Nun kompilieren wir die Anwendung neu und rufen URL [/First/Action01/zébulon] direkt im Browser auf. Wir erhalten folgende Antwort:

 

Bei den getesteten Werten für den Parameter „name“ – Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"] hat der zweite Wert funktioniert.

Führen wir dieselbe Abfrage mit POST und [Advanced Rest Client] durch:

  • Bei [1] haben wir dem Element {name} der Route einen Wert zugewiesen;
  • in [2] fügen wir der gesendeten Anfrage den Parameter [nom] hinzu;
  • die erhaltene Antwort lautet [3].

Bei den getesteten Werten für den Parameter [nom], Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"], waren zwei geeignet, nämlich die ersten beiden. Es wurde der erste verwendet.

4.2. Gültigkeit der Aktionsparameter überprüfen

Wenn eine Aktion einen Parameter namens [p] hat, versucht ASP.NET MVC, ihr einen der Werte Request.Form["p"], RouteData.Values["p"], Request.QueryString["p"] oder Request.Files["p"] zuzuweisen. Die ersten drei Werte sind Zeichenfolgen. Wenn der Parameter [p] nicht vom Typ [string] ist, können Probleme auftreten.

Erstellen wir die folgende neue Aktion:


    // Aktion02
    public ContentResult Action02(int age)
    {
      string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}", RouteData.Values["controller"], RouteData.Values["action"],age);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • In Zeile 2 akzeptiert die Aktion [Action02] einen Parameter namens [age] vom Typ int. Die abgerufene Zeichenkette muss daher in einen int konvertierbar sein.

Rufen wir die Aktion URL [http://localhost:55483/First/Action02?age=21] auf. Wir erhalten die folgende Seite:

 

Rufen wir nun die URL und [http://localhost:55483/First/Action02?age=21x] auf. Wir erhalten die folgende Seite:

 

Diesmal wurde eine Fehlerseite zurückgegeben. Es ist interessant, sich die vom Server in diesem Fall gesendeten Header HTTP anzusehen:

1
2
3
4
5
HTTP/1.1 500 Internal Server Error
...
Content-Type: text/html; charset=utf-8
...
Content-Length: 12438
  • Zeile 1: Der Server hat mit dem Code [500 Internal Server Error] geantwortet und eine Seite mit dem Code HTML (Zeile 3) von 12.438 Byte (Zeile 5) gesendet, um die möglichen Ursachen für diesen Fehler zu erläutern.

Erstellen wir nun die folgende Aktion [Action03]:


    // Aktion03
    public ContentResult Action03(int? age)
    {
      ...
}

[Action03] ist identisch mit [Action02], abgesehen davon, dass der Typ des Parameters [age] in „int?“ geändert wurde, was „Ganzzahl“ oder „null“ bedeutet.

Rufen wir nun URL und [http://localhost:55483/First/Action03?age=21x] auf. Wir erhalten die folgende Seite:

 

ASP.NET MVC konnte [21x] nicht in den Typ „int“ konvertieren. Daher wurde dem Parameter [age] der Wert „null“ zugewiesen, wie es sein Typ „int?“ zulässt. Es lässt sich jedoch feststellen, ob der Parameter einen Wert aus der Abfrage erhalten hat oder nicht.

Wir erstellen die folgende neue Aktion [Action04]:


    // Aktion04
    public ContentResult Action04(int? age)
    {
      bool valide = ModelState.IsValid;
      string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}, valide={3}", RouteData.Values["controller"], RouteData.Values["action"], age, valide);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • Zeile 2: Der Typ [int?] wurde beibehalten. Dadurch muss die Abfrage insbesondere den Parameter [age] nicht bereitstellen, der somit den Wert „null“ erhält;
  • Zeile 4: Es wird geprüft, ob das Aktionsmodell gültig ist. Das Aktionsmodell setzt sich aus der Gesamtheit seiner Parameter zusammen, hier [age]. Das Modell ist gültig, wenn alle Parameter einen Wert aus der Anfrage erhalten haben oder den Wert „null“, sofern der Typ des Parameters dies zulässt;
  • Zeile 5: Der Wert der Variablen [valide] wird in den an den Client gesendeten Text eingefügt.

Rufen wir die URL und [http://localhost:55483/First/Action04?age=21x] auf. Wir erhalten die folgende Seite:

 

ASP.NET MVC konnte [21x] nicht in den Typ „int“ konvertieren. Daher wurde dem Parameter [age] der Wert „null“ zugewiesen, wie es sein Typ „int“ zulässt. Es sind jedoch Konvertierungsfehler aufgetreten, wie der Wert von [valide] zeigt.

Möglicherweise wird eine Fehlermeldung angezeigt, die mit einer fehlgeschlagenen Konvertierung zusammenhängt. Sehen wir uns die folgende neue Aktion an:


    // Aktion05
    public ContentResult Action05(int? age)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}, valide={3}, erreurs={4}", RouteData.Values["controller"], RouteData.Values["action"], age, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Die Neuerung befindet sich in Zeile 4. Dort wird eine private Methode [getErrorMessagesFor] aufgerufen, an die der Status des Aktionsmodells übergeben wird. Sie gibt eine Zeichenkette zurück, die die Meldungen aller aufgetretenen Fehler zusammenfasst. Diese Methode lautet wie folgt:


private string getErrorMessagesFor(ModelStateDictionary état)
    {
      List<String> erreurs = new List<String>();
      string messages = string.Empty;
      if (!état.IsValid)
      {
        foreach (ModelState modelState in état.Values)
        {
          foreach (ModelError error in modelState.Errors)
          {
            erreurs.Add(getErrorMessageFor(error));
          }
        }
        foreach (string message in erreurs)
        {
          messages += string.Format("[{0}]", message);
        }
      }
      return messages;
    }
  • Zeile 1: Der an die Methode übergebene effektive Parameter [ModelState] ist vom Typ [ModelStateDictionary];
  • Zeile 3: eine Liste mit Fehlermeldungen, die zu Beginn leer ist;
  • Zeile 5: Es wird geprüft, ob der als Parameter übergebene Status gültig ist oder nicht. Falls nicht, werden alle Fehlermeldungen zu einer einzigen Zeichenkette zusammengefasst;
  • Zeile 7: Der Typ [ModelStateDictionary] verfügt über eine Eigenschaft [Values], die eine Sammlung von Typen [ModelState] darstellt. Pro Element des Modells gibt es einen [ModelState]. Zum Beispiel:
    • ModelState["age"]: der Modellstatus der Aktion für den Parameter [age],
    • ModelState["age"].Errors: die Fehlersammlung für diesen Parameter. Die Fehler haben den Typ [ModelError],
    • ModelState["age"].Errors[i].ErrorMessage: die eventuelle Fehlermeldung Nr. i für den Parameter [age] des Modells
    • ModelState["age"].Errors[i].Exception: Die Ausnahme des Fehlers Nr. i aus der Fehlersammlung zum Parameter [age],
    • ModelState["age"].Errors[i].Exception.InnerException: Die Ursache dieser Ausnahme,
    • ModelState["age"].Errors[i].Exception.InnerException.Message: die Meldung zur Ursache der Ausnahme;
  • Zeile 9: Die Sammlung [Errors] eines bestimmten [ModelState] wird durchlaufen;
  • Zeile 11: Die Fehlermeldung eines bestimmten [ModelError] wird abgerufen und der Liste der Fehlermeldungen in Zeile 3 hinzugefügt;
  • Zeilen 14–17: Die Elemente der Fehlermeldungsliste werden zu einer einzigen Zeichenkette zusammengefasst.

Die Methode [getErrorMessageFor] in Zeile 11 lautet wie folgt:


    private string getErrorMessageFor(ModelError error)
    {
      if (error.ErrorMessage != null && error.ErrorMessage.Trim() != string.Empty)
      {
        return error.ErrorMessage;
      }
      if (error.Exception != null && error.Exception.InnerException == null && error.Exception.Message != string.Empty)
      {
        return error.Exception.Message;
      }
      if (error.Exception != null && error.Exception.InnerException != null && error.Exception.InnerException.Message != string.Empty)
      {
        return error.Exception.InnerException.Message;
      }
      return string.Empty;
}
  • Zeile 1: Es wird ein Typ [ModelError] empfangen, der einen Fehler bei einem der Elemente des Aktionsmodells kapselt. Die Fehlermeldung wird an drei verschiedenen Stellen abgerufen:
    • in [ModelError].ErrorMessage, Zeilen 3–6;
    • in [ModelError].Exception.Message, Zeilen 7–10;
    • in [ModelError].Exception.InnerException.Message, Zeilen 11–14;

Bei den Tests fällt auf, dass die Fehlermeldung je nach Art des Modellelements an diesen drei Stellen zu finden ist. Es muss eine Regel geben, mit der man die zu einem Modellelement gehörende Fehlermeldung sicher ermitteln kann, aber ich kenne sie nicht. Daher suche ich an den verschiedenen Stellen, an denen ich sie finden kann, und zwar in einer bestimmten Reihenfolge. Sobald eine nicht leere Meldung gefunden wurde, wird sie zurückgegeben.

Rufen wir nun URL [http://localhost:55483/First/Action05?age=21x] auf. Wir erhalten die folgende Seite:

 

4.3. Eine Aktion mit mehreren Parametern

Betrachten wir die folgende neue Aktion:


    // Aktion06
    public ContentResult Action06(double? poids, int? age)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], poids, age, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • Zeile 2: Wir haben zwei Parameter: [poids] und [age].

Die zuvor beschriebenen Regeln gelten nun für beide Parameter. Hier sind einige Ausführungsbeispiele:

 
 

4.4. Eine Klasse als Vorlage für eine Aktion verwenden

Definieren wir eine Klasse, die als Vorlage für eine Aktion dienen soll. Wir legen sie im Ordner „[Models] [1]“ ab.

Der Code lautet wie folgt:


namespace Exemple_02.Models
{
  public class ActionModel01
  {
    public double? Poids { get; set; }
    public int? Age { get; set; }
  }
}

Unsere Klasse verfügt als automatische Eigenschaften über die beiden zuvor behandelten Parameter [Poids] und [Age]. Diese Klasse dient als Eingabeparameter für die Aktion [Action07]:


    // Aktion07
    public ContentResult Action07(ActionModel01 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • Zeile 2: Das Modell der Aktion ist eine Instanz vom Typ [ActionModel01].

Nehmen wir noch einmal die beiden gleichen Beispiele wie zuvor:

 
 

Es ist zu beachten, dass die Parameterverknüpfung nicht zwischen Groß- und Kleinschreibung unterscheidet. Die Parameter der Abfrage lauteten [age] und [poids]. Sie wurden den Eigenschaften [Age] und [Poids] der Klasse [ModelAction01] zugewiesen.

Außerdem haben wir bisher die Abfragen HTTP und [GET] verwendet. Zeigen wir nun, dass die Abfragen [POST] das gleiche Verhalten aufweisen. Dazu verwenden wir erneut die Anwendung [Advanced Rest Client]:

  • in [1], die angeforderte URL;
  • in [2] wird sie durch den Befehl POST aufgerufen;
  • in [3] die Parameter von POST.

Man erhält dieselbe Antwort wie beim GET:

Image

4.5. Aktionsvorlage mit Gültigkeitsbeschränkungen – 1

Mit dem vorherigen Modell:


namespace Exemple_02.Models
{
  public class ActionModel01
  {
    public double? Poids { get; set; }
    public int? Age { get; set; }
  }
}

können die Parameter [poids] und [age] in der Abfrage fehlen. In diesem Fall erhalten die Eigenschaften [Poids] und [Age] den Wert [null], und es wird kein Fehler gemeldet. Man könnte die Vorlage wie folgt umgestalten:


namespace Exemple_02.Models
{
  public class ActionModel01
  {
    public double Poids { get; set; }
    public int Age { get; set; }
  }
}

In den Zeilen 5 und 6 dürfen die Eigenschaften [Poids] und [Age] nicht mehr den Wert [null] annehmen. Schauen wir uns an, was mit diesem neuen Modell passiert, wenn die Parameter [poids] und [age] in der Abfrage fehlen.

 

Es sind keine Fehler aufgetreten, und die Eigenschaften [Poids] und [Age] haben ihren Initialisierungswert beibehalten: 0. ASP.NET MVC:

  • hat mit „new ActionModel01“ eine Instanz des Modells erstellt. Dabei erhielten die Eigenschaften [Poids] und [Age] den Wert 0;
  • diesen beiden Eigenschaften wurden keine Werte zugewiesen, da es keine Parameter mit diesen Namen gab.

Das erste Modell ermöglicht es uns, das Fehlen eines Parameters zu überprüfen: Die entsprechende Eigenschaft hat dann den Wert „[null]“. Das zweite Modell lässt dies nicht zu. Es ist möglich, weitere Validierungsbeschränkungen hinzuzufügen, die über den einfachen Typ der Parameter hinausgehen. Diese werden wir nun vorstellen.

Betrachten wir das folgende neue Aktionsmodell:

  

using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
  public class ActionModel02
  {
    [Required]
    [Range(1, 200)]
    public double? Poids { get; set; }
    [Required]
    [Range(1, 150)]
    public int? Age { get; set; }
  }
}
  • Zeile 6: gibt an, dass das Feld [Poids] erforderlich ist;
  • Zeile 7: gibt an, dass das Feld [Poids] im Bereich [1,200] liegen muss;
  • Zeile 9: gibt an, dass das Feld [Age] erforderlich ist;
  • Zeile 7: gibt an, dass das Feld [Age] im Intervall [1,150] liegen muss;

Die Aktion, die diese Vorlage verwendet, ist die folgende Aktion [Action08]:


    // Aktion08
    public ContentResult Action08(ActionModel02 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • Zeile 2: Die Aktion erhält eine Instanz der Vorlage [ActionModel02];

Führen wir einige Tests durch:

 
 
 
 

Die Fehler werden korrekt erkannt. Nun passen wir das Modell wie folgt an:


using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
  public class ActionModel02
  {
    [Required]
    [Range(1, 200)]
    public double Poids { get; set; }
    [Required]
    [Range(1, 150)]
    public int Age { get; set; }
  }
}

In den Zeilen 8 und 11 dürfen die Eigenschaften nicht mehr den Wert [null] haben. Kompilieren wir das Modell und führen wir den Test ohne Parameter erneut durch:

 

Da keine Parameter angegeben wurden, haben die Eigenschaften [Poids] und [Age] ihren bei der Instanziierung des Modells festgelegten Wert beibehalten: 0. Anschließend erfolgt die Validierung. Das Attribut [Required] ist dann erfüllt. Man sieht, dass die obige Fehlermeldung sich auf das Attribut [Range] bezieht. Um also das Vorhandensein eines Parameters zu überprüfen, muss die zugehörige Eigenschaft nullable lauten, d. h. sie muss den Wert null annehmen können.

Kehren wir zum ursprünglichen Modell [ActionModel02] zurück und betrachten wir eine Aktion, deren Modell aus einer Instanz [ActionModel02] und einem Typ [DateTime] nullable besteht:


    // Aktion09
    public ContentResult Action09(ActionModel02 modèle, DateTime? date)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, date={4}, valide={5}, erreurs={6}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, date, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Führen wir einige Tests durch:

 

Der Aktion wurden keine Parameter übergeben. Die Attribute [Required] der Eigenschaften [Poids] und [Age] haben ihre Funktion erfüllt. Das Datum erhielt den Wert null, und es wurde kein Fehler gemeldet.

Nun werden ungültige Parameter übergeben:

 

Nun übergeben wir gültige Werte:

 

Betrachten wir weitere Gültigkeitsbedingungen. Das neue Aktionsmuster lautet wie folgt:

  

using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
  public class ActionModel03
  {
    [Required(ErrorMessage = "Le paramètre email est requis")]
    [EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
    public string Email { get; set; }

    [Required(ErrorMessage = "Le paramètre jour est requis")]
    [RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
    public string Jour { get; set; }

    [Required(ErrorMessage = "Le paramètre info1 est requis")]
    [MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
    public string Info1 { get; set; }

    [Required(ErrorMessage = "Le paramètre info2 est requis")]
    [MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
    public string Info2 { get; set; }

    [Required(ErrorMessage = "Le paramètre info3 est requis")]
    [MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    [MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    public string Info3 { get; set; }
  }
}
  • Zeile 6: das Attribut [Required], diesmal mit einer Fehlermeldung, die wir selbst festlegen;
  • Zeile 7: Das Attribut [EMailAddress] verlangt, dass das Feld [Email] eine E-Mail-Adresse im gültigen Format enthält;
  • Zeile 11: Das Attribut [RegularExpression] schreibt vor, dass das Feld [Jour] eine Zeichenfolge aus einer oder zwei Ziffern enthalten muss. Der erste Parameter ist der reguläre Ausdruck, auf den das Feld geprüft werden soll;
  • Zeile 15: Das Attribut [MaxLength] schreibt vor, dass das Feld [Info1] höchstens 4 Zeichen enthalten darf;
  • Zeile 19: Das Attribut [MinLength] legt fest, dass das Feld [Info2] mindestens 2 Zeichen enthalten muss;
  • Zeilen 23–24: Die Attribute [MaxLength] und [MinLength] legen zusammen fest, dass das Feld [Info3] genau 4 Zeichen enthalten muss;

Die Aktion [Action10] verwendet dieses Muster:


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

Führen wir einige Tests mit dieser Aktion durch.

Zunächst einmal ohne Parameter:

 

Dann mit ungültigen Parametern:

 

Anschließend mit gültigen Parametern:

 

4.6. Aktionsmodell mit Gültigkeitsbeschränkungen – 2

Wir stellen weitere Integritätsbeschränkungen vor. Das neue Aktionsmodell ist die folgende Klasse [ActionModel04]:


using System.ComponentModel.DataAnnotations;

namespace Exemple_02.Models
{
  public class ActionModel04
  {
    [Required(ErrorMessage="Le paramètre url est requis")]
    [Url(ErrorMessage="URL invalide")]
    public string Url { get; set; }
    [Required(ErrorMessage = "Le paramètre info1 est requis")]
    public string Info1 { get; set; }
    [Required(ErrorMessage = "Le paramètre info2 est requis")]
    [Compare("Info1",ErrorMessage="Les paramètres info1 et info2 doivent être identiques")]
    public string Info2 { get; set; }
    [Required(ErrorMessage = "Le paramètre cc est requis")]
    [CreditCard(ErrorMessage = "Le paramètre cc n'est pas un n° de carte de crédit valide")]
    public string Cc { get; set; }
  }
}
  • Zeile 8: Legt fest, dass das kommentierte Feld ein gültiges URL sein muss;
  • Zeile 13: fordert, dass die Eigenschaften [Info1] und [Info2] denselben Wert haben;
  • Zeile 16: fordert, dass das kommentierte Feld eine gültige Kreditkartennummer ist.

Die Aktion, die diese Vorlage verwendet, lautet wie folgt:


    // Aktion11
    public ContentResult Action11(ActionModel04 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("URL={0}, Info1={1}, Info2={2}, CC={3},erreurs={4}",
        modèle.Url, modèle.Info1, modèle.Info2, modèle.Cc, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Um die Aktion [Action11] zu testen, verwenden wir die Anwendung [Advanced Rest Client]:

  • in [1] wird die Aktion URL der Aktion [Action11] verwendet;
  • in [2] wird diese URL zusammen mit einer POST angefordert;
  • in [3] wählt man die Registerkarte [Form] aus;
  • in [4] die Werte der vier erwarteten Parameter. Diese Initialisierung ist eine von [ARC] bereitgestellte Funktion. Die tatsächlich gesendeten Parameter sind auf den Registerkarten [Raw] und [5] zu sehen;
  • in [6] die Parameter von POST.

Auf diese Abfrage erhält man folgende Antwort:

Geben wir nun ungültige Parameter ein:

 

Daraufhin erhalten wir folgende Antwort:

4.7. Aktionsmodell mit Gültigkeitsbeschränkungen – 3

Manchmal reichen die verfügbaren Integritätsbeschränkungen nicht aus. In diesem Fall kann man eigene erstellen. Insbesondere kann man ein Modell verwenden, das die Schnittstelle [IValidatableObject] implementiert. In diesem Fall fügt man die eigenen Modellprüfungen in die Methode [Validate] dieser Schnittstelle ein. Sehen wir uns ein Beispiel an. Das neue Modell der Aktion wird die folgende Klasse [ActionModel05] sein:


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

namespace Exemple_02.Models
{
  public class ActionModel05 : IValidatableObject
  {
    [Required(ErrorMessage = "Le paramètre taux est requis")]
    public double? Taux { get; set; }
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      bool ok = Taux < 4.2 || Taux > 6.7;
      if (!ok)
      {
        résultats.Add(new ValidationResult("Le paramètre taux doit être < 4.2 ou > 6.7", new string[] { "Taux" }));
      }
      return résultats;
    }
  }
}
  • Zeile 6: Das Modell implementiert die Schnittstelle [IValidatableObject];
  • Zeile 10: die Methode [Validate] dieser Schnittstelle. Sie gibt eine Sammlung von Elementen vom Typ [ValidationResult] zurück. Dieser Typ kapselt die Fehler, die gemeldet werden sollen;
  • Zeile 9: Ein gültiger Wert ist ein Wert < 4,2 oder > 6,7;
  • Zeile 12: Es wird eine leere Liste von Elementen vom Typ [ValidationResult] erstellt;
  • Zeile 13: Die Gültigkeit der Eigenschaft [Taux] wird überprüft;
  • Zeilen 14–17: Ist die Eigenschaft [Taux] ungültig, wird ein Element vom Typ [ValidationResult] zur Ergebnisliste hinzugefügt. Der erste Parameter ist eine Fehlermeldung. Der zweite Parameter, der optional ist, ist eine Sammlung der von diesem Fehler betroffenen Eigenschaften.

Die Aktion, die dieses Muster verwendet, lautet wie folgt:


    // Aktion12
    public ContentResult Action12(ActionModel05 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Hier ein Ausführungsbeispiel:

 

4.8. Aktionsvorlage vom Typ „Tabelle“ oder „Liste“

Betrachten wir die folgende Aktion [Action13]:


// Aktion13
    public ContentResult Action13(string[] data)
    {
      string strData = "";
      if (data != null && data.Length != 0)
      {
        strData = string.Join(",", data);
      }
      string texte = string.Format("data=[{0}]", strData);
      return Content(texte, "text/plain", Encoding.UTF8);
    }
  • Zeile 2: Die Vorlage der Aktion besteht aus einer Tabelle mit der Bezeichnung [string]. Sie ermöglicht es uns, einen Parameter namens [data] abzurufen, der mehrfach in den Parametern der Abfrage vorkommen kann, wie beispielsweise in [?data=data1&data=data2&data=data3]. Die verschiedenen Parameter [data] der Anfrage werden in das Array [data] des Aktionsmodells eingelesen. Dieser Fall tritt bei Mehrfachauswahllisten auf. Der Browser sendet dann die verschiedenen vom Benutzer ausgewählten Werte mit demselben Parameternamen.

Hier ein Beispiel:

 

Das Modell kann auch eine Liste sein:


    // Aktion14
    public ContentResult Action14(List<int> data)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string strData = "";
      if (data != null && data.Count != 0)
      {
        strData = string.Join(",", data);
      }
      string texte = string.Format("data=[{0}], erreurs=[{1}]", strData, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Das Modell ist hier eine Liste von Ganzzahlen (Zeile 2). Hier ist eine erste Ausführung:

 

und eine zweite:

 

4.9. Filtern eines Aktionsmodells

Manchmal steht uns ein Modell zur Verfügung, wir möchten jedoch, dass nur bestimmte Elemente des Modells durch die Abfrage HTTP initialisiert werden. Betrachten wir das folgende Aktionsmodell [ActionModel06]:


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

namespace Exemple_02.Models
{
  [Bind(Exclude = "Info2")]
  public class ActionModel06
  {
    [Required(ErrorMessage = "Le paramètre [info1] est requis")]
    public string Info1 { get; set; }

    public string Info2 { get; set; }
  }
}
  • Zeilen 9–10: Der Parameter [info1] ist obligatorisch;
  • Zeile 6: Der Parameter [info2] aus Zeile 12 wird aus der Verknüpfung der Abfrage HTTP mit ihrem Modell ausgeschlossen.

Die Aktion lautet wie folgt: [Action15]:


    // Aktion15
    public ContentResult Action15(ActionModel06 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("valide={0}, info1={1}, info2={2}, erreurs={3}", ModelState.IsValid, modèle.Info1, modèle.Info2, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Hier ein Ausführungsbeispiel:

  • in [1]: Der Parameter [info2] wird an URL übergeben;
  • in [2]: Die Eigenschaft [Info2] des Aktionsmodells blieb leer.

4.10. Erweiterung des Datenverbindungsmodells

Kehren wir zur Ausführungsarchitektur einer Aktion zurück:

Die Aktionsklasse wird zu Beginn der Client-Anfrage instanziiert und am Ende derselben wieder freigegeben. Daher kann sie nicht dazu dienen, Daten zwischen zwei Anfragen zu speichern, selbst wenn sie wiederholt aufgerufen wird. Es kann sinnvoll sein, zwei Arten von Daten zu speichern:

  • Daten, die von allen Benutzern der Webanwendung gemeinsam genutzt werden. Dabei handelt es sich in der Regel um schreibgeschützte Daten. Zur Umsetzung dieser gemeinsamen Datennutzung werden drei Dateien verwendet:
    • [Web.Config]: die Konfigurationsdatei der Anwendung
    • [Global.asax, Global.asax.cs]: Ermöglicht die Definition einer Klasse, der sogenannten globalen Anwendungsklasse, deren Lebensdauer der der Anwendung entspricht, sowie von Handlern für bestimmte Ereignisse dieser Anwendung.

Die globale Anwendungsklasse ermöglicht es, Daten zu definieren, die für alle Abfragen aller Benutzer verfügbar sind.

  • Daten, die von den Anfragen desselben Clients gemeinsam genutzt werden. Diese Daten werden in einem Objekt namens „Session“ gespeichert. Man spricht dabei von einer Client-Session, um den Speicher des Clients zu bezeichnen. Alle Anfragen eines Clients haben Zugriff auf diese Session. Sie können dort Informationen speichern und abrufen.

Oben zeigen wir die Speichertypen, auf die eine Aktion Zugriff hat:

  • den Anwendungsspeicher, der meist schreibgeschützte Daten enthält und für alle Benutzer zugänglich ist;
  • den Speicher eines bestimmten Benutzers, auch Sitzung genannt, der Lese-/Schreibdaten enthält und auf den aufeinanderfolgende Anfragen desselben Benutzers zugreifen können;
  • oben nicht dargestellt, gibt es einen Anforderungsspeicher oder Anforderungskontext. Die Anforderung eines Benutzers kann von mehreren aufeinanderfolgenden Aktionen verarbeitet werden. Der Anforderungskontext ermöglicht es einer Aktion 1, Informationen an eine Aktion 2 weiterzugeben.

Betrachten wir ein erstes Beispiel, das diese verschiedenen Speicher verdeutlicht:

Zunächst ändern wir die Datei [Web.config] des Projekts [Exemple-02] wie folgt:


  <appSettings>
    <add key="webpages:Version" value="2.0.0.0" />
    ...
    <add key="infoAppli1" value="infoAppli1"/>
</appSettings>

Wir fügen Zeile 4 hinzu, die dem Schlüssel [infoAppli1] den Wert [infoAppli1] zuordnet. Dies wird unser Bereichsdatum [Application] sein: Es ist für alle Abfragen aller Benutzer zugänglich.

Anschließend ändern wir die Methode [Application_Start] in der Datei [Global.asax]. Diese Methode wird beim Start der Anwendung einmalig ausgeführt. Hier muss die Datei [Web.config] verwendet werden:


    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();

      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // Anwendungsinitialisierung
      Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
}

Wir fügen Zeile 10 hinzu. Diese erfüllt zwei Aufgaben:

  • Sie ruft den Wert des Schlüssels [infoAppli1] aus der Datei [Web.config] mithilfe der Klasse [System.Configuration.ConfigurationManager] ab;
  • sie speichert ihn im Wörterbuch [HttpApplication.Application], das dem Schlüssel [infoAppli1] zugeordnet ist. Alle Aktionen haben Zugriff auf dieses Wörterbuch.

In derselben Datei [Gloabal.asax] wird die folgende Methode [Session_Start] hinzugefügt:


    protected void Session_Start()
    {
      // Zählerinitialisierung
      Session["compteur"] = 0;
}

Die Methode [Session_Start] wird für jeden neuen Benutzer ausgeführt. Was ist ein neuer Benutzer? Ein Benutzer wird durch ein Sitzungstoken „verfolgt“. Dieses Token wird:

  • vom Webserver erstellt und dem neuen Benutzer in den Headern HTTP der ersten Antwort, die an ihn gesendet wird, übermittelt;
  • wird vom Browser des Benutzers bei jeder neuen Anfrage, die er stellt, zurückgesendet. Dadurch kann der Server den Benutzer erkennen und einen Speicher für ihn verwalten, der als Benutzersitzung bezeichnet wird.

Der Webserver erkennt, dass es sich um einen neuen Benutzer handelt, wenn dieser ihm kein Sitzungstoken sendet. Der Server erstellt dann eines für ihn.

In Zeile 4 oben wird in die Benutzersitzung ein Zähler gesetzt, der bei jeder Anfrage dieses Benutzers erhöht wird. Dies veranschaulicht den einem Benutzer zugeordneten Speicher. Die Klasse [Session] wird wie ein Wörterbuch verwendet (Zeile 4).

Anschließend schreiben wir die folgende Aktion [Action16]:


// Aktion 16
    public ContentResult Action16()
    {
      // Abfragekontext abrufen HTTP
      HttpContextBase contexte = ControllerContext.HttpContext;
      // Informationen zum Anwendungsbereich abrufen
      string infoAppli1 = contexte.Application["infoAppli1"] as string;
      // sowie die Informationen zum Sitzungsbereich
      int? compteur = contexte.Session["compteur"] as int?;
      compteur++;
      contexte.Session["compteur"] = compteur;
      // die Antwort an den Client
      string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • Zeile 5: Wir rufen den Kontext der gerade bearbeiteten Anfrage HTTP ab. Dieser Kontext ermöglicht uns den Zugriff auf die Daten der Gültigkeitsbereiche [Application] und [Session];
  • Zeile 7: Die Informationen des Bereichs [Application] werden abgerufen;
  • Zeile 9: Der Zähler wird aus der Sitzung abgerufen;
  • Zeilen 10–11: Er wird erhöht und anschließend wieder in die Sitzung zurückgeschrieben;
  • Zeilen 13–14: Beide Informationen werden an den Client gesendet.

Hier sind einige Ausführungsbeispiele:

[Action16] wird zunächst einmal angefordert, dann [1], anschließend wird die Seite aktualisiert: zweimal [F5] und einmal [2]:

Bei [2] hat der Client insgesamt drei Anfragen gestellt. Bei jeder Anfrage konnte er den durch die vorherige Anfrage aktualisierten Zähler abrufen.

Um einen zweiten Nutzer zu simulieren, verwenden wir einen zweiten Browser, um denselben URL abzufragen:

In [3] ruft der zweite Benutzer zwar dieselben Reichweiteninformationen wie in [Application] ab, verfügt jedoch über einen eigenen Reichweitenzähler ([Session]).

Kehren wir zum Code der Aktion [Action16] zurück:


// Aktion 16
    public ContentResult Action16()
    {
      // Der Kontext der Anfrage wird abgerufen HTTP
      HttpContextBase contexte = ControllerContext.HttpContext;
      // die Informationen im Anwendungsbereich werden abgerufen
      string infoAppli1 = contexte.Application["infoAppli1"] as string;
      // sowie die Informationen zum Sitzungsbereich
      int? compteur = contexte.Session["compteur"] as int?;
      compteur++;
      contexte.Session["compteur"] = compteur;
      // die Antwort an den Client
      string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Eines der Ziele des Frameworks ASP.NET MVC ist es, Controller und Aktionen isoliert und ohne Webserver testbar zu machen. In Zeile 5 ist jedoch zu sehen, dass der Kontext der Anfrage HTTP erforderlich ist, um die Informationen aus dem Bereich [Application] und dem Bereich [Session] abzurufen. Es wird vorgeschlagen, eine neue Aktion [Action17] zu erstellen, die die Daten der Bereiche [Application] und [Session] als Parameter erhält:


    // Aktion 17
    public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
    {
      // Die Informationen im Anwendungsbereich werden abgerufen
      string infoAppli1 = applicationData.InfoAppli1;
      // sowie die Informationen aus dem Sitzungsbereich
      int compteur = sessionData.Compteur++;
      // die Antwort an den Client
      string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Der Code weist nun keine Abhängigkeiten mehr von der Abfrage HTTP auf. Er kann daher unabhängig von einem Webserver getestet werden.

Schauen wir uns an, wie das geht. Zunächst müssen wir die Klassen [ApplicationModel] und [SessionModel] erstellen, die jeweils die Daten der Scopes [Application] und [Session] kapseln. Diese lauten wie folgt:


namespace Exemple_02.Models
{
  public class ApplicationModel
  {
    public string InfoAppli1 { get; set; }
  }
}

namespace Exemple_02.Models
{
  public class SessionModel
  {
    public int Compteur { get; set; }
    public SessionModel()
    {
      Compteur = 0;
    }
  }
}

Anschließend müssen wir die Methoden [Application_Start] und [Session_Start] in der Datei [Global.asax] ändern:


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

      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // Anwendungsinitialisierung – Fall 1
      Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
      // Anwendungsinitialisierung – Fall 2
      ApplicationModel data=new ApplicationModel();
      data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
      Application["data"] = data;
    }

    protected void Session_Start()
    {
      // Zählerinitialisierung – Fall 1
      Session["compteur"] = 0;
      // Zählerinitialisierung – Fall 2
      Session["data"] = new SessionModel();
    }
  }
  • Zeile 14: Es wird eine Instanz von [ApplicationModel] erstellt;
  • Zeile 15: Diese wird initialisiert;
  • Zeile 16: und in das Wörterbuch von [Application] aufgenommen, verknüpft mit dem Schlüssel [data]. [Application] ist eine Eigenschaft der Klasse [HttpApplication] aus Zeile 1;
  • Zeile 24: Eine Instanz von [SessionModel] wird erstellt und in das Wörterbuch von [Session] aufgenommen, verknüpft mit dem Schlüssel [data]. [Session] ist eine Eigenschaft der Klasse [HttpApplication] aus Zeile 1;

Wenn wir uns an das halten, was wir bisher gesehen haben, lautet die Signatur


    public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)

bedeutet, dass die von der Aktion verarbeitete Abfrage HTTP Parameter mit den Namen [applicationData] und [sessionData] enthalten muss. Dies wird jedoch nicht der Fall sein. Wir müssen ein neues Datenverbindungsmodell erstellen, damit, wenn eine Aktion als Parameter einen Typ

  • [ApplicationModel] als Parameter erhält, ihr die Daten mit dem Gültigkeitsbereich [Application] und dem Schlüssel [data] bereitgestellt werden;
  • [SessionModel], wobei die Daten mit dem Gültigkeitsbereich [Session] und dem Schlüssel [data] bereitgestellt werden müssen.

Dazu müssen Klassen erstellt werden, die die Schnittstelle [IModelBinder] implementieren.

Zunächst erstellen wir im Projekt [Exemple-02] einen Ordner mit dem Namen [Infrastructure]:

  

Darin erstellen wir die folgende Klasse „[ApplicationModelBinder]“:


using System.Web.Mvc;

namespace Exemple_02.Infrastructure
{
  public class ApplicationModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // Die Daten des Bereichs [Application] werden zurückgegeben
      return controllerContext.RequestContext.HttpContext.Application["data"];
    }
  }
}
  • Zeile 5: Die Klasse implementiert die Schnittstelle [IModelBinder]. Um den Code zu verstehen, muss man wissen, dass sie jedes Mal aufgerufen wird, wenn eine Aktion einen Parameter vom Typ [ApplicationModel] hat. Diese Verknüpfung [ApplicationModel] --> [ApplicationModelBinder] wird beim Start der Anwendung in der Methode [Application_Start] von [Global.asax] hergestellt;
  • Zeile 7: die einzige Methode der Schnittstelle [IModelBinder];
  • Zeile 7: Der Parameter vom Typ [ControllerContext] ermöglicht uns den Zugriff auf die gerade verarbeitete Abfrage HTTP;
  • Zeile 7: Der Parameter vom Typ [ModelBindingContext] ermöglicht uns den Zugriff auf Informationen zum zu erstellenden Modell, hier den Typ [ApplicationModel];
  • Zeile 7: Das Ergebnis von [BindModel] ist das Objekt, das dem verknüpften Parameter zugewiesen wird, hier ein Parameter vom Typ [ApplicationModel];
  • Zeile 10: Wir geben lediglich das Objekt mit dem Gültigkeitsbereich [Application] und dem Schlüssel [data] zurück.

Die Klasse [SessionModelBinder] folgt dem gleichen Schema:


using System.Web.Mvc;

namespace Exemple_02.Infrastructure
{
  public class SessionModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // Die Daten des Bereichs [Session] werden zurückgegeben
      return controllerContext.HttpContext.Session["data"];
    }
  }
}

Nun müssen wir nur noch jedes der Modelle [XModel] seinem jeweiligen binder und [XModelBinder] zuordnen. Dies geschieht in der Methode [Application_Start] von [Global.asax]:


    protected void Application_Start()
    {
....
      // Anwendungsinitialisierung – Fall 2
      ApplicationModel data=new ApplicationModel();
      data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
      Application["data"] = data;
      // Modellbinder
      ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
  • Zeile 9: Wenn eine Aktion einen Parameter vom Typ [ApplicationModel] hat, wird die Methode [ApplicationModelBinder.Bind] aufgerufen. Es ist bekannt, dass diese Methode den Datenbereich [Application] zurückgibt, der dem Schlüssel [data] zugeordnet ist;
  • Zeile 10: Gleiches gilt für den Typ [SessionModel].

Kehren wir zu unserer Aktion [Action17] zurück:


    // Aktion 17
    public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
    {
      // Abfrage der Informationen aus dem Anwendungsbereich
      string infoAppli1 = applicationData.InfoAppli1;
      // sowie die Informationen aus dem Bereich „Session“
      sessionData.Compteur++;
      int compteur = sessionData.Compteur;
      // die Antwort an den Client
      string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • Zeile 2: Wenn [Action17] aufgerufen wird, erhält sie als
    • ersten Parameter die Bereichsdaten [Application], die dem Schlüssel [data] zugeordnet sind,
    • als zweiten Parameter: den Bereichsdatensatz [Session], der dem Schlüssel [data] zugeordnet ist;

Diese beiden Daten können beliebig komplex sein und zum einen alle Daten des Bereichs [Application] und zum anderen alle Daten des Bereichs [Session] umfassen.

Hier ein Beispiel für die Ausführung der Aktion [Action17]:

 

4.11. Nachträgliche Verknüpfung der Aktionsvorlage

Wir haben die folgende Aktion [Action12] geschrieben:


// Aktion 12
    public ContentResult Action12(ActionModel05 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Im Hintergrund erstellt ASP.NET MVC:

  • erstellt eine Instanz vom Typ [ActionModel05] unter Verwendung ihres Konstruktors ohne Parameter;
  • initialisiert diese mit den Informationen aus der Anfrage, die denselben Namen (Groß-/Kleinschreibung spielt keine Rolle) wie eine der Eigenschaften von [ActionModel05] haben.

Manchmal ist dieses Verhalten nicht erwünscht. Dies ist insbesondere dann der Fall, wenn man einen bestimmten Konstruktor des Aktionsmodells verwenden möchte. In diesem Fall kann man wie folgt vorgehen:


    // Aktion 18
    public ContentResult Action18()
    {
      ActionModel05 modèle = new ActionModel05();
      TryUpdateModel(modèle);
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • Zeile 2: Die Aktion erhält keine Parameter mehr. Es findet also keine automatische Datenbindung mehr statt;
  • Zeile 4: Wir erstellen selbst eine Instanz des Aktionsmodells. Hier könnte man einen anderen Konstruktor verwenden;
  • Zeile 5: Wir initialisieren das Modell mit den Informationen aus der Anfrage. Diese Aufgabe übernimmt ASP.NET MVC. Dabei geht es genauso vor, wie wenn das Modell als Parameter übergeben worden wäre;
  • Zeile 6: Wir befinden uns nun in derselben Situation wie in der Aktion [Action12].

Hier ein Ausführungsbeispiel:

 

4.12. Conclusion

Kehren wir zur Architektur einer Anwendung ASP.NET MVC zurück:

Eine Anfrage [1] enthält verschiedene Informationen, die ASP.NET MVC der Aktion in Form eines Modells, das wir als Aktionsmodell bezeichnet haben, zur Verfügung stellt.

  • Die Anfrage HTTP des Kunden trifft in [1] ein;
  • in [2] werden die in der Anfrage enthaltenen Informationen in das Aktionsmodell [3] umgewandelt;
  • in [4] generiert die Aktion anhand dieses Modells eine Antwort. Diese besteht aus zwei Komponenten: einer Ansicht V [6] und dem Modell M dieser Ansicht [5];
  • Die Ansicht V [6] verwendet ihr Modell M [5], um die für den Kunden bestimmte Antwort HTTP zu generieren.

Im Modell MVC ist die Aktion [4] Teil des C (Controllers), das Modell der Ansicht [5] ist das M und die Ansicht [6] ist das V.

In diesem Kapitel wurden die Mechanismen der Verknüpfung zwischen den in der Anfrage übermittelten Informationen – bei denen es sich naturgemäß um Zeichenketten handelt – und dem Modell der Aktion untersucht, bei dem es sich um eine Klasse mit Eigenschaften verschiedener Typen handeln kann. Wir haben außerdem gesehen, dass es möglich ist, die Gültigkeit des der Aktion vorgelegten Modells zu überprüfen. Schließlich haben wir gesehen, wie dieses Modell auf die Daten mit den Bereichsnamen [Session] und [Application] erweitert werden kann.

Wir werden uns nun mit dem Ende der Verarbeitungskette der Anfrage [1] befassen: der Erstellung der Ansicht [6] und ihres Modells [5]. Diese beiden Elemente werden durch die Aktion [4] erzeugt.