ASP.NET MVC I18n – Teil 8: Datenvalidierung

20. Februar 2014

Der letzte Beitrag hat gezeigt, wie man Datenbezeichner und Validierungsfehler lokalisieren kann. Es wird Zeit, sich der Validierung selbst zu widmen.

Hinweis: Dieser Beitrag ist Teil einer Serie, die Übersicht findet sich hier.

 

 

Datenvalidierung ist ein weiterer Aspekt zum Thema Internationalisierung, der in weiten Teilen gut dokumentiert ist, aber eben oft nicht vollständig.

Kommen wir kurz zur letzten Fehlermeldung aus dem letzten Beitrag zurück:

Lokalisierung-7-app4

Die Fehlermeldung beim Datum in Deutsch statt Englisch zu bekommen hilft nicht wirklich über die Tatsache hinweg, dass hier gar kein Fehler vorliegt.

Das Grundproblem ist, dass die von ASP.NET MVC eingesetzte jQuery Validation normale JavaScript-Methoden zur Validierung verwendet. Diese kommen natürlich nur mit amerikanischem Format zurecht und produzieren daher u.U. falsche Fehlermeldungen. Schlimmer noch sind Fälle, in denen keine Validierungsfehler entstehen, weil die falschen Werte validiert werden. Punkt und Komma haben bei Zahlen vertauschte Bedeutung, wenn man zwischen deutschem und amerikanischem Format wechselt, gleiches gilt für Position von Tag und Monat zwischen amerikanischem und französischem Datumsformat.

Die typische Antwort auf dieses Problem ist, sich jQuery Globalize von github zu besorgen und zu verwenden… was etwas Handarbeit erfordert.

 

Zuerst…

braucht man globalize.js (auf github unter lib), dazu die notwendigen Lokalisierungsdateien (zu finden unter libcultures). Wer eine komprimierte Version der Script-Datei haben möchte (globalize.min.js im Bild unten), muss sich diese bereits selbst bauen, etwa über http://jscompress.com/. All Dateien packt man dann in den /Scripts Ordner im Web-Projekt:

Lokalisierung-8-files

Hinweis: Es gibt ebenfalls mittlerweile ein nuget package, das die Dateien ohne Umweg über github bereitstellt – einschließlich aller verfügbaren Lokalisierungsdateien – aber immer noch ohne komprimierte Variante.

 

Zweitens…

… müssen die Skripte in unsere Views aufgenommen werden. Das geht in zwei Varianten, die man beide als Beispiele im Web findet:

A) Man definiert ein entsprechendes bundle…

   1: bundles.Add(new ScriptBundle("~/bundles/globalization") 

   2:     .Include("~/Scripts/globalize.js") 

   3:     .Include("~/Scripts/globalize.culture.*") 

   4:     .Include("~/Scripts/globalize.initialize.js") 

   5: ); 

… und nimmt das _layout.cshtml auf bevor die „script“ section gerendert wird, denn dort kommt später die Validierung ins Spiel:

   1: @Scripts.Render("~/bundles/globalization") 

   2: @RenderSection("scripts", required: false) 

B) Anstatt alle Lokalisierungsdateien aufzunehmen reicht es auch, sich auf die aktuell gerade benötigte zu beschränken. Das geht einfacher ohne extra ein bundle zu definieren:

   1: @Scripts.Render("~/Scripts/globalize.js") 

   2: @Scripts.Render("~/Scripts/globalize.culture." + Culture + ".js") 

   3: @Scripts.Render("~/Scripts/globalize.initialize.js") 

 

Drittens…

… müssen wird die Sprache auf der Clientseite initialisieren.

Dafür ist ein Aufruf der Funktion Globalize.culture(…) mit der entsprechenden Angabe notwendig. Man könnte diesen Aufruf dynamisch per Code erzeugen – was sehr oft so empfohlen wird. Andererseits ist diese Information ja schon auf dem Client verfügbar (wir haben sie schließlich selbst bereitgestellt). Eine statische Script-Datei globalize.initialize.js sollte also ausreichen, und frisst zudem weniger Ressourcen:

   1: $(document).ready(function () { 

   2:     // use the language provided from server... 

   3:     var lang = $("html").attr("lang");  

   4:     if (typeof Globalize != 'undefined') 

   5:         Globalize.culture(lang); 

   6: }); 

 

Viertens…

…fehlt uns noch der Verbindung.

Wir haben die notwendigen Funktionen für lokalisierte Datenformate, aber jQuery Validation nutzt diese noch nicht. Der letzte Schritt ist also, diese Verbindung herzustellen. Was immer passieren muss… . Was jemand Microsoft hätte bereitstellen können… . Oder wenigstens vollständig hätte erklären können… .

Stattdessen findet man nur die halbgare Darstellung im Tutorial. [UPDATE: Wie sich herausstellte bin ich nicht der Erste dem das auffällt. Mehr dazu am Ende.]

jQuery hat ein Objekt, das die notwendigen Validierungsmethoden verwaltet: $.validator.methods. Diese müssen durch Varianten ersetzt werden, die die von Globalize bereitgestellten Methoden verwenden. Für MVC und unobtrusive validation geht es dabei um die 5 Methoden number, date, min, max und range. Hier die entsprechende Script-Datei globalize.validation.js:

   1: // replace methods in jquery.validate.js ($.validator.methods) as necessary for localized validation: 

   2:  

   3: $.validator.methods.number = function (value, element) { 

   4:     return this.optional(element) || !isNaN(Globalize.parseFloat(value)); 

   5: } 

   6:  

   7: $.validator.methods.date = function (value, element) { 

   8:     if (this.optional(element)) 

   9:         return true; 

  10:     var result = Globalize.parseDate(value); 

  11:     return !isNaN(result) && (result != null); 

  12: } 

  13:  

  14: $.validator.methods.min = function( value, element, param ) { 

  15:     return this.optional(element) || Globalize.parseFloat(value) >= param; 

  16: } 

  17:  

  18: $.validator.methods.max = function( value, element, param ) { 

  19:     return this.optional(element) || Globalize.parseFloat(value) <= param; 

  20: } 

  21:  

  22: $.validator.methods.range = function (value, element, param) { 

  23:     if (this.optional(element)) 

  24:         return true; 

  25:     var result = Globalize.parseFloat(value); 

  26:     return (result >= param[0] && result <= param[1]); 

  27: } 

Hinweis: Wer jQuery validation direkt verwendet (d.h im JavaScript und nicht via unobtrusive validation), der muss natürlich alle Methoden in $.validator.methods entsprechend ersetzten.

Diese Script-Datei kann man einfach in das existierende bundle für Validierungen mit aufnehmen:

   1: var bundle = bundles.GetBundleFor("~/bundles/jqueryval"); 

   2: bundle.Include("~/Scripts/globalize.validation.js"); 

 

Und jetzt funktioniert unsere Validierung so wie sie soll, und berücksichtigt deutsche Datums- und Zahlenformate korrekt.

 

UPDATE: Gerade als ich diesen Beitrag fertig hatte bin ich über John‘s Beitrag gestolpert. Das ist der erste Beitrag den ich gefunden habe, der Globalize und jQuery validation sowohl korrekt, als auch vollständig verknüpft. Außerdem stellt er ein nuget package bereit, so dass man sich einige Arbeit – nicht alles – sparen kann.