10 MVC 4.0
10.1.1 Wichtige Quellen
10.1.2 Beispielwebseite
Die Übungen sind online lauffähig zu begutachten unter:
http://mk-net-prg.azurewebsites.net/
10.1.3 Das Model-View-Controler- Entwurfsmuster für
Webanwendungen
Das MVC- Entwurfsmuster wurde in den Entwicklungslabors der Firma
Xerox in den 1980-er Jahren erfunden. Angewendet auf die Entwicklung
von Programmen mit graphischer Benutzeroberfläche führt es
zu einer strikten Aufteilung in die zu verarbeitenden Daten
(Modelle), die Geschäftslogik (Controller) und die IO-Masken der
GUI (Views).
Im klassischen ASP.NET WebForm- Modell ist keine strenge Trennung
wie in MVC vorgegeben. Nicht selten landet die Implementierung der
gesamte Geschäftslogik in den Eventhandlern der
Websteuerelemente. Die Anpassung/Änderung der GUI erfordert
damit zwangsläufig eine Neuorganisation der Implementierung der
Geschäftslogik. MVC soll dieses Problem von Anfang an
ausschließen.
Anmerkung:
Auch das klassische WebForm- Modell ermöglicht es, die
Geschäftslogik getrennt von der Logik der IO- Masken der Gui zu
implementieren. Z.B. indem die gesamte Geschäftslogik unabhängig
von der Webanwendung in einer separaten Klassenbibliothek zuerst
implementiert und getestet wird.
10.1.3.1.1 Verarbeitung eines Http- Requests mittels
klassischer Webform:
Die
klassische WebForm integriert in den Seitenaufbau die Verarbeitung
der Anwendungslogik durch serverseitige Eventhandler. Diese werden in
einer Code- Behind- Datei implementiert.
10.1.3.1.2 WebForms als UI- Schnittelle zu Geschäftsobjekten
Aus Sicht der Geschäftslogik kann man klassische Webforms als
graphische Benutzerschnittstelle von Geschäftsobjekten
betrachten. In einfachen Fällen ist dabei die Webform das
Geschäftsobjekt selbst. Im Folgenden eine
Taschenrechneranwendung. Die Webform mit ihrem Markup und
Eventhandlern stellt das komplette Geschäftsobjekt
"Taschenrechner" dar:
Markup in der Page.aspx
|
<%@ Page Title="Start\UEs\01\Formular" Language="C#" MasterPageFile="~/UEs/Masters/DMSMaster.Master"
AutoEventWireup="true" CodeBehind="SummeAusZwei.aspx.cs" Inherits="WebDms2.AspBasics.SummeAusZwei"
EnableTheming="true" Theme="Round" Trace="false" %>
<%-- In Page wird durch das Attribut Trace="true" eine detailierte Protokollierung des Seitenaufbaus eingeschaltet (für Debugzwecke) --%>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<style type="text/css">
#divServer {
margin-bottom: 0px;
}
</style>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<h1>Summe aus zwei</h1>
<table class="tabStyle">
<tr>
<td class="tabItem">A =
</td>
<td class="tabItemL">
<asp:TextBox ID="tbxA" runat="server" Text="" />
</td>
<td>
</td>
</tr>
<tr>
<td class="tabItem">B =
</td>
<td>
<asp:TextBox ID="tbxB" runat="server" Text="" />
</td>
<td>
</td>
</tr>
<tr>
<td class="tabItem" colspan="3">
<asp:Button ID="btnAdd" runat="server" Text="+" OnClick="btnOp_Click" CommandName="op"
CommandArgument="add" />
<asp:Button ID="btnSub" runat="server" Text="-" OnClick="btnOp_Click" CommandName="op"
CommandArgument="sub" />
<asp:Button ID="btnMul" runat="server" Text="x" OnClick="btnOp_Click" CommandName="op"
CommandArgument="mul" />
<asp:Button ID="btnDiv" runat="server" Text="/" OnClick="btnOp_Click" CommandName="op"
CommandArgument="div" />
<asp:Button ID="btnPow" runat="server" Text="^" CommandName="op"
CommandArgument="pow" />
</td>
</tr>
<tr>
<td class="tabItem">Res =
</td>
<td runat="server" id="tabCellResult" class="tabItemL">
<asp:Label ID="lblResult" runat="server" Text="0" />
</td>
<td> </td>
</tr>
</table>
</asp:Content>
|
Eventhandler in der Page.csw
|
namespace WebDms2.AspBasics
{
public partial class SummeAusZwei : System.Web.UI.Page
{
protected void Page_Init(object sender, EventArgs e)
{
//btnPow.Click += new EventHandler(btnPow_Click);
}
protected void Page_Load(object sender, EventArgs e)
{
Trace.Write("Bin in Page_Load");
Debug.WriteLine("Bin in PageLoad (System.Diagnostics)");
}
protected void btnOp_Click(object sender, EventArgs e)
{
Trace.Write("Bin in btnOp_Click");
Debug.WriteLine("Bin in btnOp_Click (System.Diagnostics)");
var btn = (Button)sender;
Debug.Assert(btn.CommandName=="op", "SummeAusZwei.btnOp_Click: Der Button startet keine arithmetische Operation!");
// Einlesen der Operanden
double a = double.Parse(tbxA.Text);
double b = double.Parse(tbxB.Text);
double result = 0;
string opSymbol = "";
switch (btn.CommandArgument)
{
case "add":
opSymbol = "+";
result = a + b;
break;
case "sub":
opSymbol = "-";
result = a - b;
break;
case "mul":
opSymbol = "*";
result = a * b;
break;
case "div":
opSymbol = "/";
result = a / b;
break;
case "pow":
break;
default:
throw new Exception("unbekannte Operation");
}
var resultBackColor = result > 0 ? "0x3399FF" : "red";
tabCellResult.BgColor = resultBackColor;
lblResult.Text = string.Format("A {0} B = {1:N}", opSymbol, result.ToString());
}
}
}
|
10.1.3.1.2.1 Vorteil WebForms:
Kleine, isolierte Geschäftsobjekte wie der
Taschenrechner können schnell mit einer GUI dem Anwender
bereitgestellt werden
Auf dem Ereignismodell des Seitenaufbaues bauen
Klassenbibliotheken mit hunderten Steuerelementen auf. Das
Implementieren von Webforms mit den Steuerelementen wird von Visual
Studio umfangreich unterstützt
Die Steuerelemente speichern ihren Zustand automatisch im
ViewState
10.1.3.1.2.2 Nachteil WebForms:
Die Anwendungslogik wird auf Events im sequentiellen
Seitenaufbau abgebildet – detaillierte Kenntnisse der
zeitlichen Abfolge der Events für eine korrekte Implementierung
der Geschäftslogik nötig
Automatisierte Tests der Anwendungslogik nur mit komplexent
Tools möglich (Spezialbrowser), welche über Html-
Formularelemente die Funktionen der Geschäftslogik
parametrieren und aufrufen (siehe
hier).
Viewstate kann wegen seines Umfanges die Bandbreite für
Nutzdaten erheblich einschränken
komplexes Markup (z.B. bei GridViews). Markup als Alternative
zur objektorientierten Programmiersprache.
10.1.3.1.3 Verarbeitung eines Requests in einer MVC- WebApp
Model View Controler ist ein sog. Entwurfsmuster aus der
objektorientierten Programmierung. Dabei wird ein Request wie folgt
verarbeitet:
Jedem Url, der vom Request abgerufen wird, ist eine eine
Methode, genannt Action einer Controllerklasse
zugeordnet. Die Zuordnung wird Route genannt.
Zum jedem Request wird ein Controler instanziiert.
Die Anwendungslogik ist in die Methoden (Actions) einer
Controler- Klasse verlagert. Bei der Ausführung der Action
werden die Ergebnisse in einer Datenstruktur, genannt Modell
gepeichert. Am Ende wählt die
Action eine View aus und übergibt diese zusammen mit dem Modell
an die ASP.NET Laufzeit.
Die Laufzeit instanziiert die View
und setzt in ihr die Eigenschaft Modell auf das zuvor übergebene
Modellobjekt. Dann wird die Render- Methode der View ausgeführt,
wobei serverseitiger Code genutzt wird, um die Daten aus dem Modell
in die Seite einzubetten. Das Ergebnis ist HTML und Javascript-
Code, der an den Browser gesendet wird.
10.1.3.1.4 MVC als graphische Oberfläche zur Steuerung
von Geschäftsabläufen (Workflows)
In MVC werden über Formulare aufzurufende Aktionen aus einem
Geschäftsablauf parametriert, und die Aktionen schließlich
aufgerufen. Die Aktionen generieren und konfigurieren wiederum
Geschäftsobjekte, deren Zustände mittels Views präsentiert
werden.
Komplexe Geschäftsabläufe sind durch eine MVC Anwendung
nahezu 1:1 abbildbar. Im folgenden der Workflow in einer
Taschenrechner- Anwendung:
Modelle als Geschäftsobjekte
|
namespace MVC.Basics.Models.Exc01_MVCPattern
{
public class BinOpModel : BasicsModelBase
{
private int myPrivate = 0;
protected int myProtected = 0;
public BinOpModel()
{
Operator = Operators.add;
}
public enum Operators
{
add, sub, mul, div
}
/// <summary>
/// Operand A
/// </summary>
public double A { get; set; }
/// <summary>
/// Operand B
/// </summary>
public double B { get; set; }
public Operators Operator { get; set; }
/// <summary>
/// Ergebnis der Operation
/// </summary>
public double Result { get; set; }
/// <summary>
/// Hilsfunktion für die Views: konnte bis dato noch nicht in die Extension- Klasse ausgelagert werden.
/// </summary>
public string OperatorTxt
{
get
{
return Operator.ToString();
}
set
{
Operator = (Operators)Enum.Parse(typeof(Operators), value);
}
}
}
}
|
Controller mit Actions aus einem Workflow
|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVC.Basics.Models.Exc01_MVCPattern;
namespace MVC.Basics.Controllers
{
public class Exc01_MVCPatternController : BasicsBaseController
{
/// <summary>
/// Behandlung von Ausnahmen erfolgen durch überschreiben eines Eventhandlers der Controllerklasse
/// </summary>
/// <param name="filterContext"></param>
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
View("Error").ExecuteResult(ControllerContext);
}
/// <summary>
/// Action, die den Startpunkt eines Workflows darstellt, den die Actions des Controllers abbilden
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
BinOpModel Model = RecordActionAndCreateModel<Models.Exc01_MVCPattern.BinOpModel>(this, "Index", "WF_Start", "Exc01", CreateRoutInfo(Controllers.Workflows.Exc01_BinOp.ToString()));
return View("BinOpView", Model);
}
/// <summary>
/// Action, durch die der Workflow des Aufbaus und der Berechnung eines einfachen mathematischen Ausdrucks im Controller abgebildet wird
/// </summary>
/// <param name="binOpModel"></param>
/// <returns></returns>
[HttpPost]
public ActionResult BinOp(BinOpModel binOpModel)
{
if (ModelState.IsValid)
{
Basics.Models.Exc01_MVCPattern.BinOpComputer.Exec(binOpModel);
}
return View("BinOpView", binOpModel);
}
}
}
|
View mit Servercode in Razor(C#)
|
@using Props = MVC.Basics.Properties
@model MVC.Basics.Models.Exc01_MVCPattern.BinOpModel
@{
ViewBag.Title = "BinOpView";
Layout = "~/Views/Shared/_LayoutOhneNavigation.cshtml";
// Abschalten der Clientseitigen Validierung
HtmlHelper.ClientValidationEnabled = false;
}
@* Ein Kommentar in Razor beginnt mit Klammeraffe*, und endet mit *Klammeraffe
Razor ist eine innovativer Parser auf der Serverseite, durch welchen die Integration
serverseitiger Logik ins HTML- Markup mit einer besonders schlanken Syntax beginnt.
Dabei gilt ein einfaches Prinzip: jeder Serverseitig auszuführende Ausdruck beginnt mit einem @.
*@
@using MVC.Basics.Models.Exc01_MVCPattern;
@using (Html.BeginForm("BinOp", "Exc01_MVCPattern"))
{
<div>
<h2>Binäre Arithmetik</h2>
@Html.ValidationSummary(true)
<section>
<div>
@Html.LabelFor(model => model.A, "A=", new { Class = "mathOpLabel" })
@Html.EditorFor(model => model.A, new { Class = "mathOpField" })
@Html.DropDownListFor(model => model.OperatorTxt, Model.UIOperatorSelectionList(), new { Class = "mathOperator" })
@Html.LabelFor(model => model.B, "B=", new { Class = "mathOpLabel" })
@Html.EditorFor(model => model.B, new { Class = "mathOpField" })
<input type="submit" value="=" />
@Html.DisplayFor(model => model.Result, new { Class = "mathOpField" })
</div>
</section>
</div>
}
@* Benannter Abschnitt, der in der Layoutseite im Kopf eingeblendet wird, und zusätzliche Styles enthält *@
@section Styles {
@Styles.Render("~/Content/themes/Exc01")
}
@* Bennanter Abschnitt, der im Fuss der Layoutseite eingeblendet wird, und die Skripte enthält *@
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
@Scripts.Render("~/bundles/jqueryui")
<script>
// Hier wird auf dem DOM mittels jQuery eine Operation ausgeführt, welche bewirkt,
// das alle title- Attribute als Tooltips dienen
//jQuery(document).tooltip();
</script>
}
|
Vorteile
MVC
Klare Trennung zwischen
Anwendungslogik und Präsentation in der Architektur
Vorteil, wenn Ergebnisse in
verschiedenen Formen präsentiert werden soll, z.B. für
mobilen Client, Desktop
Zugriff auf Ressourcen über
URL. Kann dadurch leicht in andere Geschäftsanwendungen (sofern
sie Hyperlinks implementieren) eingebunden werden.
Keine automatische Speicherung des
Steuerelementzustandes in ViewState- bessere Ausnutzung der
Bandbreite im Netz für Nutzdaten
Hohe Maß an Kontrolle über
das ausgelieferte Markup. Hierdurch können z.b. Webdesigner die
Darstellung im Browser besser beeinflussen. Bei Webforms wird das
Markup unerreichbar für den Designer in den Steuerelementen
generiert.
Sehr feine Steuerung der
Zugriffsberechtigung auf Ebene der Controller bis hinunter zu den
Actions.
Automatisierte Tests der in
Action- Methoden hinterlegte Anwendungslogik möglich
Beschränkung des
serverseitigen Codes in der View auf das Einbetten der Ergebnisse
aus dem Modell in das Client- Markup
Implementieren der View
mittels klassischer ASP.NET Controls oder moderner Razor
– Syntax
Besonders geeignet für große
Entwicklerteams
Nachteile
MVC
Einfache, formular- basierte
Anwendungen werden unnötig komplex (z.B. summe-aus-zwei.aspx)
Einziges Mittel zur Strukturierung
und Organisation von Actions sind Controller. Das führt in
großen Geschäftsanwendungen zu einer Vielzahl von
Controllern, die nebeneinander im Controller- Ordner abgelegt sind.
Keine Entwurfsansicht für
RAZOR- Views
API bietet vielerorts Varianten
mit Strings und untypisierten dynamischen Objekten als Parameter an.
Insbesondere die Einführenden Beispiele machen von diesen
Gebrauch. Das erschwert die Wartung insbesondere bei großen
Projekten. Kann durch die Wahl von streng typisierten API-
Funktionen überwunden werden.
10.1.4 Kompatibilität zwischen ASP.NET WebForms und
MVC
Webforms und MVC bauen auf den gleichen grundlegenden ASP.NET
Bibliotheken auf. So sind viele Klassen in beiden Architekturen
einsetzbar. Jedoch wird weder der ViewState, noch Postback-
Ereignisse unterstützt. Steuerelemente, die auf ViewState und
PostBack aufbauen wie Buttons, GridView, Repeater und Datalist sind
so nicht ohne Einschränkungen einstetzbar in MVC einsetzbar.
Bekannte Klassen der ASP.NET Webfrom Bibliothek
|
Verfügbar in MVC
|
Anwendungszustand
|
Ja, Eigenschaft von Controller
|
Sitzungszustand
|
Ja, Eigenschaft von Controller
|
Viewstate
|
Nein
|
ASPX- Steuerelemente, die auch bei abgeschalteten Viewstate
funktionieren wie Label, Textbox, DropDown
|
Ja, in aspx- Views
|
ASPX-Steuerelemente, die Viewstate benötigen, oder
PostBack Events benötigen wie Buttons
|
Nein
|
10.1.5 Struktur einer MVC-
Anwendung
Im Folgenden ein Ausschnitt aus einem
MVC Beispielprojekt
|
Erläuterungen von oben nach unten:
App_Start
Hier werden Klassen definiert, deren Instanzen für die
gesamte Anwendung sichtbar sind. Dazu zählen z.B. Tabellen
mit Routendefinitionen (RouteConfig.cs)
Controllers
Hier werden die Controllerklassen miit ihren Action- Methoden
definiert
Models
Hier werden die Modelle definiert, in denen die Controler
Ergebnisse an die Views übergeben oder von diesen Eingaben
entgegennehmen.
Views
Hier werden die View- Klassen definiert.
Views/Shared
Hier werden Masterseiten für die Views definiert
|
10.1.5.1.1 Pfade
Um die Installation von Webseiten auf Servern zu vereinfachen, ist
es notwendig, Dateipfade auf Ressourcen in Serversteuerelementen an
den jeweiligen Installationsort der Webseite anzupassen. ASP.NET
bietet dazu den RootPath- Operator, welcher durch die Tilde(~)
ausgedrückt wird.
~ // RootPath Operator: verweist auf Installationspfad der aktuellen Webanwendung
~/Images/My.gif // Absoluter Verweis auf die Gif Datei My.Gif im Image- Verzeichnis
// unterhalb des Installationsverzeichnisses
/Images/My.gif // Relativer Verweis auf die Gif Datei My.gif im Image- Verzeichnis
// unterhalb des Speicherortes der aktuell aktiven Webform
10.1.5.1.2 Namenskonventionen für Controller, Actions und Views
Die Quelltextdateien von Controllern werden sind nach folgendem
Schema zu bezeichnen:
<Name>Controller.cs
Wird z.B. ein Controller namens Home implementiert,
dann ist im Ordner Controllers eine
Quelltextdatei
HomeController.cs
abzulegen.
Liefert eine
Action
immer genau eine View
zurück, dann sollte Action und
View gleich benannt werden. Beispiel:
Der Controller
HomeController.cs
enthalte eine Action Index():
namespace mko.Asp.Mvc.Test.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
}
Die Methode View() wählt die DefaultView nach folgender
Konvention aus:
DefaultViewName := ActionName
10.1.6 http Post
Durch einen Submitbutton in einem
Webformular wird ein
http- Post
Befehl ausgelöst. Beim Post werden die Formulardaten in einem
http Datentelegramm verpackt und an die Adresse zurückgesendet,
von der die Webseite ursprünglich geladen wurde.
Die Postback Datentelegramm wird
schließlich von einer Action im Controller verarbeitet, die
Parameter haben, deren Namen
mit Formularelementen übereinstimmen
einen Parameter haben, der
vom Typ des Modells ist
Die mit dem Attribut
[HttpPost] gekennzeichnet wurde
Die im Formularelement als
PostBack- Action definiert wurde
@model MVC.Basics.Models.Exc02_Routing.RoutingInfos
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@* Mit BeginForm wird der Formularbereich im html- Dokument definiert.
Der erste Parameter definiert die Action, und der zweite den Controller,
der für die Bearbeitung des http-Postback zuständig ist.
*@
@using (Html.BeginForm("PostBackAction", "Exc02_Routing"))
{
<text>
@Html.HiddenFor(model => model.WfID)
<h3>Routing mittels http Post Befehl</h3>
<input type="submit" value="Submit" />
</text>
}
Die Postback- Action hat folgende
Signatur:
[HttpPost] // Attribut optional. Nur notwendig, wenn PostBackAtion Funktion überladen
public ActionResult PostBackAction(string WfID, string TestInput)
{
…
}
10.1.7 http Get und Routing
Hyperlinks definieren dabei im href
Attribut einen URL, von dem der Browser mittels eines http- Get
Befehls eine Ressource abruft.
Aufbau eines URL bei einem http Get:
URL::=<Schema(z.B.http)><Host(z.B.www.mkoit.de)><Path(z.B./Impressum)><Querystring(z.B.?ID=1)>
Während Schema und Host für eine Webanwendung im
allgemeinen festgelegt sind, kann im Pfad und im Querystring
Informationen für die Navigation zur gewünschten Ressource
hinterlegt werden. Dies wird auch als Routing
bezeichnet.
10.1.7.1.1 Routing beim Get (ActionLink und RoutLink)
Allgemeine Infos dazu
hier:http://msdn.microsoft.com/en-us/library/cc668201.aspx
Mit den Grundeinstellungen ordnet ein
Webserver einem URL immer eine Dateiressource zu (HTML-File). In MVC
ist diese Zuordnung zu grob. Hier muss über einen URL die
Action- Methode eines Controllers ausgewählt und parametriert
werden. Dies wird durch die Definition von Routen erreicht.
Die Routen werden definiert im
App_Start- Verzeichnis unter RouteConfig.cs. Die Definitionen
erfolgen Mittels der
MapHttpRoute
-
Methode des globalen Objektes
routes
.
Dabei ist mindestens ein Name für die Route, sowie ein URL-
Muster zu definieren.
URL Muster bestehen aus Segmenten, die
durch / getrennt sind:
SEGMENT1/SEGMENT2/.../SEGMENTn
Segmente können feste Werte oder Platzhalter sein. Die
Paltzhalter werden dabei in geschweifte Klammern gesetzt:
SEGMENT1/{PLATZHALTER1}/.../SEGMENTn
Ein Muster wird wie folgt auf eine URL gemapt:
Übereinstimmende Segmente in
URL und Muster werden einander zugeordnet. Die Reihenfolge im Muster
darf dabei nicht verletzt werden
Korrespondieren Platzhalter mit
noch nicht zugeordneten Segmenten der URL, dann werden diese
Segmente als Textwerte in einem Dictionary eingetragen, wobei der
Bezeichner des Platzhalters der Schlüssel ist
Die MVC- Laufzeit sucht in dem
Dictionary mindestens zwei Einträge:
Eintrag unter dem Schlüssel
controller: Der Wert
wird der String Controller angehängt.
Er stellt den Namen der Controllerklasse dar
Eintrag unter dem Schlüssel
action: Der Wert ist der Name der Action- Methode innerhalb
der Controller- Klasse.
Platzhalter, die nach dem
Platzhalter für die Action- Methode folgen, stehen für die
Parameter der Action- Methode. Die Bezeichner der Platzhalter müssen
mit den Parameternamen übereinstimmen.
Z.B. ordnet das folgende Pattern
den URL http://localhost:49353/Graphic/MoveTriangle/100
den Aufruf der Action
GraphicController.MoveTriangle(100)
zu:
routes.MapRoute(
name: "MyRoute1",
url: "{controller}/{action}/{translation}"
);
10.1.7.1.2
10.1.8 Modelle
10.1.8.1.1 Modelle = Geschäftsobjekte
Eine MVC- Anwendung verarbeitet die Informationen in Modellen.
Modelle sind die Klassen der Geschäftsobjekte. Eine Anwendung
zur Verwaltung des Lagerbestandes wird ein Modell namens Artikel
besitzen. Ein Computerspiel hat ein
Modell namens SpielfigurSchlachtschiff.
10.1.8.1.2 Speicherort
Innerhalb einer MVC- Anwendung
ist der Ordner
Models zur
Aufnahme der Klassendeklarationen der Modelle vorgesehen. In größeren
Anwendungen werden Modelle in separaten Klassenbibliotheken
ausgelagert.
10.1.8.1.3 Einschränkungen definieren für
Validierung
Die Definition von Eigenschaften in Modellen bildet häufig
ungenügend die Anforderungen an diese ab. So kann auf die
Festlegung bestimmter Eigenschaften verzichtet werden, während
anderen zu jedem Zeitpunkt ein gültiger Wert zugewiesen sein
muss (required). Eine Eigenschaft könnte einen Währungswert
darstellen, für den es aber in C# keinen Elementaren Datentyp
gibt.
Mittels Attribute aus dem Namespace
System.ComponentModel.DataAnnotations können
die Modelleigenschaften mit zusätzlichen Informationen versehen
werden, aus der die MVC- Laufzeit erkennt, ob es sich z.B. um
Währungs- oder erforderliche Werte handelt. Z.B. werden diese
Metainformationen von den HTML- Helper Erweiterungen zur Bindung von
Modelldaten an Formularfelder für die Implementierung von
Validierungen genutzt.
using System.ComponentModel.DataAnnotations;
namespace Calculator.Models
{
public class BinOpModel
{
[Required]
[Range(-1.0e100, 1.0e100)]
public double A { get; set; }
[Required]
[Range(-1.0e100, 1.0e100)]
public double B { get; set; }
...
}
}
Achtung: Für
Das RangeAttribute dürfen als Minimum und Maximum nicht
double.MinValue oder double.MaxValue definiert werden, da sonst die
Validierung fehlschlägt.
Validierung
im Client durch View
Beim Rendern der
View wird lt. Validierungsattributen JavaScript- Code eingebettet,
der die Validierung im Browser implementiert.
Die Validierung
funktioniert für nummerische Werte aktuell nur für
angloamerikanische Darstellung von nummerischen Werten (also 3.14
anstatt 3,14). Für die deutsche Kultur muss die clientseitige
Validierung abgeschaltet werden, indem in der View folgendes notiert
wird:
@{
ViewBag.Title = "BinOpView";
Layout = "~/Views/Shared/_Layout.cshtml";
// Abschalten der Clientseitigen Validierung
HtmlHelper.ClientValidationEnabled = false;
}
Weitere Informationen dazu unter:
http://www.dotnetlearners.com/blogs/view/86/Enable-or-disable-client-side-validations-in-mvc4.aspx
http://msdn.microsoft.com/de-de/library/gg674880(v=vs.98).aspx
Validierung
in Action auf dem Server
Sollte
die Validierung auf dem Client nicht durchgeführt werden
(deaktiviertes JavaScript), dann kann die Action gesichert werden,
indem auf dem Server die Validierungsregeln ausgeführt werden.
Dazu wird die Methode
ModelState.IsValid
aufgerufen
public ActionResult MyAction(MyModel model)
{
if(!ModelState.IsValid) return View("Error");
…
}
10.1.8.1.4 Rendering der Modelleigenschaften in der View definieren
In einem Modellzentrieren Ansatz
können auch die Darstellungen der Eigenschaftswerte bereits im
Modell definiert werden. Dies geschieht durch Attribute aus
System.ComponentModel.DataAnnotations.
Attribut
|
Bedeutung
|
Beispiel
|
DisplayFormat
|
Formatierung der Ausgabe. Z.B. wie viele Nachkommastellen bei
einem nummerischen Typ usw.
|
[DisplayFormat(DataFormatString="{0:N3}")]
|
10.1.8.1.5 Modelle aktualisieren
Modellobjekte werden in Aktionen erzeugt, mit Daten gefüllt
und an Views gesendet. Dort werden mit einem Teil ihrer Eigenschaften
Formularfelder gefüllt, die der Anwender verändern kann.
Anschließend werden die Eingaben in den Formularfeldern eine
Aktion zurückgesendet. In der Aktion ist das Modellobjekt mit
den geänderten Daten zu aktualisieren.
Die Menge aller Daten in einem Modellobjekt ist häufig größer
als die Menge der in der View geänderten Daten. So kann das
Modellobjekt nicht einfach durch die geänderten Daten ersetzt
werden. Nur die Werte der geänderten Eigenschaften im
Modellobjekt dürfen ersetzt werden.
(I) Create() → M{p1,… , pn} → View() → F{f1(pi), … , fm(pj)}
(II) → Anwender() → F{f1(pi'), … , fm(pj')}
(III.1) → PostBack() → M{…, pi', … , pj', …}
(III.2) → PostBack() → FormCollection{pi', … , pj'}
→ TryUpdateModel() → M{p1,…, pi', … , pj', …, pn}
_ III.1 und III.2 sind die von MVC unterstützen Varianten.
III.1
Voraussetzung: Die aufgerufene Action erwartet als
Parameter ein Modell.
Beim PostBack wird in der View ein neues Modell erzeugt und
mittels eines Modelbinders wird dieses mit den geänderten Werten
aus den Formularfeldern aktualisiert. Eigenschaften, die nicht mit
Formularfeldern korrespondieren, werden mit Default- werten gefüllt.
Mittels Attribute [Bind]- Attribut kann in die Bindung der
Formularfelder an die Modelleigenschaften eingeriffen werden
public ActionResult Edit([Bind(Exclude="AnzLeben")] Spielfigur){...}
10.1.8.1.6 III.2
Voraussetzung: Die aufgerufene Action hat einen Parameter
vom Typ FormCollection
Beim Postback werden die Formulardaten in eine FromCollection
verpackt. In der Action kann das Modell aus dem Sitzungszustand oder
der Datenbank geladen werden. Dann wird es mittels der Methode
ViewPage.TryUpdate mit den Daten aus dem FormCollection- Parameter
aktualisiert.
Public ActionResult Edit(FormCollection fc) {
…
Spielfigur fig = Session["Spielfigur"] as Spielfigur
TryUpdateModel(fig, fc);
…
}
10.1.9 Controller und Actions
Der Controller ist eine Klasse, die Methoden besitzt, welche
mittels Routing über URL und HTTP- Requests von einem Webclient
gestartet werden können. Diese Methoden werden Actions genannt.
Sie verarbeiten den Requests, indem Modelle erzeugt oder abgerufen,
und mit einer View verknüpft werden.
10.1.9.1 Modelle validieren und aktualisieren
10.1.9.2 Modelle an Views binden, Response- Daten erzeugen
10.1.9.3 Eventhandler
10.1.10 Views
View sind für die Darstellung der Daten aus dem Modellen im
Browser zuständig. Sie enthalten Code, der vom Server vor der
Auslieferung an den Browser ausgeführt wird, und die Modelldaten
einbettet, sowie Code, den der Browser ausführt (HTML,
JavaScript).
Beispiel: BinOpView für den Math- Controler:
@* Server
@* Definition des Models *@
@model mko.Asp.Mvc.Test.Models.BinOpModel
@{
ViewBag.Title = "BinOpView";
}
<h2>Binäre Arithmetik</h2>
@* Einbindung eines .NET Namespaces auf der Serverseite *@
@using Props= mko.Asp.Mvc.Test.Properties
@* Ein c# Usingblock, der eine Form- Instanz kapselt. Am Ende wird die Dispose- Methode der Form-
Instanz aufgerufen, was die Ausgabe eines schließenden Formtags zur folge hat.
In BeginForm wird als Ziel eines Submits die Action "BinOp" aus dem Controller "Math" definiert.
*@
@using (Html.BeginForm("BinOp", "Math"))
{
@* Html- Helper, der ein Zusammenfassung von Fehlermeldungen einblendet *@
@Html.ValidationSummary(true)
<section>
<div>
@* Folgende Helper integireren die Werte von Modelleigenschaften, sowie HTML- Formularelemente und
JavaScript Code zum Editieren und Validieren *@
@Html.LabelFor(model => model.A, new { Class = "mathOpLabel" })
@Html.EditorFor(model => model.A, new { Class = "mathOpField" })
@Html.ValidationMessageFor(model => model.A)
@Html.DropDownListFor(model => model.OperatorTxt, Model.OperatorSelection, new { Class = "mathOperator" })
@* Anstatt MVC- Helper Html.Display wird hier direkt mit Html label und jQuery.UI Tooltip gearbeitet.
Das title- Attribut ist dabei die Datenquelle der Tooltips. Das Verhalten mit den Tooltips wird durch
die jQuery- Operation auf dem DOM im Scriptblock ganz unten realisiert.
Vorteil: der Tooltip ist auch aus einer Resource einlesbar
*@
<label title="@Props.Resources.BinOpViewOperand2Description" class="mathOpLabel">@Props.Resources.BinOpViewOperand2Name : </label>
@Html.EditorFor(model => model.B, new { Class = "mathOpField" })
@Html.ValidationMessageFor(model => model.B)
@* Der Submittbutton sendet einen Post- Request an den Webserver, wobei die in
BeginForm definierte Action angefordert wird *@
<input type="submit" value="=" />
@Html.DisplayFor(model => model.Result, new { Class = "mathOpField" })
</div>
</section>
<div>
@Html.ActionLink("Back to List", "Index")
</div>
}
@* Benannter Abschnitt, der in der Layoutseite im Kopf eingeblendet wird, und die Styles enthält *@
@section Styles {
@Styles.Render("~/Content/themes/base/css")
}
@* Bennanter Abschnitt, der im Fuss der Layoutseite eingeblendet wird, und die Skripte für den Browser
in die Ausgabe an den Browser integriert *@
@section Scripts {
@* Achtung: JavaScripte sollten immer in einer separaten .js Date definiert werden, die mittels
Bundel- Konfiguration (Ordner App_Start\BundelConfig) in eine View eingebunden werden.
Sonst funktioniert z.B. das Scriptdebugging in VisualStudio nicht ! *@
@Scripts.Render("~/bundles/jqueryval")
@Scripts.Render("~/bundles/jqueryui")
<script>
// Hier wird auf dem DOM mittels jQuery eine Operation ausgeführt, welche bewirkt,
// das alle title- Attribute als Tooltips dienen
// Achtung: in Visual Studio nicht debuggbar, da nicht über Bundel- config eingebunden (siehe oben)
jQuery( document ).tooltip();
</script>
}
10.1.10.1 Serverseitiger Code mit RAZOR parsen
Der vom Server auszuführende Code wird in einer View in der
sog. RAZOR- Syntax eingebunden. RAZOR ist ein Parser, der sich am @-
Symbol orientiert. Dank eingebauter Intelligenz sind keine weiteren
Steuersymbole notwendig, die den serverseitigen Code vom
clientseitigen abgrenzen. Im Vergleich zu klassischen ASP.NET Code,
der Serverseitigen Code in <% … %> einschloss, sind
RAZOR Views leichter lesbar, da schlanker und weniger überladen.
Es gelten folgende Regeln für den RAZOR- Parser:
@
|
RAZOR Einleitungssymbol. Soll @ als literaler Text eingesetzt
werden, dann @@
|
@Variablenname
|
Inhalt einer Variable wird serverseitig in die Ausgabe an den
Browser eingebettet
|
@Methode(...)
|
Rückgabewert der Methode wird serverseitig eingebettet
|
@(...)
|
Wirkungsweise von RAZOR auf Klammerinhalt beschränken.
Muss eingesetzt werden, wenn RAZOR das Ende einer serverseitigen
Anweisung nicht korrekt erkennt
|
@{ … }
|
Einbetten serverseitiger Anweisungsliste in View
|
@model Typename
|
Datentyp des Modells in der View festlegen (streng typisierte
View)
|
@using Namespace
|
Deklaration eines Namespaces
|
@helper Methodensig{...}
|
RAZOR- Unterprogramm.
|
@* … *@
|
Kommentar
|
<text>...</text>
|
Der Inhalt des Textelementes ist clientseitiger Code, den RAZOR
überspringt. Beim Rendern werden die <text> Tags
entfernt.
|
<htmlTag>...</htmlTag>
|
Text in HTML- Tags wird von RAZOR übersprungen und an den
Client geliefert.
|
10.1.10.1.1 RAZOR- Unterprogramme
Wiederholen sich serverseitige Anweisungen, dann können die
in einen helper- Unterprogrammblock ausgelagert werden:
@* Selbsdefinierter Helper, der die Einbettung von Operanden automatisiert *@
@helper MyLabel(string name, string description)
{
<label title="@description" class="mathOpLabel">@name : </label>
}
<h2>Binäre Arithmetik</h2>
@using Props = mko.Asp.Mvc.Test.Properties
@using (Html.BeginForm("BinOp", "Math"))
{
@* Html- Helper, der ein Zusammenfassung von Fehlermeldungen einblendet *@
@Html.ValidationSummary(true)
<section>
<div>
@* Einsatz selbsdefinierter Helper *@
@MyLabel(Props.Resources.BinOpViewOperand1Name, @Props.Resources.BinOpViewOperand1Description)
@Html.EditorFor(model => model.A, new { Class = "mathOpField" })
@Html.ValidationMessageFor(model => model.A)
10.1.10.1.2 Html.Helper
http://www.asp.net/mvc/tutorials/views/introduction-to-razor-syntax
10.1.10.1.3 Anpassen
der Ausgaben von Html.Display etc. mit Templates
10.1.10.2 Partielle Views
http://haacked.com/archive/2009/11/18/aspnetmvc2-render-action.aspx
10.1.10.3 Partielle Views aktualisieren mit jQuery
http://stackoverflow.com/questions/1670415/mvc-how-to-return-instruction-to-run-javascipt-method/1670447#1670447
10.1.11 Clientseitige Scripts einbinden
JavaScript sollte in die Views nicht direkt eingebunden
werden wie im folgenden Beispiel, da z.B. das Scriptdebuggen von
Scriptcode direkt in der View nicht unterstützt wird:
@section Scripts {
@* Achtung: JavaScripte sollten immer in einer separaten .js Date definiert werden, die mittels
Bundel- Konfiguration (Ordner App_Start\BundelConfig) in eine View eingebunden werden.
Sonst funktioniert z.B. das Scriptdebugging in VisualStudio nicht ! *@
@Scripts.Render("~/bundles/jqueryval")
@Scripts.Render("~/bundles/jqueryui")
<script>
// Hier wird auf dem DOM mittels jQuery eine Operation ausgeführt, welche bewirkt,
// das alle title- Attribute als Tooltips dienen
// Achtung: in Visual Studio nicht debuggbar, da nicht über Bundel- config eingebunden (siehe oben)
jQuery( document ).tooltip();
</script>
}
Stattdessen ist das Script in eine .js- Datei auszulagern, die im
Scriptordner abgelegt wird. Das Script wird dann mittel
@Script.Render("Url")
in die View eingebunden:
@section Scripts {
@Scripts.Render("~/bundles/Stat")
}
10.2 Sicherheit
MVC ist Teil von ASP.NET. Damit sind die gleichen Systeme zur
Absicherung des Zugriffs auf Anwendung und Daten nutzbar wie im
klassischen ASP.NET. Im einzelnen:
Authentifizierung über IIS
Formular basierte Authentifizierung mittels ASP.NET
Membership
Rollenverwaltung mittels ASP.NET RoleManager
Autorisierung des Zugriffs auf Anwendungsordner über
WebConfig
Autorisierung auf Dateisystemressourcen über NTFS-
Dateizugriffsrechte
Achtung:
Die
Projektvorlage
Internet
Application
registriert
für Demozwecke einen vereinfachten Membership- Provider über
das Attribut [InitializeSimpleMembership] vor dem Account-
Controller. Um die echte Membership zu nutzen, muss dieses Attribut
auskommentiert/entfernt werden.
10.2.1 Zugriff auf Actions einschränken
Zusätzlich zu den genannten Instrumenten der
Zugriffskontrolle können mittels Attribute die Zugriffe auf
Aktions eines Controllers eingeschränkt werden:
[Authorize]
|
Nur angemeldete Benutzer dürfen diese Action aufrufen
|
[Authorize(Roles="A, B")]
|
Nur Benutzer, die zur Rolle A oder B gehören, dürfen
die Action aufrufen.
|
[AllowAnonymous]
|
Alle Benutzer dürfen diese Action aufrufen
|