Thomas Bandt

Über mich | Kontakt | Archiv

ASP.NET MVC - Formulare per Ajax verarbeiten

Disclaimer: Ich bin kein Ajax-Entwickler. Als das Thema vor 5 Jahren auf den Tisch kam, hatte ich schon einige Jahre mit XmlHttpRequest im Internet Explorer gearbeitet, aber mich eigentlich schwerpunktmäßig immer auf die Serverseite konzentriert. Das hat sich auch bis heute noch nicht groß geändert - ich arbeite zwar mit jQuery, mootools und auch noch klassisch nativem JavaScript, aber meist wirklich nur für das Nötigste.

In normalen Formularen gehe ich was die Überprüfung der Eingaben angeht bislang recht klassisch vor - die Validierung findet im Service und damit auf der Serverseite statt. Sicherheit geht nunmal über alles. Entsprechend stiefmütterlich habe ich dann aber auch die clientseitige Validierung bisher behandelt.

Nun bin ich aber gestern zu einem Punkt gekommen, bei dem ich nur zwei Alternativen habe:

  1. Eine vernünftige Lösung mit Ajax und/oder einem Client-Validation-Framework bauen.
  2. Etwas serverseitiges und für den User schlecht benutzbares zusammenfrickeln.

Es geht um einen Dateiupload, genauer einen Multi-File-Upload, wie man ihn z.B. mit swfupload oder uploadify verwirklichen kann. Dieser Upload ist in ein normales Formular eingebettet, was einige Pflichtfelder besitzt. Wählt der Nutzer nun also Dateien aus und vergisst aber eines der Pflichtfelder auszufüllen und schickt das Formular ab - dann sind nach dem Roundtrip zum Server die ausgewählten Dateien wieder weg.

Keine gute Sache für den User. Also sollte der Workflow so aussehen:

  1. Formular ausfüllen
  2. Dateien auswählen
  3. Formular überprüfen
  4. Ggf. Eingaben korrigieren
  5. Bei erfolgreichem Ausfüllen Formular abschicken und Hauptdatensatz anlegen
  6. Anschließend die Dateien hochladen und dem Hauptdatensatz zuordnen

Dafür gibt es zwei brauchbare Wege (die mir einfallen):

Ajax

Validierung und Erzeugung des Hauptdatensatzes erfolgt per Ajax. Wenn dies geklappt hat, wird der Upload der ausgewählten Dateien angestoßen. Vorteil: die ID des Datensatzes steht hier vor dem Upload bereits zur Verfügung.

Client-Validation-Framework

Die Validierung erfolgt zusätzlich zur Serverseite, wo sie weiter obligatorisch bleibt, clientseitig, zum Beispiel mit dem jQuery-Validation-Plugin oder den Tools, die ASP.NET MVC dafür bereits mitbringt. Nachteile: doppelte Konfiguration (vernachlässigbar), im Zweifel verschieden aussehende Ausgaben der Fehler client-/serverseitig (vernachlässigbar), die ID des Hauptdatensatzes steht hier beim Dateiupload noch nicht zur Verfügung, da erst die Dateien hochgeladen müssen und dann der POST erfolgen kann.

Ein schwieriges Thema, bei dem wirklich hunderttausende Wege nach Rom führen. In Sachen Ajax stört mich der meist enorme Overhead. Ich habe relativ wenig Lust die Ausgabe der Fehler "zu synchronisieren".

Ein brauchbarer Weg erschien mir aber der von Jarrett Vance in seinem Post "Making an Ajax Form with jQuery in ASP.NET MVC" aufgezeigte. Vorteil: die Ausgabe bleibt exakt die gleiche, man muss sie nicht doppelt pflegen. Zudem braucht es auch keinen großen Aufwand auf der Client-Script-Seite. Nachteil: die Formulare müssen in PartialViews ausgelagert werden.

Ich habe es allerdings mal als Grundlage für eine Demo genommen und seinen Ansatz für meine Zwecke erweitert.

   1:  [HttpPost]
   2:  [ValidateAntiForgeryToken]
   3:  public ActionResult CreateTask(TaskViewModel model)
   4:  {
   5:   
   6:      // DEMO - Validierung wird im Service/BLL ausgeführt
   7:   
   8:      if(string.IsNullOrWhiteSpace(model.Name))
   9:          ModelState.AddModelError("Name", "Bitte geben Sie einen Namen an.");
  10:   
  11:      if(string.IsNullOrWhiteSpace(model.Text))
  12:          ModelState.AddModelError("Text", "Bitte geben Sie einen Text an.");
  13:   
  14:      if (ModelState.IsValid)
  15:          TempData["Success"] = "Task erfolgreich angelegt.";
  16:   
  17:      if(Request.IsAjaxRequest())
  18:      {
  19:          if(ModelState.IsValid)
  20:              return Json(new AjaxOperationResultInfo { Successfull = true, RedirectUrl = Url.Action("index") });
  21:          return PartialView("CreateTaskForm", model);
  22:      }
  23:   
  24:      if(ModelState.IsValid)
  25:          return RedirectToAction("index");
  26:      return View(model);
  27:   
  28:  }

Nur wenn es ein Ajax-Request ist, wird der PartialView zurückgegeben. Ansonsten, also in allen Fällen, in denen JavaScript im Browser deaktiviert ist, gibt es den normalen View zurück. Damit funktioniert der Spaß auch bei deaktiviertem JavaScript.

Nun ein Kompromiss:

Wenn ein Fehlerauftritt, wird der PartialView zurückgegeben. Wenn die Sache allerdings erfolgreich war, gebe ich ein JSON-Ergebnis zurück. Das ist zwar nicht astrein, erlaubt mir dann aber auf der Clientseite weitere Entscheidungen zu treffen. Im Beispiel übergebe ich die Ziel-URL, auf die nach erfolgreichem Verarbeiten des Formulars weitergeleitet werden soll. In meinem Praxisfall würde ich noch die ID des angelegten Datensatzes übergeben, damit diese beim Upload der Dateien gleich verwendet würden könnte.

Der JS-Code bleibt übersichtlich:

   1:  $(document).ready(function () {
   2:      $('#ajaxForm form').live('submit', function () {
   3:          $.post($(this).attr('action'), $(this).serialize(), function (result) {
   4:              if ($.isString(result)) {
   5:                  $("#ajaxForm").replaceWith($(result));
   6:              }
   7:              else if (result["Successfull"]) {
   8:                  window.location.href = result["RedirectUrl"];
   9:              }
  10:              else {
  11:                  alert('Es ist ein Fehler aufgetreten. Bitte benachrichtigen Sie den Codemonkey.');
  12:              }
  13:          });
  14:          return false;
  15:      });
  16:  });
  17:   
  18:  jQuery.isString = function (o) {
  19:      return (typeof o === "string");
  20:  }

Die komplette Projektmappe habe ich mal angehangen.

Unterm Strich bleibt es ein scheinbar brauchbarer Weg, richtig glücklich bin ich damit aber noch nicht. Ich werde als nächstes mal jQuery Validation testen sowie die DataAnnotations wieder ausgraben, rein für die clientseitige-Validierung.

Downloads

Kommentare

  1. Dariusz schrieb am Mittwoch, 28. Juli 2010 13:00:00 Uhr:

    Wirf mal einen Blick auf das jQuery Validation Plugin. Dort gibt es auch die Möglichkeit für jedes Validierungs Regel eine zusätzlich Remote Methode einzubinden (via HTTP GET), wenn Du inhaltliche Validierungen machen möchtest. Da kannst Du dann auch die bestehenden Serverseitigen Validierungen mit einbinden.

    z.B.
    rules: { taskname: { required: true, minlength: 4, remote: "/validationservice" } }
  2. Thomas schrieb am Mittwoch, 28. Juli 2010 13:11:00 Uhr:

    Bin schon dabei :-).
  3. André Krämer schrieb am Mittwoch, 28. Juli 2010 13:35:00 Uhr:

    Dein Ansatz, die redirect URL via JSON herauszugeben gefällt mir sehr gut. Habe bisher (ähnlich wie auf der dotnet Cologne gezeigt) einfach nur eine kurze Bestätigung auf der Seite angezeigt.

    So wie du es machst ist natürlich schicker ;-)


« Zurück  |  Weiter »