Skip to content

3. Controller, Aktionen, Routing

Betrachten wir die Architektur einer ASP.NET- und MVC-Anwendung:

In diesem Kapitel betrachten wir den Prozess, der die Anfrage [1] zu dem Controller und der Aktion [2a] leitet, die sie verarbeiten werden – ein Mechanismus, der als Routing bezeichnet wird. Außerdem stellen wir die verschiedenen Antworten [3] vor, die eine Aktion an den Browser zurückgeben kann. Dabei kann es sich um etwas anderes als eine V-Ansicht [4b] handeln.

3.1. Die Struktur eines Projekts ASP.NET MVC

Erstellen wir ein erstes Projekt ASP.NET MVC mit Visual Studio Express 2012. Wir fügen es [1] der im vorherigen Kapitel verwendeten Lösung hinzu:

  • in [2], den Namen des neuen Projekts;
  • als [3, 4] wählen wir ein Basisprojekt ASP.NET MVC aus. Diese Vorlage stellt uns eine leere Webanwendung zur Verfügung, die jedoch alle Ressourcen (DLL, JavaScript-Bibliotheken usw.) enthält, die wir für die Arbeit benötigen.

Das resultierende Projekt wird in [5] dargestellt. Wir machen daraus [6] das Startprojekt der Lösung:

In [5] sind folgende Punkte zu beachten:

  • Die Projektarchitektur spiegelt das Modell wider:
  • Die C-Controller werden im Ordner [Controllers] abgelegt,
  • die M-Datenmodelle werden im Ordner [Models] abgelegt,
  • die V-Ansichten werden im Ordner [Views] abgelegt,
  • in [1] ist die Datei [Site.css] die Standarddatei der Anwendung;
  • in [2] stehen uns eine Reihe von JavaScript-Bibliotheken zur Verfügung;
  • In [3] finden sich drei spezielle Ansichten: _ViewStart, _Layout und Error.

Die Datei [_ViewStart] sieht wie folgt aus:


@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}
  • Zeile 1: Das Zeichen @ kündigt eine C#-Sequenz in der Ansicht an. Tatsächlich kann man C#-Code in eine Ansicht einbinden;
  • Zeile 2: Definiert eine Variable Layout, die die übergeordnete Ansicht aller Ansichten festlegt. Sie entspricht der Master-Seite des klassischen ASP.NET-Frameworks.

Die Datei „[_Layout]“ sieht wie folgt aus:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    @RenderBody()

    @Scripts.Render("~/bundles/jquery")
    @RenderSection("scripts", required: false)
</body>
</html>

Wenn eine Ansicht aus dem Ordner „[Views]“ gerendert wird, wird ihr Hauptteil durch die obige Zeile 11 gerendert. Das bedeutet, dass die Ansicht die Tags <html>, <head> und <body> nicht enthalten muss. Diese werden von der oben genannten Datei bereitgestellt. Diese ist derzeit noch recht undurchsichtig. Vereinfachen wir sie:


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  @RenderBody()
</body>
</html>
  • Zeile 6: der für alle Ansichten gemeinsame Titel;
  • Zeile 9: die für alle Ansichten gemeinsame Kopfzeile;
  • Zeile 10: der für die angezeigte Ansicht spezifische Inhalt.
  
  • [Web.config] ist die Konfigurationsdatei der Webanwendung. Sie ist komplex. Sie muss geändert werden, wenn das Framework [Spring.net] in einer mehrschichtigen Architektur verwendet wird.
  • [Global.asax] enthält den Code, der beim Start der Anwendung ausgeführt wird. Im Allgemeinen greift dieser Code auf die verschiedenen Konfigurationsdateien der Anwendung zurück, darunter [Web.config].

3.2. Das Standard-Routing von URL

Der Code von [Global.asax] lautet derzeit wie folgt:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace Exemple_01
{
 
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
   ...
    }
  }
}
  • Zeile 6, das namespace der Klasse. Es stammt direkt aus dem Projektnamen und ist in den Projekteigenschaften enthalten:

Image

Wir legen in [1] den Standard-Namensraum fest. Dieser wird dann standardmäßig für alle Klassen verwendet, die im Projekt erstellt werden.

Kehren wir zum Code von [Global.asax] zurück:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace Exemple_01
{
 
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
   ...
    }
  }
}
  • Zeile 9: Die Klasse [MvcApplication] leitet sich von der Klasse [HttpApplication] ab. Der Name [MvcApplication] kann geändert werden;
  • Zeile 11: Die Methode [Application_Start] ist die Methode, die beim Start der Webanwendung ausgeführt wird. Sie wird nur einmal ausgeführt. Hier wird die Anwendung initialisiert.

Der Code von [Application_Start] lautet derzeit wie folgt:


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

      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
}

Im Moment müssen wir diesen Code noch nicht vollständig verstehen. Die Zeilen 5–8 definieren die von der Webanwendung akzeptierten Routen. Kehren wir zur Architektur einer ASP.NET-MVC-Anwendung zurück:

Wir haben bereits erläutert, dass [Front Controller] eine URL an die Aktion weiterleiten muss, die für deren Verarbeitung zuständig ist. Eine Route dient dazu, eine Verbindung zwischen einem URL-Modell und einer Aktion herzustellen. Diese Routen werden im Ordner [App_Start] des Projekts durch die Klassen [WebApiConfig, FilterConfig, RouteConfig, BundleConfig] definiert:

 

Derzeit interessiert uns nur die Klasse [RouteConfig]:


using System.Web.Mvc;
using System.Web.Routing;

namespace Exemple_01
{
  public class RouteConfig
  {
    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

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

Die Zeilen 12–16 definieren das Format der von der Anwendung akzeptierten URL-Dateien. Dies wird als „Route“ bezeichnet. Es kann mehrere mögliche Routen geben (= mehrere mögliche URL-Vorlagen). Sie unterscheiden sich voneinander durch ihren Namen (Zeile 13). Das Format der URL der Route wird in Zeile 14 definiert. Hier besteht die URL aus drei Komponenten:

  • {controller}: der Name einer von [Controller] abgeleiteten Klasse. Diese wird im Ordner [Controllers] des Projekts gesucht. Gemäß Konvention gilt: Wenn die URL /X/Y/Z lautet, ist die Klasse XController für die Verarbeitung dieser URL zuständig. Das Suffix „Controller“ wird an den Namen des Controllers im URL angehängt;
  • {action}: der Name einer Methode in dem oben genannten Controller. Diese Methode empfängt die mit dem URL übermittelten Parameter und verarbeitet sie. Diese Methode kann verschiedene Ergebnisse zurückgeben:
    • void: Die Aktion erstellt selbst die Antwort an den Client-Browser
    • String: Die Aktion gibt eine Zeichenkette an den Client zurück;
    • ViewResult: gibt eine Ansicht an den Client zurück;
    • PartialViewResult: gibt eine Teilansicht zurück;
    • EmptyResult: Eine leere Antwort wird an den Client gesendet;
    • RedirectResult: fordert den Client auf, sich zu einer URL umzuleiten
    • RedirectToRouteResult: wie oben, jedoch wird die URL aus Routen der Anwendung aufgebaut;
    • JsonResult: sendet eine Antwort JSON
    • JavaScriptResult: gibt einen JavaScript-Code an den Client zurück;
    • ContentResult: sendet einen Datenstrom HTML an den Client, ohne eine Ansicht zu durchlaufen;
    • FileContentResult: sendet eine Datei an den Client zurück;
    • FileStreamResult: dasselbe, jedoch auf einem anderen Weg;
    • FilePathResult: …
  • {id}: Ein Parameter, der an die Aktion übergeben wird. Dazu muss die Aktion über einen Parameter namens „id“ verfügen.

Zeile 15 definiert Standardwerte für den Fall, dass die URL nicht die erwartete Form /{controller}/{action}/{id} hat. Sie gibt außerdem an, dass der Parameter {id} in der URL optional ist. Hier ist eine Liste unvollständiger URL sowie die mit den Standardwerten vervollständigte URL:

Ursprüngliches URL
Ergänzte URL
/
/Home/Index
/Do
/Do/Index
/Do/Something
/Do/Something
/Do/Something/4
/Do/Something/4
/Do/Something/x/y/z
URL nicht geroutet

3.3. Erstellen eines Controllers und einer ersten Aktion

Erstellen wir einen ersten Controller:

 
  • Geben Sie in [1] den Namen des Controllers mit dem Suffix [Controller] an;
  • in [2] erstellen Sie einen leeren Controller MVC;
  • in [3] wurde er erstellt.

Der Code für [FirstController] lautet wie folgt:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Exemple_01.Controllers
{
    public class FirstController : Controller
    {
        //
        // GET: /First/

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

    }
}
  • Zeile 7: Ein Standard-namespace wurde generiert;
  • Zeile 9: Die Klasse [FirstController] leitet sich von der Klasse [System.Web.Mvc.Controller] ab;
  • Zeilen 14–17: Standardmäßig wurde eine Aktion [Index] generiert. Sie ist öffentlich. Das ist wichtig, da sie sonst nicht gefunden wird. Sie gibt einen Typ [ActionResult] zurück, bei dem es sich um eine abstrakte Klasse handelt, von der die meisten Ergebnisse abgeleitet sind, die eine Aktion üblicherweise zurückgibt. Es handelt sich um einen „Sammeltyp“, den man präzisieren kann, indem man ihn durch den tatsächlichen Namen des zurückgegebenen Typs ersetzt;
  • Zeile 16: Die Methode führt keine Aktion aus. Sie gibt lediglich eine Ansicht zurück, also einen Typ [ViewResult]. Der Name der Ansicht ist nicht angegeben. In diesem Fall sucht das Framework im Ordner „[Views / First]“ nach einer Ansicht mit dem Namen der Aktion, also hier: „[Index.cshtml]“.

Erstellen wir die Ansicht [Index.cshtml]:

  • In [1] klicken wir mit der rechten Maustaste in den Aktionscode und wählen die Option [Ajouter une vue];
  • Bei [2] schlägt der Assistent eine Ansicht mit dem Namen der Aktion vor. Das ist hier genau das, was wir wollen;
  • Bei [3] wird standardmäßig die Verwendung der Masterseite [_Layout.cshtml] vorgeschlagen;
  • Bei [4] erstellt der Assistent nach der Bestätigung die Ansicht in einem Unterordner des Ordners [Views], der den Namen des Controllers (First) trägt.

Der für die Ansicht [Index] generierte Code lautet wie folgt:


@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>
  • Zeilen 1–3: C#-Code, der eine Variable definiert;
  • Zeile 5: ein Tag HTML.

Der gesamte vorherige Code wird durch folgenden ersetzt:


<strong>Vue [Index]...</strong>

Fassen wir zusammen:

  • Wir haben einen C-Controller namens [First];
  • wir haben eine Aktion namens [Index], die die Anzeige einer Ansicht namens [Index] anfordert;
  • wir haben die Ansicht V [Index].

Wir können die Aktion [Index] auf zwei Arten aufrufen:

  • /First/Index;
  • /First, da [Index] auch die Standardaktion in den Routen ist.

Führen wir die Anwendung (CTRL-F5) aus. Wir erhalten die folgende Seite:

1

Image

Bei [1] lautete die angeforderte Aktion http://localhost:49302. Es gibt keinen Pfad. Wir wissen, dass unser Router Aktionen in der Form /{controller}/{action}/{id} erwartet. Da diese Elemente fehlen, werden die Standardwerte verwendet. Die URL URL wird zu http://localhost:49302/Home/Index. Der Controller [Home] existiert nicht. Daher wird die URL URL abgelehnt.

Versuchen wir nun, die URL http://localhost:49302/First/Index zu testen, indem wir sie direkt in den Browser eingeben:

Die obige Seite wurde durch die Aktion [Index] des Controllers [First] generiert. Die durch diese Aktion generierte Seite ist die Ansicht [Index], deren Code wie folgt lautete:


<strong>Vue [Index]...</strong>

Sie generiert den Teil [1]. Der Teil [2] stammt hingegen von der Master-Seite [_Layout], die wir zuvor definiert haben:


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  @RenderBody()
</body>
</html>

Zeile 9 hat den Teil [2] der Seite generiert. Die Ansicht [Index] kommt hingegen erst in Zeile 10 zum Einsatz.

Wenn man den Quellcode der empfangenen Seite anzeigt, findet man tatsächlich die Seite [Index] (Zeile 10 unten), die in die Seite [Layout] eingebunden ist:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  <strong>Vue [Index]...</strong>
</body>
</html>

Versuchen wir es nun mit URL und [/First]:

  

Die Datei „URL“ und „[/First]“ waren unvollständig. Sie wurden mit den Standardwerten der Route vervollständigt und zu „[/First/Index]“ umbenannt. Wir erhalten somit das gleiche Ergebnis wie zuvor.

3.4. Aktion mit einem Ergebnis vom Typ [ContentResult] – 1

Erstellen wir eine neue Aktion im Controller [First]:


using System.Text;
using System.Web.Mvc;

namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
    // Index
    public ViewResult Index()
    {
      return View();
    }
    // Aktion01
    public ContentResult Action01()
    {
      return Content("<h1>Action [Action01]</h1>", "text/plain", Encoding.UTF8);
    }
  }
}

Die neue Aktion ist in den Zeilen 14–18 definiert. Sie gibt lediglich eine Zeichenkette mithilfe der Methode [Content] (Zeile 16) der Klasse [Controller] (Zeile 6) zurück. Die Parameter der Methode sind:

  1. die Zeichenkette der Antwort;
  2. ein Kennzeichen für die Art des gesendeten Textes: „text/plain“, „text/html“, „text/xml“, … Dieser Indikator wird als Typ MIME (http://fr.wikipedia.org/wiki/Type_MIME) bezeichnet;
  3. Der dritte Parameter dient zur Angabe der für den Text verwendeten Kodierung.

Anstatt den abstrakten Typ [ActionResult] zu verwenden, geben unsere Methoden den tatsächlich gerenderten Typ an (Zeilen 9 und 14).

Rufen wir URL [/First/Action01] auf. Wir erhalten die folgende Seite:

  

Sehen wir uns den Quellcode der erhaltenen Seite an:

<h1>Action [Action01]</h1>

Der Browser hat nur den von der Aktion gesendeten Text empfangen und sonst nichts. Dieser Modus ist interessant, wenn man vom Webserver reine Daten ohne die umgebende HTML-Struktur anfordern möchte. Es ist oben zu beachten, dass der Browser das Tag <h1> nicht interpretiert hat. Um zu verstehen, warum das so ist, schauen wir uns in Chrome den Datenaustausch an:

Der Browser hat die folgenden Header gesendet:

1
2
3
4
5
6
7
8
GET /First/Action01 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0,8
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4

Der Server hat mit den folgenden Headern geantwortet:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMQ==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:22:33 GMT
Content-Length: 141
  • Zeile 3: Legt die Art des Dokuments fest. Hier finden wir die in der Methode [Action01] festgelegten Attribute wieder. Da dem Browser mitgeteilt wurde, dass das Dokument vom Typ „text/plain“ und nicht „text/html“ sei, hat er das im empfangenen Dokument enthaltene h1-Tag nicht interpretiert.

3.5. Aktion mit einem Ergebnis vom Typ [ContentResult] – 2

Betrachten wir nun die folgende dritte Aktion:


using System.Text;
using System.Web.Mvc;

namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
   ...
    // Aktion02
    public ContentResult Action02()
    {
      string data = "<action><name>Action02</name><description>renvoie un texte XML</description></action>";
      return Content(data, "text/xml", Encoding.UTF8);
    }
  }
}
  • Zeile 12: Es wird ein Text vom Typ XML definiert;
  • Zeile 13: Dieser wird an den Browser gesendet, wobei angegeben wird, dass es sich um XML mit dem Typ MIME „text/xml“ handelt.

Im Browser wird folgende Seite angezeigt:

 

Sehen wir uns in Chrome die Antwort HTTP des Servers an:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMg==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:29:34 GMT
Content-Length: 176
  • Zeile 3: Legt die Art des Dokuments fest. Hier finden sich die in der Methode [Action02] festgelegten Attribute;
  • Zeile 12: Die Größe des vom Server gesendeten Dokuments in Byte.

Das vom Server gesendete Dokument lautet wie folgt (Kopie aus Chrome):

 

3.6. Aktion mit einem Ergebnis vom Typ [JsonResult]

Fügen wir dem Controller [First] die folgende Aktion hinzu:


    // Aktion03
    public JsonResult Action03()
    {
      dynamic personne = new ExpandoObject();
      personne.nom = "someone";
      personne.age = 20;
      return Json(personne,JsonRequestBehavior.AllowGet);
}
  • Zeile 4: eine Variable vom Typ dynamic. Bei der Ausführung können einer solchen Variable beliebig Eigenschaften hinzugefügt werden. Die Eigenschaft wird gleichzeitig mit ihrer Initialisierung erstellt;
  • Zeilen 5–6: Es werden zwei Eigenschaften initialisiert: nom und age;
  • Zeile 7: Die Darstellung JSON (JavaScript Object Notation) des Objekts wird zurückgegeben. Mit JSON lässt sich ein Objekt in eine Zeichenkette serialisieren und umgekehrt eine Zeichenkette in ein Objekt deserialisieren. Dies ist eine Alternative zur Serialisierung/Deserialisierung mit XML;
  • Zeile 2: Die Aktion gibt einen Typ [JsonResult] zurück. Dieser Typ kann nur bei einer Anfrage vom Typ POST zurückgegeben werden. Wenn man ihn für eine Methode GET zurückgeben möchte, muss man dem zweiten Parameter des Konstruktors der Klasse Json (Zeile 7) den Wert JsonRequestBehavior.AllowGet übergeben.

Wenn man die Anfrage URL [/First/Action03] stellt, zeigt der Browser Folgendes an:

 

Die Antwort des Servers lautet hingegen wie folgt:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMw==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:48:53 GMT
Content-Length: 58
  • Zeile 3: gibt an, dass das gesendete Dokument JSON ist;
  • Zeile 10: Das gesendete Dokument ist 58 Byte groß. Es lautet wie folgt:
[{"Key":"nom","Value":"someone"},{"Key":"age","Value":20}]

Das dynamische Element [personne] wird von JSON als ein Array von Dictionaries angesehen, wobei jedes Dictionary:

  • einem Feld der Variablen [personne] entspricht;
  • zwei Schlüssel „Key“ und „Value“ hat. „Key“ ist mit dem Namen des Feldes verknüpft und „Value“ mit dem Wert des Feldes.

3.7. Aktion mit einem Ergebnis vom Typ [string]

Fügen wir dem Controller [First] die folgende Aktion hinzu:


    // Aktion04
    public string Action04()
    {
      return "<h3>Contrôleur=First, Action=Action04</h3>";
}

Wenn wir die Seite URL [/First/Action04] mit Chrome aufrufen, erhalten wir folgende Antwort:

 

Man sieht, dass das Tag <h3> interpretiert wurde. Schauen wir uns die Antwort HTTP des Servers an:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNA==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 07:49:00 GMT
Content-Length: 156

und das folgende Dokument:

<h3>Contrôleur=First, Action=Action04</h3>

In Zeile 3 ist zu sehen, dass der Server angegeben hat, Text im Format HTML zu senden. Deshalb hat der Browser das Tag <h3> interpretiert. Wenn man reinen Text senden möchte, ist es daher besser, ein [ContentResult] statt eines [string] zurückzugeben. Mit [ContentResult] können wir nämlich den Typ „text/plain“ angeben, um zu signalisieren, dass wir unformatierten Text senden, der somit vom Browser nicht interpretiert werden kann.

3.8. Aktion mit einem Ergebnis vom Typ [EmptyResult]

Nehmen wir folgende neue Aktion:


    // Aktion05
    public EmptyResult Action05()
    {
      return new EmptyResult();
}

Die Aktion gibt lediglich den Typ [EmptyResult] zurück. In diesem Fall sendet der Server eine leere Antwort an den Client, wie aus dessen Antwort HTTP hervorgeht:

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Cache-Control: private
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNQ==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:11:12 GMT
Content-Length: 0
  • Zeile 9: Der Server teilt seinem Client mit, dass er ihm ein leeres Dokument sendet.

3.9. Aktion mit einem Ergebnis vom Typ [RedirectResult] – 1

Nehmen wir folgende neue Aktion:


    // Aktion06
    public RedirectResult Action06()
    {
      return new RedirectResult("/First/Action05");
}

Die Aktion gibt den Typ [RedirectResult] zurück. Dieser Typ ermöglicht es, dem Client einen Umleitungsbefehl zum Konstruktorparameter URL (Zeile 4) zu senden. Der Client sendet daraufhin eine neue Anfrage GET an [/First/Action05]. Der Client stellt somit insgesamt zwei Anfragen.

Der Browser zeigt das Ergebnis der zweiten Anfrage an:

 

Sehen wir uns die Antwort HTTP des Servers in Chrome an:

 

Oben sind die beiden Anfragen des Browsers zu sehen. Sehen wir uns die erste Anfrage [Action06] an. Die Antwort HTTP des Servers lautet wie folgt:

HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /First/Action05
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNg==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:16:59 GMT
Content-Length: 132
  • Zeile 1: Der Server hat mit dem Statuscode 302 Found geantwortet. Zuvor lautete der Statuscode 200 OK, was bedeutet, dass das angeforderte Dokument gefunden wurde. Der Code 302 zeigt an, dass eine Weiterleitung angefordert wird. Die Weiterleitungsadresse ist in Zeile 4 angegeben. Dort finden wir die Weiterleitungsadresse, die wir im Aktionscode angegeben hatten;
  • Zeile 11: Der Server gibt an, dass er mit seiner Antwort „HTTP“ ein Dokument vom Typ „text/html“ (Zeile 3) mit einer Größe von 132 Byte (Zeile 11) sendet. Wenn man in Chrome die Antwort auf die Anfrage [Action06] untersucht, ist sie erwartungsgemäß leer. Es gibt wahrscheinlich eine Erklärung dafür, aber ich kenne sie nicht.

Aufgrund der Weiterleitung sendet der Browser eine neue Anfrage GET an die oben in Zeile 4 angegebene Adresse URL, wie in Chrome in Zeile 1 unten zu sehen ist:

1
2
3
4
5
6
7
GET /First/Action05 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0,8
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4

3.10. Aktion mit einem Ergebnis vom Typ [RedirectResult] – 2

Nehmen wir folgende neue Aktion:


    // Aktion07
    public RedirectResult Action07()
    {
      return new RedirectResult("/First/Action05",true);
}

In Zeile 4 wurde dem Konstruktor vom Typ [RedirectResult] ein zweiter Parameter hinzugefügt. Es handelt sich um einen booleschen Wert, der standardmäßig auf false gesetzt ist. Wenn man ihn auf true setzt, ändert sich die an den Client gesendete Antwort HTTP. Sie lautet dann:

HTTP/1.1 301 Moved Permanently

Somit lautet der an den Client gesendete Antwortcode nun 301 Moved Permanently. Die Weiterleitung erfolgt wie zuvor, es wird jedoch angegeben, dass diese Weiterleitung dauerhaft ist. Dadurch können Suchmaschinen in ihren Ergebnissen den alten Code URL durch den neuen ersetzen.

3.11. Aktion mit einem Ergebnis vom Typ [RedirectToRouteResult]

Nehmen wir folgende neue Aktion an:


    // Aktion08
    public RedirectToRouteResult Action08()
    {
      return new RedirectToRouteResult("Default",new RouteValueDictionary(new {controller="First",action="Action05"}));
}
  • Zeile 2: Die Aktion gibt einen Typ [RedirectToRouteResult] zurück. Dieser Typ ermöglicht es, einen Client zu einer URL umzuleiten, die nicht wie zuvor durch eine Zeichenkette, sondern durch eine Route angegeben wird.

Die Routen sind in [App_Start/RouteConfig] definiert. Derzeit gibt es nur eine:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
  • In Zeile 4 wird der Client angewiesen, sich mit der Variablen „controller=First“ und der Variablen „action=Action05“ an die Route mit dem Namen [Default] weiterzuleiten. Das Routing-System generiert daraufhin die Weiterleitung URL von /First/Action05. Dies geht aus der Antwort HTTP des Servers hervor:
HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /First/Action05
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wOA==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:58:19 GMT
Content-Length: 132
  • Zeile 1: die Weiterleitung;
  • Zeile 4: Die vom Routing-System von URL generierte Umleitungsadresse.

3.12. Aktion mit einem Ergebnis vom Typ [void]

Nehmen wir folgende neue Aktion:


    // Aktion09
    public void Action09()
    {
      string nom = Request.QueryString["nom"] ?? "inconnu";
      Response.AddHeader("Content-Type", "text/plain");
      Response.Write(string.Format("<h3>Action09</h3>nom={0}", nom));
}
  • Zeile 2: Die Aktion liefert kein Ergebnis. Sie schreibt selbst in den an den Client gesendeten Antwort-Stream;
  • Zeile 4: Ein eventueller Parameter namens [nom] wird aus der Anfrage abgerufen. Dieser ist über die Eigenschaft [Request] des Controllers [Controller] zugänglich, von dem der Controller [First] erbt. Der Parameter [nom], der in der Form [/First/Action09?nom=quelquechose] übergeben wird, ist in Request.QueryString["nom"] verfügbar. Die Syntax von Zeile 4 entspricht folgendem:
string nom=Request.QueryString["nom"];
if(nom==null){
    nom="inconnu";
}
  • Zeile 5: Die an den Client gesendete Antwort ist über die Eigenschaft [Response] des Controllers [Controller] zugänglich, von dem der Controller [First] erbt;
  • Zeile 5: Es wird der Header HTTP [Content-Type] festgelegt, der die Art des Dokuments angibt, das der Server an den Client senden wird. Hier bedeutet „text/plain“, dass es sich um reinen Text handelt, der vom Browser nicht interpretiert werden soll;
  • Zeile 6: In den Antwortstrom wird eine Zeichenkette geschrieben. Darin sind Tags HTML enthalten, die vom Browser nicht interpretiert werden dürfen, da dieser zuvor den Header HTTP [Content-Type : text/plain"] erhalten hat. Genau das wollen wir überprüfen.

Kompilieren wir das Projekt und rufen wir zunächst die URL [/First/Action09?nom=someone ][1] und anschließend dieURL [/First/Action09 ] [2]:

Sehen wir uns nun in Chrome die Antwort HTTP vom Server an:

1
2
3
4
5
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
...
Content-Length: 144
  • Zeile 3: Hier finden wir den Header HTTP wieder, den wir selbst im Code der Aktion festgelegt hatten.

3.13. Ein zweiter Controller

Erstellen wir im Projekt einen zweiten Controller. Dabei gehen wir nach der in Abschnitt 3.1, Seite 40, beschriebenen Vorgehensweise vor. Wir nennen ihn [Second].

  

Der generierte Code lautet wie folgt:


namespace Exemple_01.Controllers
{
  public class SecondController : Controller
  {
    //
    // GET: /Sekunde/

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

  }
}

Ändern wir ihn wie folgt:


using System.Text;
using System.Web.Mvc;

namespace Exemple_01.Controllers
{
  public class SecondController : Controller
  {
    // /Sekunde/Aktion01
    public ContentResult Action01()
    {
      return Content("Contrôleur=Second, Action=Action01", "text/plain", Encoding.UTF8);
    }

  }
}

Rufen wir dann den Code URL [/Second/Action01] mit einem Browser auf. Wir erhalten folgende Antwort:

 

Dieser URL wurde mit einem Befehl HTTP GET angefordert, wie die Protokolle HTTP der Anfrage in Chrome zeigen:

GET /Second/Action01 HTTP/1.1

URL kann ebenfalls mit den Befehlen HTTP und POST aufgerufen werden. Um dies zu veranschaulichen, verwenden wir erneut die Anwendung [Advanced Rest Client]:

  • In [1] starten wir die Anwendung (im Tab [Applications] eines neuen Chrome-Tabs);
  • unter [2] wählen wir die Option [Request] aus;
  • unter [3] gibt man die gewünschte URL an;
  • in [4] wird angegeben, dass die URL zusammen mit einer POST angefordert werden soll;

Die Chrome-Protokolle werden über (CTRL-I) aktiviert, um die Antwort HTTP vom Server zu erhalten. Wenn man [Send] als Folge der vorherigen Anfrage ausführt, sieht der Datenaustausch mit HTTP wie folgt aus:

Der Browser sendet die folgende Anfrage:

POST /Second/Action01 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Content-Length: 0
Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
  • Zeile 1: URL wird tatsächlich mit einem POST angefordert;
  • Zeile 4: Die Größe der gesendeten Elemente in Byte. Hier gibt es keine.

Die Antwort HTTP des Servers lautet wie folgt:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxTZWNvbmRcQWN0aW9uMDE=?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 10:47:59 GMT
Content-Length: 148
  • Zeile 3: Der Server sendet ein unformatiertes Textdokument (plain);
  • Zeile 12: mit 148 Zeichen.

Das gesendete Dokument lautet wie folgt:

 

Man erhält dasselbe Dokument wie bei GET.

3.14. Nach einem Attribut gefilterte Aktion

Erstellen wir die folgende neue Aktion:


    // /Sekunde/Aktion02
    [HttpPost]
    public ContentResult Action02()
    {
      return Content("Contrôleur=Second, Action=Action02", "text/plain", Encoding.UTF8);
}

Die Aktion [Action02] entspricht der Aktion [Action01], es wird jedoch festgelegt, dass sie nur über den Befehl HTTP POST (Zeile 2) aufgerufen werden kann. Es können weitere Attribute verwendet werden:

HttpGet
dient ausschließlich dem Befehl GET
HttpHead
gilt nur für den Befehl HEAD
HttpOptions
gilt nur für den Auftrag OPTIONS
HttpPut
gilt nur für den Auftrag PUT
HttpDelete
dient ausschließlich dem Befehl DELETE

Rufen wir URL [/Second/Action02] direkt im Browser auf. Es wird dann ein GET angefordert. Der Browser zeigt daraufhin folgende Antwort an:

 

Die Antwort HTTP des Servers lautete wie folgt:

1
2
3
HTTP/1.1 404 Not Found
...
Content-Length: 3807
  • Zeile 1: Der Code HTTP 404 Not Found zeigt an, dass der Server das angeforderte Dokument nicht gefunden hat. Hier konnte die Aktion [Action02] die Anfrage GET nicht bedienen, da sie nur die Befehle POST bedient;
  • Zeile 3: Die Größe des zurückgegebenen Dokuments. Dies ist die Seite, die vom Browser angezeigt wurde:
 

3.15. Elemente einer Route abrufen

In den beiden zuvor beschriebenen Aktionen wurde etwa Folgendes geschrieben:


public ContentResult Action02()
    {
      return Content("Contrôleur=Second, Action=Action02", "text/plain", Encoding.UTF8);
}

Die Namen des Controllers und der Aktion waren fest im Code hinterlegt. Ändert man diese Namen, ist der Code nicht mehr korrekt. Man kann wie folgt auf den Controller und die Aktion zugreifen:


    // /Sekunde/Aktion03
    public ContentResult Action03()
    {
      string texte = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Die in [App_Start/RouteConfig] definierte Route lautet wie folgt:


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

Zeile 3: Die drei Elemente der Route lassen sich über RouteData.Values["élément"] mit dem Element in [controller, action, id] abrufen.

Rufen wir nun URL und [http://localhost:49302/Second/Action03] auf:

 

Wir haben sowohl den Namen des Controllers als auch den Namen der Aktion erfolgreich abgerufen.