Freigeben über


Bereitstellen von CRUD-Unterstützung (Create, Read, Update, Delete) für Datenformulareinträge

von Microsoft

PDF herunterladen

Dies ist Schritt 5 eines kostenlosen "NerdDinner"-Anwendungstutorial , in dem Sie schrittweise eine kleine, aber vollständige Webanwendung mit ASP.NET MVC 1 erstellen.

Schritt 5 zeigt, wie Sie unsere DinnersController-Klasse weiterentwickeln, indem Sie die Unterstützung für das Bearbeiten, Erstellen und Löschen von Dinners damit aktivieren.

Wenn Sie ASP.NET MVC 3 verwenden, empfehlen wir Ihnen, die Tutorials Erste Schritte Mit MVC 3 oder MVC Music Store zu befolgen.

NerdDinner Schritt 5: Erstellen, Aktualisieren, Löschen von Formularszenarien

Wir haben Controller und Ansichten eingeführt und beschrieben, wie Sie sie verwenden, um eine Auflistung/Details-Erfahrung für Dinners vor Ort zu implementieren. Unser nächster Schritt wird sein, unsere DinnersController-Klasse weiter zu weiterentwickeln und die Unterstützung für das Bearbeiten, Erstellen und Löschen von Dinners auch damit zu ermöglichen.

VON DinnersController behandelte URLs

Wir haben zuvor Aktionsmethoden zu DinnersController hinzugefügt, die Unterstützung für zwei URLs implementiert haben: /Dinnersund /Dinners/Details/[id].

URL VERB Zweck
/Abendessen/ GET Zeigt eine HTML-Liste der bevorstehenden Abendessen an.
/Dinners/Details/[id] GET Zeigen Sie Details zu einem bestimmten Abendessen an.

Wir fügen nun Aktionsmethoden hinzu, um drei zusätzliche URLs zu implementieren: /Dinners/Edit/[id], /Dinners/Create und /Dinners/Delete/[id]. Diese URLs ermöglichen die Unterstützung für das Bearbeiten vorhandener Dinner, das Erstellen neuer Dinner und das Löschen von Dinners.

Wir unterstützen sowohl HTTP GET- als auch HTTP POST-Verbinteraktionen mit diesen neuen URLs. HTTP GET-Anforderungen an diese URLs zeigen die anfängliche HTML-Ansicht der Daten an (ein Formular, das mit den Dinner-Daten im Fall von "bearbeiten", ein leeres Formular im Fall von "erstellen" und einen Löschbestätigungsbildschirm im Fall von "löschen" aufgefüllt wird). HTTP POST-Anforderungen an diese URLs speichern/aktualisieren/löschen die Dinner-Daten in unserem DinnerRepository (und von dort in die Datenbank).

URL VERB Zweck
/Dinners/Edit/[id] GET Zeigt ein bearbeitbares HTML-Formular mit Dinner-Daten an.
POST Speichern Sie die Formularänderungen für ein bestimmtes Dinner in der Datenbank.
/Dinners/Create GET Zeigt ein leeres HTML-Formular an, mit dem Benutzer neue Dinners definieren können.
POST Erstellen Sie ein neues Dinner, und speichern Sie es in der Datenbank.
/Dinners/Delete/[id] GET Bildschirm zur Bestätigung des Löschvorgangs anzeigen.
POST Löscht das angegebene Dinner aus der Datenbank.

Bearbeiten des Supports

Beginnen wir mit der Implementierung des "Bearbeiten"-Szenarios.

Die HTTP-GET Edit Action-Methode

Wir beginnen damit, das HTTP-Verhalten "GET" unserer Bearbeitungsaktionsmethode zu implementieren. Diese Methode wird aufgerufen, wenn die URL /Dinners/Edit/[id] angefordert wird. Unsere Implementierung sieht wie folgt aus:

//
// GET: /Dinners/Edit/2

public ActionResult Edit(int id) {

    Dinner dinner = dinnerRepository.GetDinner(id);
    
    return View(dinner);
}

Im obigen Code wird das DinnerRepository-Objekt verwendet, um ein Dinner-Objekt abzurufen. Anschließend wird eine View-Vorlage mithilfe des Dinner-Objekts gerendert. Da wir keinen Vorlagennamen explizit an die View() -Hilfsmethode übergeben haben, wird der konventionsbasierte Standardpfad verwendet, um die Ansichtsvorlage aufzulösen: /Views/Dinners/Edit.aspx.

Nun erstellen wir diese Ansichtsvorlage. Klicken Sie dazu mit der rechten Maustaste in die Edit-Methode, und wählen Sie den Kontextmenübefehl "Ansicht hinzufügen" aus:

Screenshot: Erstellen einer Ansichtsvorlage zum Hinzufügen einer Ansicht in Visual Studio

Im Dialogfeld "Ansicht hinzufügen" geben wir an, dass wir ein Dinner-Objekt als Modell an die Ansichtsvorlage übergeben und eine Vorlage "Bearbeiten" automatisch gerüstet haben:

Screenshot: Ansicht hinzufügen zum automatischen Gerüst für eine Bearbeitungsvorlage

Wenn wir auf die Schaltfläche "Hinzufügen" klicken, fügt Visual Studio eine neue Ansichtsvorlagendatei "Bearbeiten.aspx" für uns im Verzeichnis "\Views\Dinners" hinzu. Außerdem wird die neue Ansichtsvorlage "Edit.aspx" im Code-Editor geöffnet, die mit einer ersten Gerüstimplementierung "Bearbeiten" wie unten aufgefüllt wird:

Screenshot der neuen Bearbeitungsansichtsvorlage im Code-Editor.

Nehmen wir einige Änderungen am generierten Standardgerüst "Bearbeiten" vor, und aktualisieren Sie die Bearbeitungsansichtsvorlage so, dass sie den folgenden Inhalt enthält (wodurch einige der Eigenschaften entfernt werden, die wir nicht verfügbar machen möchten):

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Edit: <%=Html.Encode(Model.Title)%>
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Edit Dinner</h2>

    <%=Html.ValidationSummary("Please correct the errors and try again.") %>  
    
    <% using (Html.BeginForm()) { %>

        <fieldset>
            <p>
                <label for="Title">Dinner Title:</label>
                <%=Html.TextBox("Title") %>
                <%=Html.ValidationMessage("Title", "*") %>
            </p>
            <p>
                <label for="EventDate">EventDate:</label>
                <%=Html.TextBox("EventDate", String.Format("{0:g}", Model.EventDate))%>
                <%=Html.ValidationMessage("EventDate", "*") %>
            </p>
            <p>
                <label for="Description">Description:</label>
                <%=Html.TextArea("Description") %>
                <%=Html.ValidationMessage("Description", "*")%>
            </p>
            <p>
                <label for="Address">Address:</label>
                <%=Html.TextBox("Address") %>
                <%=Html.ValidationMessage("Address", "*") %>
            </p>
            <p>
                <label for="Country">Country:</label>
                <%=Html.TextBox("Country") %>               
                <%=Html.ValidationMessage("Country", "*") %>
            </p>
            <p>
                <label for="ContactPhone">ContactPhone #:</label>
                <%=Html.TextBox("ContactPhone") %>
                <%=Html.ValidationMessage("ContactPhone", "*") %>
            </p>
            <p>
                <label for="Latitude">Latitude:</label>
                <%=Html.TextBox("Latitude") %>
                <%=Html.ValidationMessage("Latitude", "*") %>
            </p>
            <p>
                <label for="Longitude">Longitude:</label>
                <%=Html.TextBox("Longitude") %>
                <%=Html.ValidationMessage("Longitude", "*") %>
            </p>
            <p>
                <input type="submit" value="Save"/>
            </p>
        </fieldset>
        
    <% } %>
    
</asp:Content>

Wenn wir die Anwendung ausführen und die URL "/Dinners/Edit/1" anfordern, wird die folgende Seite angezeigt:

Screenshot der Seite

Das von unserer Ansicht generierte HTML-Markup sieht wie folgt aus. Es handelt sich um standard-HTML – mit einem <Formularelement> , das eine HTTP POST-Datei an die URL /Dinners/Edit/1 ausführt, wenn die Schaltfläche "Save" <input type="submit"/> gedrückt wird. Für jede bearbeitbare Eigenschaft wurde ein HTML input type="text"/>-<Element ausgegeben:

Screenshot des generierten H T M L-Markups.

Html.BeginForm() und Html.TextBox() Html-Hilfsmethoden

Unsere Ansichtsvorlage "Edit.aspx" verwendet mehrere Html-Hilfsmethoden: Html.ValidationSummary(), Html.BeginForm(), Html.TextBox() und Html.ValidationMessage(). Zusätzlich zum Generieren von HTML-Markup für uns bieten diese Hilfsmethoden integrierte Fehlerbehandlung und Validierungsunterstützung.

Html.BeginForm()-Hilfsmethode

Die Html.BeginForm()-Hilfsmethode gibt das HTML-Formularelement <> in unserem Markup aus. In unserer Edit.aspx-Ansichtsvorlage werden Sie feststellen, dass wir bei Verwendung dieser Methode eine C#-Anweisung "using" anwenden. Die geöffnete geschweifte Klammer gibt den Anfang des <Formularinhalts> an, und die schließende geschweifte Klammer gibt das Ende des </form-Elements> an:

<% using (Html.BeginForm()) { %>

   <fieldset>
   
      <!-- Fields Omitted for Brevity -->
   
      <p>
         <input type="submit" value="Save"/>
      </p>
   </fieldset>
   
<% } %>

Wenn Sie den Ansatz der "using"-Anweisung für ein Szenario wie dieses als unnatürlich empfinden, können Sie alternativ eine Html.BeginForm() und Html.EndForm()-Kombination verwenden (die dasselbe tut):

<% Html.BeginForm();  %>

   <fieldset>
   
      <!-- Fields Omitted for Brevity -->
   
      <p>
          <input type="submit" value="Save"/>
      </p>
   </fieldset>
   
<% Html.EndForm(); %>

Wenn Html.BeginForm() ohne Parameter aufgerufen wird, wird ein Formularelement ausgegeben, das einen HTTP-POST-Vorgang an die URL der aktuellen Anforderung ausführt. Aus diesem Grund generiert die Ansicht Bearbeiten ein <element form action="/Dinners/Edit/1" method="post"> . Alternativ hätten wir explizite Parameter an Html.BeginForm() übergeben können, wenn wir beiträge an eine andere URL senden wollten.

Html.TextBox()-Hilfsmethode

Unsere Edit.aspx-Ansicht verwendet die Html.TextBox()-Hilfsmethode, um input type="text"/>-Elemente auszugeben<:

<%= Html.TextBox("Title") %>

Die obige Html.TextBox()-Methode verwendet einen einzelnen Parameter, der verwendet wird, um sowohl die id/name-Attribute des <input type="text"/> -Elements anzugeben, aus dem ausgegeben werden soll, als auch die Modelleigenschaft zum Auffüllen des Textfeldwerts. Das Dinner-Objekt, das wir an die Bearbeitungsansicht übergeben haben, hatte beispielsweise den Eigenschaftswert "Title" von ".NET Futures", und daher ruft unsere Html.TextBox("Title") -Methode die Ausgabe aus: <input id="Title" name="Title" type="text" value=".NET Futures" />.

Alternativ können wir den ersten Html.TextBox()-Parameter verwenden, um die ID/den Namen des Elements anzugeben, und dann explizit den Wert übergeben, der als zweiter Parameter verwendet werden soll:

<%= Html.TextBox("Title", Model.Title)%>

Häufig möchten wir eine benutzerdefinierte Formatierung für den ausgegebenen Wert ausführen. Die in .NET integrierte statische String.Format()-Methode ist für diese Szenarien nützlich. Unsere Edit.aspx-Ansichtsvorlage verwendet diese, um den EventDate-Wert (vom Typ DateTime) so zu formatieren, dass keine Sekunden für die Uhrzeit angezeigt werden:

<%= Html.TextBox("EventDate", String.Format("{0:g}", Model.EventDate)) %>

Ein dritter Parameter für Html.TextBox() kann optional verwendet werden, um zusätzliche HTML-Attribute auszugeben. Der folgende Codeausschnitt veranschaulicht, wie ein zusätzliches size="30"-Attribut und ein class="mycssclass"-Attribut für das <input type="text"/> -Element gerendert werden. Beachten Sie, wie wir den Namen des Klassenattributes mit einem "@"-Zeichen versehen, da "class" eine reservierte Schlüsselwort (keyword) in C# ist:

<%= Html.TextBox("Title", Model.Title, new { size=30, @class="myclass" } )%>

Implementieren der HTTP-POST-Aktionsmethode "Bearbeiten"

Wir haben jetzt die HTTP-GET-Version unserer Edit-Aktionsmethode implementiert. Wenn ein Benutzer die URL /Dinners/Edit/1 anfordert, erhält er eine HTML-Seite wie die folgende:

Screenshot der H T M L-Ausgabe, wenn der Benutzer ein Edit Dinner anfordert.

Wenn Sie die Schaltfläche "Speichern" drücken, wird ein Formular in die URL /Dinners/Edit/1 eingefügt, und die HTML-Eingabeformularwerte <> werden mithilfe des HTTP POST-Verbs übermittelt. Implementieren wir nun das HTTP POST-Verhalten unserer Bearbeitungsaktionsmethode, die das Speichern des Dinners behandelt.

Zunächst fügen Sie dem DinnersController eine überladene Aktionsmethode "Bearbeiten" hinzu, die über das Attribut "AcceptVerbs" verfügt, das angibt, dass http POST-Szenarien verarbeitet werden:

//
// POST: /Dinners/Edit/2

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
   ...
}

Wenn das [AcceptVerbs]-Attribut auf überladene Aktionsmethoden angewendet wird, verarbeitet ASP.NET MVC automatisch Die Verteilung von Anforderungen an die entsprechende Aktionsmethode hängt vom eingehenden HTTP-Verb ab. HTTP POST-Anforderungen an /Dinners/Edit/[id] URLs werden an die oben genannte Edit-Methode gesendet, während alle anderen HTTP-Verbanforderungen an /Dinners/Edit/[id]- URLs an die erste von uns implementierte Edit-Methode (die kein Attribut hatte [AcceptVerbs] ).

Nebenthema: Warum über HTTP-Verben unterscheiden?
Sie fragen sich vielleicht: Warum verwenden wir eine einzelne URL und unterscheiden ihr Verhalten über das HTTP-Verb? Warum haben Sie nicht einfach zwei separate URLs, um das Laden und Speichern von Bearbeitungsänderungen zu verarbeiten? Beispiel: /Dinners/Edit/[id] zum Anzeigen des Anfangsformulars und /Dinners/Save/[id] zum Behandeln des Formularbeitrags zum Speichern? Der Nachteil bei der Veröffentlichung von zwei separaten URLs besteht darin, dass der Endbenutzer in fällen, in denen wir in /Dinners/Save/2 posten und dann das HTML-Formular aufgrund eines Eingabefehlers erneut anzeigen müssen, am Ende die URL /Dinners/Save/2 in der Adressleiste seines Browsers hat (da dies die URL war, an die das Formular gepostet wurde). Wenn der Endbenutzer diese wieder angezeigte Seite in seiner Browser-Favoritenliste als Lesezeichen markiert oder die URL kopiert/einfüge und per E-Mail an einen Freund sendet, speichert er am Ende eine URL, die in Zukunft nicht funktioniert (da diese URL von post-Werten abhängt). Durch das Verfügbarmachen einer einzelnen URL (z. B. /Dinners/Edit/[id]) und die Unterscheidung der Verarbeitung durch das HTTP-Verb ist es für Endbenutzer sicher, die Bearbeitungsseite mit Einem Lesezeichen zu versehen und/oder die URL an andere Personen zu senden.

Abrufen von Formularbeitragswerten

Es gibt eine Vielzahl von Möglichkeiten, wie wir auf bereitgestellte Formularparameter in unserer HTTP POST -Methode "Edit" zugreifen können. Ein einfacher Ansatz besteht darin, einfach die Request-Eigenschaft für die Controller-Basisklasse zu verwenden, um auf die Formularsammlung zuzugreifen und die bereitgestellten Werte direkt abzurufen:

//
// POST: /Dinners/Edit/2

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {

    // Retrieve existing dinner
    Dinner dinner = dinnerRepository.GetDinner(id);

    // Update dinner with form posted values
    dinner.Title = Request.Form["Title"];
    dinner.Description = Request.Form["Description"];
    dinner.EventDate = DateTime.Parse(Request.Form["EventDate"]);
    dinner.Address = Request.Form["Address"];
    dinner.Country = Request.Form["Country"];
    dinner.ContactPhone = Request.Form["ContactPhone"];

    // Persist changes back to database
    dinnerRepository.Save();

    // Perform HTTP redirect to details page for the saved Dinner
    return RedirectToAction("Details", new { id = dinner.DinnerID });
}

Der obige Ansatz ist jedoch etwas ausführlich, insbesondere wenn wir Fehlerbehandlungslogik hinzufügen.

Ein besserer Ansatz für dieses Szenario besteht darin, die integrierte UpdateModel() -Hilfsmethode für die Controller-Basisklasse zu nutzen. Es unterstützt das Aktualisieren der Eigenschaften eines Objekts, das wir mit den parametern des eingehenden Formulars übergeben. Es verwendet Reflektion, um die Eigenschaftsnamen für das Objekt zu bestimmen, und dann konvertiert und weist ihnen basierend auf den vom Client übermittelten Eingabewerten automatisch Werte zu.

Wir könnten die UpdateModel()-Methode verwenden, um unsere HTTP-POST-Bearbeitungsaktion mithilfe des folgenden Codes zu vereinfachen:

//
// POST: /Dinners/Edit/2

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    UpdateModel(dinner);

    dinnerRepository.Save();

    return RedirectToAction("Details", new { id = dinner.DinnerID });
}

Wir können jetzt die URL /Dinners/Edit/1 aufrufen und den Titel unseres Dinners ändern:

Screenshot der Seite

Wenn wir auf die Schaltfläche "Speichern" klicken, führen wir einen Formularbeitrag für unsere Bearbeitungsaktion aus, und die aktualisierten Werte werden in der Datenbank beibehalten. Wir werden dann zur Detail-URL für das Dinner weitergeleitet (in der die neu gespeicherten Werte angezeigt werden):

Screenshot der Detail-URL für das Abendessen.

Behandeln von Bearbeitungsfehlern

Unsere aktuelle HTTP-POST-Implementierung funktioniert einwandfrei – außer wenn Fehler auftreten.

Wenn ein Benutzer beim Bearbeiten eines Formulars einen Fehler macht, müssen wir sicherstellen, dass das Formular mit einer informativen Fehlermeldung erneut angezeigt wird, die ihn anleitet, es zu beheben. Dies schließt Fälle ein, in denen ein Endbenutzer falsche Eingaben veröffentlicht (z. B. eine falsch formatierte Datumszeichenfolge), sowie Fälle, in denen das Eingabeformat gültig ist, aber ein Verstoß gegen die Geschäftsregel vorliegt. Wenn Fehler auftreten, sollte das Formular die Eingabedaten beibehalten, die der Benutzer ursprünglich eingegeben hat, damit er seine Änderungen nicht manuell erneut ausfüllen muss. Dieser Vorgang sollte so oft wie nötig wiederholt werden, bis das Formular erfolgreich abgeschlossen ist.

ASP.NET MVC enthält einige nützliche integrierte Features, die die Fehlerbehandlung und das Erneute Anzeigen von Formularen vereinfachen. Um diese Features in Aktion zu sehen, aktualisieren wir unsere Edit-Aktionsmethode mit dem folgenden Code:

//
// POST: /Dinners/Edit/2

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    try {

        UpdateModel(dinner);

        dinnerRepository.Save();

        return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch {

        foreach (var issue in dinner.GetRuleViolations()) {
            ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
        }

        return View(dinner);
    }
}

Der obige Code ähnelt unserer vorherigen Implementierung– mit der Ausnahme, dass wir jetzt einen Try/Catch-Fehlerbehandlungsblock um unsere Arbeit umschließen. Wenn eine Ausnahme auftritt, entweder beim Aufrufen von UpdateModel() oder beim Versuch, dinnerRepository zu speichern (wodurch eine Ausnahme ausgelöst wird, wenn das Zu speichernde Dinner-Objekt aufgrund eines Regelverstoßes innerhalb unseres Modells ungültig ist), wird der Block zur Fehlerbehandlung auffangen ausgeführt. Darin werden alle Regelverstöße, die im Dinner-Objekt vorhanden sind, durchschleifen und einem ModelState-Objekt hinzugefügt (das wir in Kürze besprechen werden). Anschließend wird die Ansicht erneut angezeigt.

Um dies zu sehen, führen wir die Anwendung erneut aus, bearbeiten Sie ein Dinner, und ändern Sie es in einen leeren Titel, ein EventDate von "BOGUS", und verwenden Sie eine Telefonnummer im Vereinigten Königreich mit dem Land/Region-Wert USA. Wenn wir die Schaltfläche "Speichern" drücken, kann unsere HTTP POST Edit-Methode das Dinner nicht speichern (da Fehler vorliegen) und zeigt das Formular erneut an:

Screenshot: Erneute Anzeige des Formulars aufgrund von Fehlern mit der H T T P S P O S T T Edit-Methode.

Unsere Anwendung verfügt über eine anständige Fehlererfahrung. Die Textelemente mit der ungültigen Eingabe sind rot hervorgehoben, und dem Endbenutzer werden Validierungsfehlermeldungen angezeigt. Das Formular behält auch die Eingabedaten bei, die der Benutzer ursprünglich eingegeben hat, sodass er nichts erneut ausfüllen muss.

Wie, fragen Sie sich vielleicht, ist das passiert? Wie haben sich die Textfelder Title, EventDate und ContactPhone rot hervorgehoben und wissen, dass die ursprünglich eingegebenen Benutzerwerte ausgegeben werden? Und wie wurden Fehlermeldungen in der Liste oben angezeigt? Die gute Nachricht ist, dass dies nicht durch Zauberei geschehen ist , sondern weil wir einige der integrierten ASP.NET MVC-Features verwendet haben, die Eingabevalidierungs- und Fehlerbehandlungsszenarien einfach machen.

Grundlegendes zu ModelState und den Validierungs-HTML-Hilfsmethoden

Controllerklassen verfügen über eine "ModelState"-Eigenschaftsauflistung, die eine Möglichkeit bietet, anzugeben, dass Fehler bei der Übergabe eines Modellobjekts an einen View-Objekt vorliegen. Fehlereinträge innerhalb der ModelState-Auflistung identifizieren den Namen der Modelleigenschaft mit dem Problem (z. B. "Title", "EventDate" oder "ContactPhone") und lassen die Angabe einer menschlichen Fehlermeldung zu (z. B. "Title is required").

Die UpdateModel() -Hilfsmethode füllt die ModelState-Auflistung automatisch auf, wenn beim Versuch, Den Eigenschaften des Modellobjekts Formularwerte zuzuweisen, Fehler auftreten. Die EventDate-Eigenschaft des Dinner-Objekts ist beispielsweise vom Typ DateTime. Wenn die UpdateModel()-Methode im obigen Szenario nicht den Zeichenfolgenwert "BOGUS" zuweisen konnte, hat die UpdateModel()-Methode der ModelState-Auflistung einen Eintrag hinzugefügt, der angibt, dass bei dieser Eigenschaft ein Zuweisungsfehler aufgetreten ist.

Entwickler können auch Code schreiben, um der ModelState-Auflistung explizit Fehlereinträge hinzuzufügen, wie wir dies unten in unserem Fehlerbehandlungsblock "catch" tun, der die ModelState-Auflistung mit Einträgen auffüllt, die auf den aktiven Regelverstößen im Dinner-Objekt basieren:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    try {
    
        UpdateModel(dinner);

        dinnerRepository.Save();

        return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch {
    
        foreach (var issue in dinner.GetRuleViolations()) {
            ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
        }

        return View(dinner);
    }
}

Html-Hilfsprogrammintegration mit ModelState

HTML-Hilfsmethoden – z. B. Html.TextBox() – überprüfen die ModelState-Auflistung beim Rendern der Ausgabe. Wenn ein Fehler für das Element vorhanden ist, rendern sie den vom Benutzer eingegebenen Wert und eine CSS-Fehlerklasse.

In unserer Ansicht "Bearbeiten" verwenden wir beispielsweise die Html.TextBox()-Hilfsmethode, um das EventDate des Dinner-Objekts zu rendern:

<%= Html.TextBox("EventDate", String.Format("{0:g}", Model.EventDate)) %>

Wenn die Ansicht im Fehlerszenario gerendert wurde, überprüfte die Html.TextBox()-Methode die ModelState-Auflistung, um festzustellen, ob fehler im Zusammenhang mit der Eigenschaft "EventDate" des Dinner-Objekts aufgetreten sind. Als festgestellt wurde, dass ein Fehler aufgetreten ist, hat es die übermittelte Benutzereingabe ("BOGUS") als Wert gerendert und dem <generierten Input type="textbox"/> -Markup eine CSS-Fehlerklasse hinzugefügt:

<input class="input-validation-error"id="EventDate" name="EventDate" type="text" value="BOGUS"/>

Sie können die Darstellung der CSS-Fehlerklasse so anpassen, dass sie beliebig aussieht. Die CSS-Standardfehlerklasse "input-validation-error" ist im Stylesheet \content\site.css definiert und sieht wie folgt aus:

.input-validation-error
{
    border: 1px solid #ff0000;
    background-color: #ffeeee;
}

Diese CSS-Regel hat dazu geführt, dass unsere ungültigen Eingabeelemente wie unten hervorgehoben wurden:

Screenshot der hervorgehobenen ungültigen Eingabeelemente.

Html.ValidationMessage()-Hilfsmethode

Die Html.ValidationMessage()-Hilfsmethode kann verwendet werden, um die ModelState-Fehlermeldung auszugeben, die einer bestimmten Modelleigenschaft zugeordnet ist:

<%= Html.ValidationMessage("EventDate")%>

Die obigen Codeausgaben: <span class="field-validation-error"> Der Wert 'BOGUS' ist ungültig</span>

Die Html.ValidationMessage()-Hilfsmethode unterstützt auch einen zweiten Parameter, mit dem Entwickler die angezeigte Fehlermeldung überschreiben können:

<%= Html.ValidationMessage("EventDate","*") %>

Die obigen Codeausgaben: <span class="field-validation-error">*</span> anstelle des Standardfehlertexts, wenn ein Fehler für die EventDate-Eigenschaft vorhanden ist.

Html.ValidationSummary() Helper-Methode

Die Html.ValidationSummary()-Hilfsmethode kann verwendet werden, um eine Zusammenfassende Fehlermeldung zu rendern, begleitet von einer <ul li/></ul-Liste> aller detaillierten Fehlermeldungen in der ModelState-Auflistung><:

Screenshot der Liste aller detaillierten Fehlermeldungen in der ModelState-Auflistung.

Die Html.ValidationSummary()-Hilfsmethode verwendet einen optionalen Zeichenfolgenparameter, der eine Zusammenfassende Fehlermeldung definiert, die oberhalb der Liste der detaillierten Fehler angezeigt werden soll:

<%= Html.ValidationSummary("Please correct the errors and try again.") %>

Sie können optional CSS verwenden, um zu überschreiben, wie die Fehlerliste aussieht.

Verwenden einer AddRuleViolations-Hilfsmethode

Unsere anfängliche HTTP-POST Edit-Implementierung verwendet eine foreach-Anweisung innerhalb des Catch-Blocks, um die Regelverletzungen des Dinner-Objekts zu durchlaufen und sie der ModelState-Auflistung des Controllers hinzuzufügen:

catch {
        foreach (var issue in dinner.GetRuleViolations()) {
            ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
        }

        return View(dinner);
    }

Wir können diesen Code etwas übersichtlicher gestalten, indem wir dem NerdDinner-Projekt eine "ControllerHelpers"-Klasse hinzufügen und darin eine Erweiterungsmethode "AddRuleViolations" implementieren, die der ASP.NET MVC ModelStateDictionary-Klasse eine Hilfsmethode hinzufügt. Diese Erweiterungsmethode kann die Logik kapseln, die zum Auffüllen von ModelStateDictionary mit einer Liste von RuleViolation-Fehlern erforderlich ist:

public static class ControllerHelpers {

   public static void AddRuleViolations(this ModelStateDictionary modelState, IEnumerable<RuleViolation> errors) {
   
       foreach (RuleViolation issue in errors) {
           modelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
       }
   }
}

Wir können dann unsere HTTP-POST Edit-Aktionsmethode aktualisieren, um diese Erweiterungsmethode zu verwenden, um die ModelState-Auflistung mit unseren Dinner Rule-Verstößen aufzufüllen.

Abschließen der Implementierungen der Aktionsmethode "Bearbeiten"

Der folgende Code implementiert die gesamte Controllerlogik, die für unser Bearbeitungsszenario erforderlich ist:

//
// GET: /Dinners/Edit/2

public ActionResult Edit(int id) {

    Dinner dinner = dinnerRepository.GetDinner(id);
    
    return View(dinner);
}

//
// POST: /Dinners/Edit/2

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    try {
    
        UpdateModel(dinner);

        dinnerRepository.Save();

        return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch {
    
        ModelState.AddRuleViolations(dinner.GetRuleViolations());

        return View(dinner);
    }
}

Das Schöne an unserer Edit-Implementierung ist, dass weder unsere Controllerklasse noch unsere Ansichtsvorlage etwas über die spezifischen Validierungs- oder Geschäftsregeln wissen müssen, die von unserem Dinner-Modell erzwungen werden. Wir können in Zukunft zusätzliche Regeln zu unserem Modell hinzufügen und müssen keine Codeänderungen an unserem Controller oder unserer Ansicht vornehmen, damit sie unterstützt werden. Dies bietet uns die Flexibilität, unsere Anwendungsanforderungen in Zukunft mit einem Minimum an Codeänderungen problemlos weiterzuentwickeln.

Erstellen des Supports

Wir haben die Implementierung des "Bearbeiten"-Verhaltens unserer DinnersController-Klasse abgeschlossen. Lassen Sie uns nun mit der Implementierung der Unterstützung "Erstellen" fortfahren, die es Benutzern ermöglicht, neue Dinner hinzuzufügen.

Die HTTP-GET Create Action-Methode

Wir beginnen damit, das HTTP-Verhalten "GET" unserer Create-Aktionsmethode zu implementieren. Diese Methode wird aufgerufen, wenn jemand die URL /Dinners/Create aufruft . Unsere Implementierung sieht wie folgt aus:

//
// GET: /Dinners/Create

public ActionResult Create() {

    Dinner dinner = new Dinner() {
        EventDate = DateTime.Now.AddDays(7)
    };

    return View(dinner);
}

Der obige Code erstellt ein neues Dinner-Objekt und weist dessen EventDate-Eigenschaft auf eine Woche in der Zukunft zu. Anschließend wird eine Ansicht gerendert, die auf dem neuen Dinner-Objekt basiert. Da wir keinen Namen explizit an die View() -Hilfsmethode übergeben haben, wird der konventionsbasierte Standardpfad verwendet, um die Ansichtsvorlage aufzulösen: /Views/Dinners/Create.aspx.

Nun erstellen wir diese Ansichtsvorlage. Dazu klicken Sie mit der rechten Maustaste in die Aktionsmethode Erstellen, und wählen Sie den Kontextmenübefehl "Ansicht hinzufügen" aus. Im Dialogfeld "Ansicht hinzufügen" geben wir an, dass wir ein Dinner-Objekt an die Ansichtsvorlage übergeben, und wählen ein automatisches Gerüst für eine "Erstellen"-Vorlage aus:

Screenshot: Ansicht hinzufügen, um eine Ansichtsvorlage zu erstellen.

Wenn wir auf die Schaltfläche "Hinzufügen" klicken, speichert Visual Studio eine neue gerüstbasierte "Create.aspx"-Ansicht im Verzeichnis "\Views\Dinners" und öffnet sie in der IDE:

Screenshot: EE zum Bearbeiten des Codes.

Nehmen wir einige Änderungen an der standardmäßigen "create"-Gerüstdatei vor, die für uns generiert wurde, und ändern Sie sie so, dass sie wie folgt aussieht:

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
     Host a Dinner
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Host a Dinner</h2>

    <%=Html.ValidationSummary("Please correct the errors and try again.") %>
 
    <% using (Html.BeginForm()) {%>
  
        <fieldset>
            <p>
                <label for="Title">Title:</label>
                <%= Html.TextBox("Title") %>
                <%= Html.ValidationMessage("Title", "*") %>
            </p>
            <p>
                <label for="EventDate">EventDate:</label>
                <%=Html.TextBox("EventDate") %>
                <%=Html.ValidationMessage("EventDate", "*") %>
            </p>
            <p>
                <label for="Description">Description:</label>
                <%=Html.TextArea("Description") %>
                <%=Html.ValidationMessage("Description", "*") %>
            </p>
            <p>
                <label for="Address">Address:</label>
                <%=Html.TextBox("Address") %>
                <%=Html.ValidationMessage("Address", "*") %>
            </p>
            <p>
                <label for="Country">Country:</label>
                <%=Html.TextBox("Country") %>
                <%=Html.ValidationMessage("Country", "*") %>
            </p>
            <p>
                <label for="ContactPhone">ContactPhone:</label>
                <%=Html.TextBox("ContactPhone") %>
                <%=Html.ValidationMessage("ContactPhone", "*") %>
            </p>            
            <p>
                <label for="Latitude">Latitude:</label>
                <%=Html.TextBox("Latitude") %>
                <%=Html.ValidationMessage("Latitude", "*") %>
            </p>
            <p>
                <label for="Longitude">Longitude:</label>
                <%=Html.TextBox("Longitude") %>
                <%=Html.ValidationMessage("Longitude", "*") %>
            </p>
            <p>
                <input type="submit" value="Save"/>
            </p>
        </fieldset>
    <% } 
%>
</asp:Content>

Und wenn wir jetzt unsere Anwendung ausführen und im Browser auf die URL "/Dinners/Create" zugreifen, wird die Benutzeroberfläche wie unten aus unserer Implementierung der Aktion Erstellen gerendert:

Screenshot der Implementierung einer Aktion erstellen, wenn wir unsere Anwendung ausführen und auf die U R L von Dinners zugreifen.

Implementieren der HTTP-POST Create Action-Methode

Wir haben die HTTP-GET-Version unserer Create-Aktionsmethode implementiert. Wenn ein Benutzer auf die Schaltfläche "Speichern" klickt, führt er einen Formularbeitrag an die /Dinners/Create-URL aus und übermittelt die HTML-Eingabeformularwerte <> mithilfe des HTTP POST-Verbs.

Nun implementieren wir das HTTP POST-Verhalten unserer Create-Aktionsmethode. Zunächst fügen Wir dem DinnersController eine überladene Aktionsmethode "Create" hinzu, die über ein "AcceptVerbs"-Attribut verfügt, das angibt, dass http POST-Szenarien verarbeitet werden:

//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create() {
    ...
}

Es gibt eine Vielzahl von Möglichkeiten, wie wir in unserer HTTP-POST-fähigen "Create"-Methode auf die bereitgestellten Formularparameter zugreifen können.

Ein Ansatz besteht darin, ein neues Dinner-Objekt zu erstellen und dann die UpdateModel() -Hilfsmethode (wie bei der Edit-Aktion) zu verwenden, um es mit den bereitgestellten Formularwerten aufzufüllen. Wir können es dann zu unserem DinnerRepository hinzufügen, in der Datenbank beibehalten und den Benutzer zu unserer Aktion Details umleiten, um das neu erstellte Dinner mithilfe des folgenden Codes anzuzeigen:

//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create() {

    Dinner dinner = new Dinner();

    try {
    
        UpdateModel(dinner);

        dinnerRepository.Add(dinner);
        dinnerRepository.Save();

        return RedirectToAction("Details", new {id=dinner.DinnerID});
    }
    catch {
    
        ModelState.AddRuleViolations(dinner.GetRuleViolations());

        return View(dinner);
    }
}

Alternativ können wir einen Ansatz verwenden, bei dem die Create()-Aktionsmethode ein Dinner-Objekt als Methodenparameter verwendet. ASP.NET MVC instanziieren dann automatisch ein neues Dinner-Objekt für uns, füllen seine Eigenschaften mithilfe der Formulareingaben auf und übergeben es an unsere Aktionsmethode:

//
//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner) {

    if (ModelState.IsValid) {

        try {
            dinner.HostedBy = "SomeUser";

            dinnerRepository.Add(dinner);
            dinnerRepository.Save();

            return RedirectToAction("Details", new {id = dinner.DinnerID });
        }
        catch {        
            ModelState.AddRuleViolations(dinner.GetRuleViolations());
        }
    }
    
    return View(dinner);
}

Mit der obigen Aktionsmethode wird überprüft, ob das Dinner-Objekt erfolgreich mit den Formularpostwerten aufgefüllt wurde, indem die ModelState.IsValid-Eigenschaft überprüft wird. Dadurch wird false zurückgegeben, wenn Probleme bei der Eingabekonvertierung auftreten (z. B. eine Zeichenfolge von "BOGUS" für die EventDate-Eigenschaft), und wenn Probleme auftreten, zeigt unsere Aktionsmethode das Formular erneut an.

Wenn die Eingabewerte gültig sind, versucht die Aktionsmethode, das neue Dinner im DinnerRepository hinzuzufügen und zu speichern. Es umschließt diese Arbeit in einem try/catch-Block und zeigt das Formular erneut an, wenn Verstöße gegen Geschäftsregeln vorliegen (was dazu führen würde, dass die dinnerRepository.Save()-Methode eine Ausnahme auslöst).

Um dieses Fehlerbehandlungsverhalten in Aktion zu sehen, können wir die URL /Dinners/Create anfordern und Details zu einem neuen Dinner ausfüllen. Falsche Eingaben oder Werte führen dazu, dass das Formular zum Erstellen erneut angezeigt wird und die Fehler wie unten hervorgehoben sind:

Screenshot: Erneut angezeigtes Formular mit hervorgehobenen Fehlern.

Beachten Sie, dass für das Formular Erstellen genau die gleichen Validierungs- und Geschäftsregeln gelten wie für das Formular Bearbeiten. Dies liegt daran, dass unsere Validierungs- und Geschäftsregeln im Modell definiert und nicht in die Benutzeroberfläche oder den Controller der Anwendung eingebettet wurden. Dies bedeutet, dass wir unsere Validierungs- oder Geschäftsregeln später an einem zentralen Ort ändern/weiterentwickeln können und sie in unserer gesamten Anwendung anwenden lassen können. Wir müssen keinen Code innerhalb unserer Edit- oder Create-Aktionsmethoden ändern, um automatisch neue Regeln oder Änderungen an vorhandenen Regeln zu berücksichtigen.

Wenn wir die Eingabewerte korrigieren und erneut auf die Schaltfläche "Speichern" klicken, ist unsere Ergänzung zum DinnerRepository erfolgreich, und ein neues Dinner wird der Datenbank hinzugefügt. Wir werden dann zur URL /Dinners/Details/[id] weitergeleitet, wo uns Details zum neu erstellten Dinner angezeigt werden:

Screenshot des neu erstellten Dinners.

Support löschen

Nun fügen wir unserem DinnersController die Unterstützung für "Löschen" hinzu.

Die HTTP-GET Delete Action-Methode

Wir beginnen mit der Implementierung des HTTP GET-Verhaltens unserer Löschaktionsmethode. Diese Methode wird aufgerufen, wenn jemand die URL /Dinners/Delete/[id] aufruft. Im Folgenden finden Sie die Implementierung:

//
// HTTP GET: /Dinners/Delete/1

public ActionResult Delete(int id) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (dinner == null)
         return View("NotFound");
    else
        return View(dinner);
}

Die Aktionsmethode versucht, das zu löschende Dinner abzurufen. Wenn das Dinner vorhanden ist, wird eine Ansicht basierend auf dem Dinner-Objekt gerendert. Wenn das Objekt nicht vorhanden ist (oder bereits gelöscht wurde), gibt es einen View-Wert zurück, der die Ansichtsvorlage "NotFound" rendert, die wir zuvor für die Aktionsmethode "Details" erstellt haben.

Wir können die Ansichtsvorlage "Löschen" erstellen, indem Sie mit der rechten Maustaste in die Aktionsmethode Löschen klicken und den Kontextmenübefehl "Ansicht hinzufügen" auswählen. Im Dialogfeld "Ansicht hinzufügen" geben wir an, dass wir ein Dinner-Objekt als Modell an unsere Ansichtsvorlage übergeben und eine leere Vorlage erstellen möchten:

Screenshot: Erstellen der Vorlage

Wenn wir auf die Schaltfläche "Hinzufügen" klicken, fügt Visual Studio eine neue Ansichtsvorlagendatei "Delete.aspx" für uns in unserem Verzeichnis "\Views\Dinners" hinzu. Wir fügen der Vorlage HTML und Code hinzu, um einen Löschbestätigungsbildschirm wie unten zu implementieren:

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Delete Confirmation:  <%=Html.Encode(Model.Title) %>
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2>
        Delete Confirmation
    </h2>

    <div>
        <p>Please confirm you want to cancel the dinner titled: 
           <i> <%=Html.Encode(Model.Title) %>? </i> 
        </p>
    </div>
    
    <% using (Html.BeginForm()) {  %>
        <input name="confirmButton" type="submit" value="Delete" />        
    <% } %>
     
</asp:Content>

Der obige Code zeigt den Titel des zu löschenden Dinners an und gibt ein <Formularelement> aus, das einen POST-Vorgang an die URL /Dinners/Delete/[id] ausführt, wenn der Endbenutzer darauf auf die Schaltfläche "Löschen" klickt.

Wenn wir unsere Anwendung ausführen und auf die URL "/Dinners/Delete/[id]" für ein gültiges Dinner-Objekt zugreifen, wird die Benutzeroberfläche wie folgt gerendert:

Screenshot der Bestätigung des Dinnerlöschvorgangs U I in der Aktionsmethode H T T P G E T T Löschen.

Nebenthema: Warum machen wir einen POST?
Sie fragen sich vielleicht: Warum haben wir uns die Mühe gemacht, ein <Formular> auf unserem Bestätigungsbildschirm löschen zu erstellen? Warum verwenden Sie nicht einfach einen Standardlink, um einen Link zu einer Aktionsmethode zu erstellen, die den eigentlichen Löschvorgang ausführt? Der Grund ist, dass wir darauf achten möchten, uns vor Webcrawlern und Suchmaschinen zu schützen, die unsere URLs entdecken und versehentlich dazu führen, dass Daten gelöscht werden, wenn sie den Links folgen. HTTP-GET-basierte URLs gelten als "sicher", damit sie auf/durchforsten können, und sie sollten http-POST-URLs nicht folgen. Eine gute Regel besteht darin, sicherzustellen, dass Sie immer destruktive Vorgänge oder Datenmodifizierungsvorgänge hinter HTTP-POST-Anforderungen setzen.

Implementieren der HTTP-POST Delete Action-Methode

Wir haben jetzt die HTTP-GET-Version unserer Delete-Aktionsmethode implementiert, die einen Löschbestätigungsbildschirm anzeigt. Wenn ein Endbenutzer auf die Schaltfläche "Löschen" klickt, führt er einen Formularbeitrag an die URL /Dinners/Dinner/[id] aus.

Nun implementieren wir das HTTP-Verhalten "POST" der Delete-Aktionsmethode mithilfe des folgenden Codes:

// 
// HTTP POST: /Dinners/Delete/1

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id, string confirmButton) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (dinner == null)
        return View("NotFound");

    dinnerRepository.Delete(dinner);
    dinnerRepository.Save();

    return View("Deleted");
}

Die HTTP-POST-Version unserer Delete-Aktionsmethode versucht, das zu löschende Dinner-Objekt abzurufen. Wenn sie nicht gefunden werden kann (weil sie bereits gelöscht wurde), wird die Vorlage "NotFound" gerendert. Wenn das Dinner gefunden wird, wird es aus dem DinnerRepository gelöscht. Anschließend wird eine "Gelöschte" Vorlage gerendert.

Um die Vorlage "Gelöscht" zu implementieren, klicken wir mit der rechten Maustaste in die Aktionsmethode und wählen das Kontextmenü "Ansicht hinzufügen" aus. Wir nennen die Ansicht "Deleted" und lassen sie als leere Vorlage verwenden (und kein stark typisiertes Modellobjekt verwenden). Anschließend fügen wir einige HTML-Inhalte hinzu:

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Dinner Deleted
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Dinner Deleted</h2>

    <div>
        <p>Your dinner was successfully deleted.</p>
    </div>
    
    <div>
        <p><a href="/dinners">Click for Upcoming Dinners</a></p>
    </div>
    
</asp:Content>

Und wenn wir jetzt unsere Anwendung ausführen und auf die URL "/Dinners/Delete/[id]" für ein gültiges Dinner-Objekt zugreifen, wird der Bestätigungsbildschirm für das Löschen von Dinner wie folgt gerendert:

Screenshot des Bestätigungsbildschirms

Wenn wir auf die Schaltfläche "Löschen" klicken, wird eine HTTP-POST-Datei für die URL /Dinners/Delete/[id] ausgeführt, wodurch das Dinner aus unserer Datenbank gelöscht wird, und die Ansichtsvorlage "Gelöscht" wird angezeigt:

Screenshot der Vorlage

Modellbindungssicherheit

Wir haben zwei verschiedene Möglichkeiten zur Verwendung der integrierten Modellbindungsfeatures von ASP.NET MVC erläutert. Die erste verwendet die UpdateModel()-Methode, um Eigenschaften für ein vorhandenes Modellobjekt zu aktualisieren, und die zweite mit ASP.NET MVC-Unterstützung für die Übergabe von Modellobjekten als Aktionsmethodenparameter. Beide Techniken sind sehr leistungsfähig und äußerst nützlich.

Diese Macht bringt auch Verantwortung mit sich. Es ist wichtig, immer paranoid bezüglich der Sicherheit zu sein, wenn Benutzereingaben akzeptiert werden, und dies gilt auch, wenn Objekte an Formulareingaben gebunden werden. Sie sollten darauf achten, alle vom Benutzer eingegebenen Werte immer html zu codieren, um ANGRIFFE mit HTML- und JavaScript-Einschleusung zu vermeiden, und achten Sie auf SQL-Einschleusungsangriffe (Hinweis: Wir verwenden LINQ to SQL für unsere Anwendung, die parameter automatisch codiert, um diese Arten von Angriffen zu verhindern). Sie sollten sich nie allein auf die clientseitige Validierung verlassen und immer serverseitige Validierung verwenden, um sich vor Hackern zu schützen, die Versuchen, Ihnen gefälschte Werte zu senden.

Ein zusätzliches Sicherheitselement, das Sie bei der Verwendung der Bindungsfeatures von ASP.NET MVC berücksichtigen, ist der Bereich der Objekte, die Sie binden. Insbesondere möchten Sie sicherstellen, dass Sie die Sicherheitsauswirkungen der Eigenschaften verstehen, die Sie binden lassen, und sicherstellen, dass nur die Eigenschaften aktualisiert werden, die tatsächlich von einem Endbenutzer aktualisierbar sein sollten.

Standardmäßig versucht die UpdateModel()-Methode, alle Eigenschaften des Modellobjekts zu aktualisieren, die den werten des eingehenden Formularparameters entsprechen. Ebenso können objekte, die standardmäßig als Aktionsmethodenparameter übergeben werden, alle eigenschaften über Formularparameter festgelegt werden.

Sperren der Bindung pro Nutzung

Sie können die Bindungsrichtlinie auf Nutzungsbasis sperren, indem Sie eine explizite "Includeliste" mit Eigenschaften bereitstellen, die aktualisiert werden können. Hierzu können Sie einen zusätzlichen Zeichenfolgenarrayparameter wie folgt an die UpdateModel()-Methode übergeben:

string[] allowedProperties = new[]{ "Title","Description", 
                                    "ContactPhone", "Address",
                                    "EventDate", "Latitude", 
                                    "Longitude"};
                                    
UpdateModel(dinner, allowedProperties);

Objekte, die als Aktionsmethodenparameter übergeben werden, unterstützen auch ein [Bind]-Attribut, mit dem eine "Includeliste" mit zulässigen Eigenschaften wie unten angegeben werden kann:

//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create( [Bind(Include="Title,Address")] Dinner dinner ) {
    ...
}

Sperren der Bindung auf Typbasis

Sie können die Bindungsregeln auch typbezogen sperren. Auf diese Weise können Sie die Bindungsregeln einmal angeben und sie dann in allen Szenarien (einschließlich UpdateModel und Aktionsmethodenparameterszenarien) auf allen Controllern und Aktionsmethoden anwenden lassen.

Sie können die Bindungsregeln pro Typ anpassen, indem Sie einem Typ ein [Bind]-Attribut hinzufügen oder es in der Datei Global.asax der Anwendung registrieren (nützlich für Szenarien, in denen Sie den Typ nicht besitzen). Anschließend können Sie die Include- und Exclude-Eigenschaften des Bind-Attributs verwenden, um zu steuern, welche Eigenschaften für die jeweilige Klasse oder Schnittstelle gebunden werden können.

Wir verwenden diese Technik für die Dinner-Klasse in unserer NerdDinner-Anwendung und fügen ihr ein [Bind]-Attribut hinzu, das die Liste der bindbaren Eigenschaften auf Folgendes beschränkt:

[Bind(Include="Title,Description,EventDate,Address,Country,ContactPhone,Latitude,Longitude")]
public partial class Dinner {
   ...
}

Beachten Sie, dass wir nicht zulassen, dass die RSVPs-Auflistung per Bindung bearbeitet wird, noch lassen wir zu, dass die DinnerID- oder HostedBy-Eigenschaften per Bindung festgelegt werden. Aus Sicherheitsgründen bearbeiten wir diese spezifischen Eigenschaften stattdessen nur mithilfe von explizitem Code innerhalb unserer Aktionsmethoden.

CRUD-Wrap-Up

ASP.NET MVC enthält eine Reihe integrierter Features, die bei der Implementierung von Formularpostingszenarien helfen. Wir haben eine Vielzahl dieser Features verwendet, um zusätzlich zu unserem DinnerRepository CRUD UI-Unterstützung bereitzustellen.

Wir verwenden einen modellorientierten Ansatz, um unsere Anwendung zu implementieren. Dies bedeutet, dass unsere gesamte Validierungs- und Geschäftsregellogik innerhalb unserer Modellebene und nicht innerhalb unserer Controller oder Ansichten definiert ist. Weder unsere Controller-Klasse noch unsere Ansichtsvorlagen wissen etwas über die spezifischen Geschäftsregeln, die von unserer Dinner-Modellklasse erzwungen werden.

Dadurch bleibt unsere Anwendungsarchitektur sauber und erleichtert das Testen. Wir können unserer Modellebene in Zukunft zusätzliche Geschäftsregeln hinzufügen und müssen keine Codeänderungen an unserem Controller oder der Ansicht vornehmen, damit sie unterstützt werden. Dies wird uns ein großes Maß an Flexibilität bieten, um unsere Anwendung in Zukunft weiterzuentwickeln und zu ändern.

Unser DinnerController aktiviert jetzt Dinner-Auflistungen/Details sowie die Unterstützung zum Erstellen, Bearbeiten und Löschen. Den vollständigen Code für die -Klasse finden Sie unten:

public class DinnersController : Controller {

    DinnerRepository dinnerRepository = new DinnerRepository();

    //
    // GET: /Dinners/

    public ActionResult Index() {

        var dinners = dinnerRepository.FindUpcomingDinners().ToList();
        return View(dinners);
    }

    //
    // GET: /Dinners/Details/2

    public ActionResult Details(int id) {

        Dinner dinner = dinnerRepository.GetDinner(id);

        if (dinner == null)
            return View("NotFound");
        else
            return View(dinner);
    }

    //
    // GET: /Dinners/Edit/2

    public ActionResult Edit(int id) {

        Dinner dinner = dinnerRepository.GetDinner(id);
        return View(dinner);
    }

    //
    // POST: /Dinners/Edit/2

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(int id, FormCollection formValues) {

        Dinner dinner = dinnerRepository.GetDinner(id);

        try {
            UpdateModel(dinner);

            dinnerRepository.Save();

            return RedirectToAction("Details", new { id= dinner.DinnerID });
        }
        catch {
            ModelState.AddRuleViolations(dinner.GetRuleViolations());

            return View(dinner);
        }
    }

    //
    // GET: /Dinners/Create

    public ActionResult Create() {

        Dinner dinner = new Dinner() {
            EventDate = DateTime.Now.AddDays(7)
        };
        return View(dinner);
    }

    //
    // POST: /Dinners/Create

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(Dinner dinner) {

        if (ModelState.IsValid) {

            try {
                dinner.HostedBy = "SomeUser";

                dinnerRepository.Add(dinner);
                dinnerRepository.Save();

                return RedirectToAction("Details", new{id=dinner.DinnerID});
            }
            catch {
                ModelState.AddRuleViolations(dinner.GetRuleViolations());
            }
        }

        return View(dinner);
    }

    //
    // HTTP GET: /Dinners/Delete/1

    public ActionResult Delete(int id) {

        Dinner dinner = dinnerRepository.GetDinner(id);

        if (dinner == null)
            return View("NotFound");
        else
            return View(dinner);
    }

    // 
    // HTTP POST: /Dinners/Delete/1

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Delete(int id, string confirmButton) {

        Dinner dinner = dinnerRepository.GetDinner(id);

        if (dinner == null)
            return View("NotFound");

        dinnerRepository.Delete(dinner);
        dinnerRepository.Save();

        return View("Deleted");
    }
}

Nächster Schritt

Wir haben jetzt grundlegende CRUD-Unterstützung (Create, Read, Update und Delete) in unserer DinnersController-Klasse implementiert.

Sehen wir uns nun an, wie wir die ViewData- und ViewModel-Klassen verwenden können, um eine noch umfangreichere Benutzeroberfläche für unsere Formulare zu ermöglichen.