ASP.NET MVC eigener JsonConverter für Decimal Datentyp mit CultureInfo


Eigentlich habe ich bereits einen Beitrag geschrieben, in dem ich auf die Problematik “…Datetime JSON Serialize…” eingegangen bin. Ich bin aber erneut auf ein ähnliches Problem gestoßen und habe noch einmal etwas detaillierter recherchiert und fand eine vergleichbare Lösung für Datetime und das Serialisieren des richtigen Dezimaltrennzeichens. Das ganze funktioniert jetzt mit einem Benutzerdefinierten JsonConverter und einem “eigenen” Actionresult, das ganze wird dann über eine Controllerextension aufgerufen und wir leiten nicht extra vom Controller ab wie in meiner “alten” Lösung.

Um die passenden Dezimaltrenneichen zu serialisieren ob z.B. Punkt “.” oder Komma “,” verwendet werden soll benötigen wir einen eigenen JsonConverter, den wir später unserem Serializer hinzufügen. Diesem Konverter übergeben wir die aktuelle CultureInfo für die der Wert ausgegeben werden soll und .NET weiß dann von “allein” welches Trennzeichen für die jeweilige Culture benutzt werden muss. Dann müssen wir noch angeben für welche Datentypen der Konverter verwendet werden soll, in unserem Fall sind das alle Datentypen die Dezimaltrennzeichen verwenden, wie z.B. “decimal”, “double” und “float”.

public class FormattedDecimalConverter : JsonConverter
{
    private CultureInfo culture;

    public FormattedDecimalConverter(CultureInfo culture)
    {
        if (culture == null)
        {
            culture = new CultureInfo("De-de");
        }

        this.culture = culture;
    }

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(decimal) ||
                objectType == typeof(double) ||
                objectType == typeof(float));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(Convert.ToString(value, culture));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Diesen JsonConverter verwenden wir dann in unserem Benutzerdefinierten ActionResult, in dem wir den JsonSerializerSettings den passenden Konverter hinzufügen. Dabei gibt es bereits einige Vorgefertigte Konverter wie z.B. den “IsoDateTimeConverter” der das Datum im ISO Format serialisiert oder den “StringEnumConverter” der beim Serialisieren nicht den Int Wert des Enum serialisiert sondern den Namen des jeweiligen Enum Wertes. Man kann beliebig viele Konverter den JsonSerializerSettings hinzufügen um ein Json Objekt ganz nach den eigenen Maßstäben zu erstellen. Hier sollte man aber aufpassen, das .NET die entsprechenden Datentypen auch bei einem Postback wieder “zurückverwandeln” kann in den entsprechenden Datentyp, denn dafür muss sonst noch ein eigener Modelbinder geschrieben werden.

/// <summary>
/// Benutzerdefiniertes Json Result um z.B. Dezimalwerte Länderspezisch zurückzugeben
/// http://yobriefca.se/blog/2010/11/20/better-json-serialisation-for-asp-dot-net-mvc/
/// </summary>
public class JsonNetResult : ActionResult
{
    #region Konstruktor
    public JsonNetResult()
    {
    }

    public JsonNetResult(object data)
    {
        Data = data;
    }

    public JsonNetResult(object data, CultureInfo culture)
    {
        CultureInfo = culture;
        Data = data;
    }

    public JsonNetResult(object data, JsonSerializerSettings settings)
        : base()
    {
        Data = data;
        Settings = settings;
    }
    #endregion

    #region Properties
    /// <summary>
    /// Die aktuellen Ländereinstellungen Default ist "De-de", z.B. für das Umwandeln
    /// von Dezimalzahlen notwendig
    /// </summary>
    public CultureInfo CultureInfo { get; set; }

    /// <summary>
    /// Gets or sets the serialiser settings
    /// </summary>
    public JsonSerializerSettings Settings { get; set; }

    /// <summary>
    /// Gets or sets the encoding of the response
    /// </summary>
    public Encoding ContentEncoding { get; set; }

    /// <summary>
    /// Gets or sets the content type for the response
    /// </summary>
    public string ContentType { get; set; }

    /// <summary>Gets or sets the body of the response</summary>
    public object Data { get; set; }

    /// <summary>
    /// Gets the formatting types depending on whether we are in debug mode
    /// </summary>
    private Formatting Formatting
    {
        get
        {
            return Debugger.IsAttached ? Formatting.Indented : Formatting.None;
        }
    }
    #endregion

    #region Override
    /// <summary>
    /// Serialises the response and writes it out to the response object
    /// </summary>
    /// <param name="context">The execution context</param>
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        HttpResponseBase response = context.HttpContext.Response;
        response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/json";

        // set content encoding
        if (ContentEncoding != null)
        {
            response.ContentEncoding = ContentEncoding;
        }

        //Setzen der passenden Sprache
        if (CultureInfo == null)
        {
            CultureInfo = new CultureInfo("De-de");
        }

        if (Settings == null)
        {
            Settings = new JsonSerializerSettings();
            //Setzen der Standard Converter um z.B. bei Dezimalzahlen
            //statt "." eine "," als Seperator zu verwenden
            Settings.Converters.Add(new FormattedDecimalConverter(CultureInfo));
            //Den passenden Datetime Konverter einstellen
            //http://james.newtonking.com/archive/2009/02/20/good-date-times-with-json-net
            Settings.Converters.Add(new IsoDateTimeConverter());
            //Enum Converter einstellen, das nicht Int sondern der String versendet wird
            //Settings.Converters.Add(new StringEnumConverter());
        }

        if (Data != null)
        {
            JsonTextWriter writer = new JsonTextWriter(response.Output) { Formatting = Formatting };
            JsonSerializer serializer = JsonSerializer.Create(Settings);
            serializer.Converters.Add(new FormattedDecimalConverter(CultureInfo));

            serializer.Serialize(writer, Data);
            writer.Flush();
        }
    }
    #endregion
}

Das ganze kann dann sehr einfach mit Hilfe einer kleinen Extensionmethode für den Controller verwendet werden

/// <summary>
/// Controller Extensions um einfacher auf unser Custom JsonResult zugreifen zu können.
/// </summary>
public static class ControllerExtensions
{
    /// <summary>
    /// Umwandeln der übergebenen Daten in ein passenden JSON Objekt.
    /// </summary>
    /// <param name="data">Die Daten die umgewandelt werden sollen</param>
    public static JsonNetResult JsonEx(this Controller controller, object data)
    {
        return new JsonNetResult(data);
    }

    /// <summary>
    /// Umwandeln der übergebenen Daten in ein passenden JSON Objekt.
    /// </summary>
    /// <param name="data">Die Daten die umgewandelt werden sollen</param>
    /// <param name="culture">Die Länderinformationen die bei der Umwandlung verwendet werden sollen</param>
    public static JsonNetResult JsonEx(this Controller controller, object data, CultureInfo culture)
    {
        return new JsonNetResult(data, culture);
    }

    /// <summary>
    /// Umwandeln der übergebenen Daten in ein passenden JSON Objekt.
    /// </summary>
    /// <param name="data">Die Daten die umgewandelt werden sollen</param>
    /// <param name="settings">Die Serializer Settings die man hier Individuell angeben kann</param>
    public static JsonNetResult JsonEx(this Controller controller, object data, JsonSerializerSettings settings)
    {
        return new JsonNetResult(data, settings);
    }
}

Im Controller selbst sieht der Aufruf, dann z.B. folgendermaßen aus

 public ActionResult GetModelData()
 {
     var obj = new ServiceData()
     {
         Alter = 32,
         Geburtstag = new DateTime(1982, 3, 15),
         Gehalt = (decimal) 1034.45,
         Name = "Johannes"
     };

     return this.JsonEx(obj);
 }
Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s