Ok, ich gebe es zu - ich schrieb meine eigene Ansicht Zustand Anlage für ASP.NET MVC. Ich bin an der Kritik anderer interessiert, vor allem angesichts des ganzen View-State-Bashings im Zusammenhang mit WebForms. Auf der anderen Seite, in Pro ASP.NET MVC-Framework (S. 405-406) Steven Sanderson sagt: "Ich glaube, dass [ViewState] als allgemeines Web-Design-Muster völlig in Ordnung ist: Webentwickler haben schon immer Daten in versteckten Formularfeldern aufbewahrt; dies bringt es einfach auf die nächste Stufe, indem es diese Technik formalisiert und eine saubere Abstraktionsschicht bietet." In Anbetracht meines spezifischen Problems schien es ein vernünftiger Ansatz zu sein, eine solche leichtgewichtige Abstraktionsschicht zu schaffen und gleichzeitig die Stärken von MVC, nämlich Transparenz und Testbarkeit, beizubehalten.
In Frageform:
- Ist die Verwendung von ViewData die beste oder zumindest eine starke Möglichkeit, mein Problem zu lösen?
- Gibt es gravierende Schwachstellen (z. B. Leistung, Sicherheit) in meinem spezifischen Ansatz?
- Wie gut passt der Ansatz zur MVC-Designästhetik?
- Gibt es eine bessere Lösung? Wenn ja, wie lautet sie und warum?
Ich schreibe eine sichere Schnittstelle zur Verwaltung von Benutzern/Rollen/Konten - so etwas in der Art. Die aus der Datenbank abgerufenen Daten haben ein Identitäts-Token und einen Zeitstempel, die für die optimistische Gleichzeitigkeitskontrolle verwendet werden. Für Operationen wie die Bearbeitung müssen die Identität und der Zeitstempel mit der Client-Operation verknüpft werden, was eine Art clientseitige Persistenz voraussetzt. Der Zeitstempel ist ein Schlüsselfaktor für diese clientseitige Persistenz, da bei der Aktualisierung eines Datensatzes der Zeitstempel des Abrufs mit dem aktuellen Zeitstempel verglichen werden muss, um festzustellen, ob ein anderer Benutzer den Datensatz seit dem ursprünglichen Abruf aktualisiert hat. Die Integrität des Zeitstempels muss gewahrt bleiben, da ein böswilliger Benutzer durch Manipulation des Zeitstempels Datenbankeinträge überschreiben könnte.
Die üblichen Persistenzoptionen sind ViewData, TempData und Sitzungsstatus. Andere Optionen wie das Schreiben einer eigenen Datenbankeinrichtung habe ich nicht ernsthaft in Betracht gezogen. Ich habe mich für ViewData entschieden, da die Daten für mehr als einen Roundtrip aufbewahrt werden können (z.B. bleibt der Status auch dann erhalten, wenn ein Client zu einer anderen Seite und zurück springt) und weil ich eine Menge Sitzungsdatenverwaltung vermeiden wollte. Ich denke, dass der Ansatz relativ wenig Aufwand erfordert und sicher ist, wenn nur ausgewählte Daten in ViewData gespeichert werden und diese mit einem HMAC-Code (Hashing Code Message Authentication) geschützt sind.
In der Praxis verwende ich ein Funktionspaar Encode/Decode, um die Daten zu serialisieren und den HMAC-Code zu berechnen, und einen Html-Helfer Html.FormState(), um die serialisierten Daten im Formular zu speichern. (Die Encode/Decode-API ist etwas komplizierter, als ich zeige, denn sie ermöglicht es mir, mehrere Objekte zu speichern usw.) Außerdem gebe ich den Status als Argument an die Aktionsmethode zurück. Dadurch wird ein Design mit funktionalem Charakter beibehalten und die Testbarkeit gefördert. Hier ist ein Beispiel (die Inline-Zuweisung zu ViewData dient nur zur Veranschaulichung):
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Edit(Guid? id) {
User user = _crmContext.Users.GetUser(id ?? Guid.Empty);
if (user == null) {
TempMessage = "User not found";
return RedirectToAction("Index");
}
else {
ViewData["formState"] = EncodeState("user", user);
return View(user);
}
}
[AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Edit(Guid? id, string formState) {
User user = DecodeState("user", formState) as User;
if (user == null || id != user.UserId) {
TempMessage = "User not found";
return RedirectToAction("Index");
}
else {
try {
UpdateModel(user, "user");
_crmContext.Users.UpdateUser(user);
TempMessage = "User changes saved.";
return RedirectToAction("Details", new { id = user.UserId });
}
catch (RulesException e) {
e.AddModelStateErrors(ModelState, "user");
ViewData["formState"] = EncodeState("user", user);
return View(user);
}
}
}
public static string FormState(this HtmlHelper html) {
string anti = html.AntiForgeryToken();
string data = html.Hidden("formState");
return "\n" + anti + "\n" + data;
}