Jun 6, 2012
5.306 Besuche

Probleme mit MVCMovies Beispiel: Validierung des Preises mit Dezimalstellen schlägt fehl

Wer das MVCMovies Beispiel für MVC3 durchgeht, das übrigens sehr gut und auch schön geschrieben ist, wird feststellen: es ist vermutlich von einem Amerikaner für andere Amerikaner geschrieben worden. Die nicht amerikanischen 90% der Welt sind.. übersehen worden. (Siehe auch Kommentare auf der Seite.)

Das zeigt sich, wenn man zur Validierung unter http://www.asp.net/mvc/tutorials/getting-started-with-aspnet-mvc3/cs/adding-validation-to-the-model vorgestoßen ist. Versucht man nun nämlich, für den Preis eine Zahl mit Nachkommastellen einzugeben, wie z. B. 9,99, so scheitert dies. 9.99 scheitert übrigens auch – an 2 verschiedenen Stellen wird hier unterschiedlich geprüft.

Die Stelle im Code sieht laut Tutorial so aus:

1
2
3
[Required(ErrorMessage = "Price Required")]
[Range(1, 100, ErrorMessage = "Price must be between $1 and $100") ]
public decimal Price { get; set; }

Der entsprechende Fehler sieht dann so aus:

Eingabe „9,99”: Fehlermeldung „Price must be between $1 and $100”.
Eingabe „9.99”: Fehlermeldung „Das Feld “Price” muss eine Zahl sein.”

Es gibt also keine Möglichkeit mehr, einen Wert mit Nachkommastellen einzugeben.

Der erste Ansatz einer Lösung kommt von Microsoft (http://msdn.microsoft.com/en-us/library/gg674880(VS.98).aspx). Es stellt sich jedoch heraus, daß nicht nur die Datei methods_de.js nicht an der Stelle gefunden wird, die der Artikel beschreibt, sondern daß der Artikel an sich für dieses Problem auch wenig hilfreich ist.

Nach längerer Suche stieß ich schließlich auf eine schöne Lösung für ASP MVC2 (http://haacked.com/archive/2010/05/10/globalizing-mvc-validation.aspx), die jedoch bei mir (u. a. weil auch der Site Master fehlt) nicht funktioniert hat. Erneute Suche mit passenderen Begriffen führte mich schließlich zu http://stackoverflow.com/questions/5199835/mvc-3-jquery-validation-globalizing-of-number-decimal-field, wo mir besonders die Antwort von giacomo gefallen hat. Und, noch wichtiger: funktioniert.

Hier muß nun lediglich im web.config in dem <system.web> Abschnitt

<globalization culture="auto" uiCulture="auto" enableClientBasedCulture="true"/>

hinzugefügt werden.
Z. B. so:

1
2
3
4
5
6
7
8
9
10
11
12
<system.web>
		<globalization culture="auto" uiCulture="auto" enableClientBasedCulture="true"/>
		<compilation debug="true" targetFramework="4.0">
			<assemblies>
				<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
				<add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
				<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
				<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
				<add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
			</assemblies>
		</compilation>
...noch mehr..

Anschließend muß noch der HtmlHelper erweitert werden. Die neue Klasse inkl. Namespace können wir z. B. im global.asax.cs hinzufügen (sowie das fehlende Using ergänzen):

1
2
3
4
5
6
7
8
9
10
11
12
13
using System.Threading;
(...)
namespace System.Web.Mvc
{
    public static class LocalizationHelper
    {
        public static IHtmlString MetaAcceptLanguage(this HtmlHelper html)
        {
            var acceptLang = HttpUtility.HtmlAttributeEncode(Thread.CurrentThread.CurrentUICulture.ToString());
            return new HtmlString(string.Format("<meta name="accept-language" content="{0}"/>", acceptLang));
        }
    }
}

Schließlich müssen wir noch die _Layout.cshtml ergänzen, und zwar unter <head> folgendes einfügen:

1
2
3
4
5
6
7
@Html.MetaAcceptLanguage();
<script type="text/javascript">
    $(document).ready(function () {
        var data = $("meta[name='accept-language']").attr("content");
        $.global.preferCulture(data);
    });
</script>

Somit erhalten wir nun

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
    @Html.MetaAcceptLanguage();
    <script type="text/javascript">
        $(document).ready(function () {
            var data = $("meta[name='accept-language']").attr("content");
            $.global.preferCulture(data);
        });
    </script>
</head>

Und schon funktioniert alles.. und.. der Client kann nunmehr mit seinen Einstellungen das Format für die Überprüfung vorgeben. Ob das Ganze jetzt wiederum für einen englischen Client funktioniert kann ich aktuell nicht testen.. zumindest läuft das Beispiel jetzt auch hier erst einmal so, wie es soll.

Weitere fehlgeschlagene Versuche

Weitere Versuche, wie das Setzen von Globalisierung oder der CultureInfo des aktuellen Threads, verliefen übrigens im Sand. Ich halte sie mal fest, sollte jemand diesen Weg gehen wollen.

1
2
3
AjaxHelper.GlobalizationScriptPath = "http://ajax.microsoft.com/ajax/4.0/1/globalization/";
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE");
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de-DE");

1 Kommentar

  • Das funktioniert auch sehr gut für den MVCMusicStore.
    Hier verschwindet dann die falsche Validierungsmeldung „Das Feld “Price” muss eine Zahl sein.”
    Aus

    wird dann (andere Felder sind nicht ausgefüllt, damit man die Validierungsnachricht sieht – sind diese ausgefüllt kommt natürlich keine Fehlermeldung mehr, sondern das neue Album wird direkt gespeichert)

    Noch einfacher ist es, die Applikation auf Englisch zu stellen – dann ist der . wieder der Seperator. Hierzu fügt man im web.config im <system.web> Abschnitt

    <globalization culture ="en-US" />

    ein.

    Im Album.cs (dem Album-Modell) sollte man übrigens noch

    1
    2
    3
    
    [ScaffoldColumn(false)]
    [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
    public int AlbumId { get; set; }

    die DatabaseGenerated Annotation hinzufügen. Sonst werden hier immer neue Alben mit der ID 0 eingefügt – das mögen Datenbanken recht ungern.

    Gemäß dem Kommentar von arrow3215 sollte auch die Edit Methode angepaßt werden:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    [HttpPost]
    public ActionResult Edit(Album album, int id)
    {
        if (ModelState.IsValid)
        {
            album.AlbumId = id;
            db.Entry(album).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
        ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
        return View(album);
    }

    http://www.asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-6

Einen Kommentar schreiben