Steuerung
Steuerungen (oder auch Controller genannt) sind der Grundbaustein für MVC-Anwendungen und stehen nach dem Erhalt einer
HTTP-Anfrage an erster Stelle. Der für die URL zugehörige Controller sowie die für die URL zugehörige Anwendung wird
mittels eines Routing-Verfahrens vom MVC-Framework ermittelt, welches wir im nächsten Abschnitt genauer erklären werden.
Die Steuerung repräsentiert die sogenannte Anwendungslogik. Jede öffentliche Funktion (Zugriffsmodifizierer public
)
stellt eine sogenannte Aktionsmethode dar. Eine Aktionsmethode kann in der Regel von außen indirekt über die angegebene URL
aufgerufen werden. Als Rückgabetyp verfügen Aktionsmethoden üblicherweise über die Klasse ActionResult
(bzw. über
eine von der Klasse ActionResult
abgeleitete Klasse). Aktionsmethoden können dabei Übergabeparameter besitzen.
Dies können zum einen individuelle Parameter (festgelegt durch Routen) und zum anderen ein Datenmodell sein (bei POST-Aktionen).
Darauf werden wir jedoch später noch genauer eingehen.
Prinzipiell sind Steuerungen nichts anderes als C#-Klassen. Jedoch sind diese von der Klasse Controller
(Namensraum
System.Web.Mvc
) abgeleitet und müssen am Ende des Klassennamens über den Suffix Controller
verfügen.
Der andere Teil des Klassennamens stellt den eigentlichen Controller-Namen dar. Der Name der Aktionsmethode entspricht,
sofern dieser nicht über den ActionName
-Selektor (dazu später mehr) geändert wurde, auch dem Namen, welcher in der
URL zum Aufruf der Aktion angegeben wird.
Routing
Wie bereits erklärt, werden die URLs mittels eines Routing-Verfahrens zu allererst einer Anwendung, anschließend einer Steuerung
und letztendlich einer Aktionsmethode zugeordnet. Dabei gilt für die URL innerhalb einer MVC-Anwendung folgender Aufbau:
{controller}/{action}/{id}
. Ein Aufruf von Home/Index/1
wird also dem Controller Home
und
der Aktionsmethode Index()
zugewiesen. Dieser kann (sofern gewünscht) ein Parameter übergeben werden, welcher im obigen
Beispiel dem Wert 1
entsprechen würde. Ein Aufruf von Kunden/Editierung/4781
würde die Aktionsmethode
Editierung()
des Controllers Kunden
(Datei KundenController.cs
) aufrufen und der Methode den
Wert 4781
übergeben. Dies ist wohl ein typisches Beispiel für ASP.NET MVC, wie die URL für die Editierung eines Kunden
in einem Firmen-Portal aussehen könnte.
Möchten wir diesen Standardaufbau ändern oder erweitern, so müssen wir weitere Routen registrieren oder vorhandene anpassen.
Eine Route ist beim Erstellen eines MVC-Projekts bereits vorhanden. Diese, welche wir oben bereits erklärt haben, wird auch als
Standard-Route (engl. default route) bezeichnet. Routen werden auf Anwendungsebene definiert und sind standardmäßig
in der Funktion RegisterRoutes()
der Datei App_Start/RouteConfig.cs
definiert. Der Funktion wird als
Parameter ein Objekt der Klasse RouteCollection
übergeben. In diesem werden die Routen registriert.
Innerhalb der Funktion RegisterRoutes()
wird nun die Funktion MapRoute()
aufgerufen, mit welcher
URL-Routen festgelegt werden können. Als Parameter werden der Routenname, das URL-Muster und die Standard-Werte übergeben.
Der Routenname kann zur Identifizierung der Route und für Routenlinks verwendet werden. Der Routenname wird in der
Routing-Tabelle als Index verwendet, weshalb der Name eindeutig sein muss. Das URL-Muster für Routen besteht aus Platzhaltern,
Schrägstrichen (zur Trennung) sowie bei Bedarf Konstanten. Zwischen Platzhaltern ist es zwingend erforderlich, dass ein Schrägstrich
vorhanden ist. Platzhalter werden in geschweifte Klammern notiert. Die einfachsten Beispiele sind die in der Standard-Route enthaltenen
Platzhalter {controller}
und {action}
. {controller}
verweist dabei direkt auf den Namen der
Steuerung und {action}
auf den Namen der Aktionsmethode. Weitere Platzhalter können nach Belieben definiert werden.
Der Wert solcher Platzhalter werden ebenfalls über die eingegebene URL spezifiziert und der Aktionsmethode als Parameter übergeben.
Die Zuordnung des Platzhalterwerts zu dem Parameter wird über den Namen getroffen, d. h. der Name des Platzhalters muss auch dem
Parameternamen der Aktionsmethode entsprechen. Über den dritten Parameter der Funktion MapRoute()
werden die
Standardwerte der Platzhalter festgelegt. Dafür wird in der Regel ein anonymes Objekt erzeugt und dieses der Funktion
übergeben. Das (anonyme) Objekt enthält Eigenschaften, die die Standardwerte der Platzhalter repräsentieren. Hierfür muss der Name
der Eigenschaft dem Namen des Platzhalters entsprechen. Die Standardwerte (engl. default values) werden immer dann benötigt,
wenn einer der Werte nicht angegeben wurde. Dies ist auch der Grund, warum beim Aufruf des Root-Verzeichnisses der Anwendung
standardmäßig die Steuerung Home
und die Aktionsmethode Index
aufgerufen wird. Existiert z. B. ein
weiterer Controller mit dem Namen Produkt
, so wird beim Aufruf von ~/Produkt
die Aktionsmethode
Index()
des Produkt
-Controllers aufgerufen. Möchten Sie einen Platzhalter nicht mit einem Standard-Wert
belegen, sondern diesen als optional markieren, so können Sie der Eigenschaft des Objekts für Standardwerte die Konstante
UrlParameter.Optional
zuweisen. Wird der Platzhalter in der URL nicht angegeben, so wird der Aktionsmethode als
Wert null
übergeben. Hierbei ist darauf zu achten, dass der Datentyp den Wert null
unterstützt. Die
Funktion IgnoreRoute()
erlaubt es mit Hilfe eines URL-Musters, bestimmte Routen zu ignorieren, um somit z. B.
einen Controller „zu deaktivieren“.
Im unteren Beispiel gibt es zwei Controller: Home
und Info
. Beide Controller verfügen über mehrere
Aktionsmethoden. Die Aktionsmethode MeineID()
des Controllers Home
verwendet den Platzhalter
bzw. Parameter id
und gibt dessen Wert aus. In der Routenkonfiguration wurde eine Route hinzugefügt, mit welcher
die Aktionsmethode auch über die URL ~/ID
aufgerufen werden kann. Die folgende Tabelle zeigt die verschiedenen
Aufrufmöglichkeiten für die Webanwendung und die dazugehörigen Controller sowie Aktionsmethoden:
URL | Controller | Aktionsmethode | id-Parameter |
---|---|---|---|
~/ | Home | Index() | null |
~/Home | Home | Index() | null |
~/Home/Index | Home | Index() | null |
~/Home/DatumUhrzeit | Home | DatumUhrzeit() | null |
~/Home/MeineID | Home | MeineID() | null |
~/Home/MeineID/123 | Home | MeineID() | 123 |
~/Info/Anfrage | Info | Anfrage() | null |
~/Info/Antwort | Info | Antwort() | null |
~/ID | Home | MeineID() | null |
~/ID/123 | Home | MeineID() | 123 |
Übrigens: Ein Aufruf von ~/Info
ist ungültig (bzw. führt zu einem 404 HTTP-Status-Fehler), da der
Info
-Controller über keine Index()
-Aktionsmethode verfügt.
Hier der Quellcode der zwei Controller sowie der Routenkonfiguration:
HomeController.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace HWhBsp.Routing.Controllers { public class HomeController : Controller { public string Index() { return "Hallo Welt!"; } public string DatumUhrzeit() { return "Aktuelles Datum und Uhrzeit: " + DateTime.Now.ToString(); } public string MeineID(string id) { return "ID: " + ((id == null) ? "-" : id); } } }
InfoController.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace HWhBsp.Routing.Controllers { public class InfoController : Controller { public string Anfrage() { return "User-Agent: " + Request.UserAgent; } public string Antwort() { return "Zeichenkodierung: " + Response.Charset; } } }
RouteConfig.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace HWhBsp.Routing { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "ID-Ausgabe", url: "ID/{id}", defaults: new { controller = "Home", action = "MeineID", id = UrlParameter.Optional } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
Wichtig: Die Routen werden von oben nach unten abgearbeitet. Daher muss die Standard-Route immer als letztes notiert werden.
Übrigens: Bei der Angabe von Parametern kann zusätzlich zum Wert auch noch der Name angegeben werden (name: wert
).
Dies wird gemacht, sodass der Programmierer direkt erkennen kann, welcher Wert zu welchem Parameter gehört (ohne die
Parameter-Reihenfolge zu kennen). Diese „Beschriftung“ kann jedoch ohne weiteres weggelassen werden. Es gibt zudem einige
Funktionen, welche „Benannte Argumente“ verwenden. Bei diesen muss der Parametername angegeben werden, da darüber die Zuordnung
stattfindet. Für Sie mag diese Art der Parameterangabe evtl. neu sein, jedoch werden Sie sehen, dass diese beiden Verfahren
oft bei ASP.NET MVC verwendet werden.
Stellen wir uns ein praxisnahes Szenario vor: Sie möchten auf Grund von Suchmaschinenoptimierung der URL zusätzlich zum
Namen des Controllers und der Aktionsmethode ein Sprachkürzel hinzufügen. So soll es z. B. an Stelle der URL
~/Produkte/Liste
die URLs ~/DE/Produkte/Liste
und ~/EN/Produkte/Liste
geben. Das
URL-Muster könnte in einem solchen Fall z. B. so aussehen: {lang}/{controller}/{action}/{id}
. Der Parameter
lang
wird dabei den Aktionsmethoden übergeben, wodurch dieser den Inhalt der jeweiligen Seite laden kann.
Hier zwei Beispielcodeausschnitte:
public string Index(string lang) { return "Ihre Sprache ist: " + lang; }
routes.MapRoute( name: "Default", url: "{lang}/{controller}/{action}/{id}", defaults: new { lang = "DE", controller = "Home", action = "Index", id = UrlParameter.Optional } );
Wichtig: Parameter am Anfang oder in der Mitte einer URL müssen angegeben werden. Im obigen Beispiel kann der Parameter
lang
also nicht weggelassen werden. Grundsätzlich können Parameter also immer nur in umgekehrter
Reihenfolge und somit vom Ende zum Anfang weggelassen werden.
Aktionen
Aktionsmethoden können unterschiedliche Rückgabetypen haben. Bisher haben wir der Einfachheit halber den Rückgabetyp
string
verwendet. Im Regelfall wird jedoch der Rückgabetyp ActionResult
verwendet. Die Klasse
ActionResult
ist eine abstrakte Klasse, d. h. von dieser kann kein Objekt erzeugt werden. Jedoch gibt es einige
Klassen, welche von der Klasse ActionResult
abgeleitet sind. Trotzdem kann als Rückgabetyp stets ActionResult
angegeben werden. Dies ermöglicht z. B. das Zurückgeben von einem Objekt der Klasse RedirectResult
oder
ContentResult
innerhalb der gleichen Aktionsmethode. Um ein Objekt solcher Klassen zu erzeugen, können oft
Hilfsfunktionen eingesetzt werden. Die folgende Tabelle zeigt eine Auflistung der wichtigsten Klassen, welche von
ActionResult
abgeleitet sind, sowie der Hilfsfunktionen und einer Beschreibung:
Klasse | Hilfsfunktion | Beschreibung |
---|---|---|
ContentResult | Content() | Gibt einen Inhalt (ggf. mit einem angegebenen Inhaltstyp) zurück. |
FileResult | File() | Gibt den Inhalt der angegebenen Datei (ggf. mit dem angegebenen Inhaltstyp) zurück. |
HttpNotFoundResult | - | Gibt einen 404 HTTP-Status-Fehler bzw. eine Umleitung zur dazugehörigen Fehlerseite zurück. |
HttpStatusCodeResult | - | Gibt einen beliebigen HTTP-Status-Fehler bzw. eine Umleitung zur dazugehörigen Fehlerseite zurück. |
HttpUnauthorizedResult | - | Gibt einen 403 HTTP-Status-Fehler bzw. eine Umleitung zur dazugehörigen Fehlerseite zurück. |
RedirectResult | Redirect() | Gibt eine Umleitung an Hand einer URL zurück. |
RedirectToRouteResult | RedirectToAction() | Gibt eine Umleitung an Hand eines Controllers und einer Aktion zurück. |
RedirectToRoute() | Gibt eine Umleitung an Hand einer Route (Angabe aller Platzhalter möglich) zurück. | |
ViewResult | View() | Gibt eine Ansicht zurück (dazu später mehr). |
Im Folgenden sehen Sie ein Beispiel mit mehreren Aktionsmethoden. Zur klaren Darstellung wurde als Rückgabetyp explizit die
jeweils verwendete Klasse angegeben und nicht, wie es üblich ist, die Klasse ActionResult
.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace HWhBsp.Aktionen.Controllers { public class HomeController : Controller { public string Index() { return "Hallo Welt!"; } public RedirectResult UmleitungStartseite() { return Redirect("~/"); } public RedirectToRouteResult UmleitungRoute() { return RedirectToAction("InhaltHTML"); } public ContentResult InhaltHTML() { return Content("<b>Hallo</b> Welt!", "text/html"); } public ContentResult InhaltXML() { return Content("<a>\r\n <b>123</b>\r\n <b>456</b>\r\n</a>", "text/xml"); } public HttpStatusCodeResult ServerFehler() { return new HttpStatusCodeResult(500); } public HttpUnauthorizedResult AuthentifizierungsFehler() { return new HttpUnauthorizedResult(); } public HttpNotFoundResult NichtGefundenFehler() { return new HttpNotFoundResult(); } public FileResult Datei() { return File("~/Controllers/HomeController.cs", "text/plain"); } } }
Filter
Filter erlauben das Anwenden einer bestimmten Logik auf eine Aktion. Diese Logik wird vor (lat. pre) oder nach
(lat. post) der Ausführung der Aktionsmethode ausgeführt. Bei Filtern handelt es sich um Attribut-Klassen,
d. h. der Attribut-Name der Klasse wird innerhalb von eckigen Klammern oberhalb der Funktion oder der Klasse angegeben.
Da das Thema Filter und vor allem das Erstellen von benutzerdefinierten Filtern komplexer ist und es eher für einfachere
Anwendungen nicht benötigt wird, werden wir uns hier lediglich mit dem Filter OutputCache
beschäftigen. Das
Attribut OutputCache
ermöglicht das Festlegen des Cache-Verhaltens für die jeweilige Aktion. Mit der
Eigenschaft Duration
können Sie bestimmen, wie lange (angegeben in Sekunden) die Aktion gecacht werden darf.
Im unteren Beispiel wird die Aktion Index()
für 5 Sekunden gecacht. Dieses Verhalten ist im Beispiel auf Grund
der Datum- und Uhrzeitausgabe gut ersichtlich.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace HWhBsp.Filter.Controllers { public class HomeController : Controller { [OutputCache(Duration=5)] public string Index() { return DateTime.Now.ToString(); } } }
Selektoren
Selektoren dienen dazu, Aktionsmethoden anzupassen, um somit das Verhalten bei Anfragen zu steuern. Selektoren unterstützen
dabei also das Routing-Verfahren. In ASP.NET MVC gibt es drei Selektoren: ActionName
, NonAction
und
ActionVerbs. ActionName
und NonAction
werden direkt als Attribute oberhalb der Funktion notiert.
Mit ActionName
können Sie den Namen der Aktionsmethode ändern. Der Aktionsname wird im Konstruktor übergeben.
Durch dieses Attribut ändert sich jedoch nicht der Name der Funktion, sondern lediglich der Zugriffsname, welcher in
der URL zum Aufruf der Aktion verwendet wird. Haben wir also eine Aktionsmethode mit dem Namen GetDatumUndUhrzeit()
,
welcher wir nun den Namen DatumUhrzeit
zuweisen, so heißt die Funktion weiterhin GetDatumUndUhrzeit()
.
Ein Zugriff mittels einer URL kann jedoch nur noch mit dem Aktionsnamen DatumUhrzeit
erfolgen. GetDatumUndUhrzeit
kann dann nicht mehr als Zugriffsname in der URL verwendet werden. Mit dem Attribut NonAction
können Sie eine
Funktion markieren, dass diese nicht als Aktion über die URL aufgerufen werden kann. Dieses Attribut benötigen Sie immer
dann, wenn eine öffentliche Funktion nicht für den HTTP-Zugriff bestimmt ist. ActionVerbs sind spezielle Selektoren, welche
Aktionen auf bestimmte HTTP-Methoden begrenzen. Die jeweilig anzugebenden Attribute setzen sich aus Http
und
dem Namen der HTTP-Methode (in Camel-Case-Schreibweise) zusammen (z. B. HttpGet
und HttpPost
). Hier nun
ein Beispiel zum Thema Selektoren:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace HWhBsp.Selektoren.Controllers { public class HomeController : Controller { [HttpGet] public string Index() { return "<form action=\"" + Url.Content("~/Home/FormularVerarbeitung/") + "\" method=\"post\"><input type=\"submit\" value=\"Formular per POST senden\" /></form>"; } [HttpPost] public string FormularVerarbeitung() { return "<a href=\"" + Url.Content("~/") + "\">Seite per GET anzeigen</a>"; } [ActionName("DatumUhrzeit")] public string GetDatumUndUhrzeit() { return GetDatum() + " " + GetUhrzeit(); } [NonAction] public string GetDatum() { return DateTime.Now.ToShortDateString(); } [NonAction] public string GetUhrzeit() { return DateTime.Now.ToShortTimeString(); } } }