Question

It has been decided by the ASP.NET Web API team to use the JSON.NET library for model binding JSON data. However, "normal" MVC controllers still use the inferior JsonDataContractSerializer. This causes issues with parsing dates, and is causing me much headache.

See this for reference:
http://www.devcurry.com/2013/04/json-dates-are-different-in-aspnet-mvc.html

The author chooses to solve the issue in the Knockout layer on the client. But I would prefer to solve this by using the same JSON.NET model binder in MVC controllers as in Web API controllers.

How do I substitute a different JSON model binder into ASP.NET MVC? Specifically, the JSON.NET library. Using the same model binder from Web API would be ideal if possible.

Was it helpful?

Solution

I have done this, and also heavily customized the serialization that Json.NET is doing, by:

Replace the default formatter in global.asax.cs, Application_Start:

GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
GlobalConfiguration.Configuration.Formatters.Add(new CustomJsonMediaTypeFormatter());

And my CustomJsonMediaTypeFormatter is:

public static class CustomJsonSettings
{
    private static JsonSerializerSettings _settings;

    public static JsonSerializerSettings Instance
    {
        get
        {
            if (_settings == null)
            {
                var settings = new JsonSerializerSettings();

                // Must convert times coming from the client (always in UTC) to local - need both these parts:
                settings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = System.Globalization.DateTimeStyles.AssumeUniversal }); // Critical part 1
                settings.DateTimeZoneHandling = DateTimeZoneHandling.Local;   // Critical part 2

                // Skip circular references
                settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

                // Handle special cases in json (self-referencing loops, etc)
                settings.ContractResolver = new CustomJsonResolver();

                _settings = settings;
            }

            return _settings;
        }
    }
}

public class CustomJsonMediaTypeFormatter : MediaTypeFormatter
{
    public JsonSerializerSettings _jsonSerializerSettings;

    public CustomJsonMediaTypeFormatter()
    {
        _jsonSerializerSettings = CustomJsonSettings.Instance;

        // Fill out the mediatype and encoding we support
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
        SupportedEncodings.Add(new UTF8Encoding(false, true));
    }

    public override bool CanReadType(Type type)
    {
        return true;
    }

    public override bool CanWriteType(Type type)
    {
        return true;
    }

    public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
    {
        // Create a serializer
        JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

        // Create task reading the content
        return Task.Factory.StartNew(() =>
        {
            using (StreamReader streamReader = new StreamReader(stream, SupportedEncodings.First()))
            {
                using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
                {
                    return serializer.Deserialize(jsonTextReader, type);
                }
            }
        });
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
    {
        // Create a serializer
        JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

        // Create task writing the serialized content
        return Task.Factory.StartNew(() =>
        {
            using (StreamWriter streamWriter = new StreamWriter(stream, SupportedEncodings.First()))
            {
                using (JsonTextWriter jsonTextWriter = new JsonTextWriter(streamWriter))
                {
                    serializer.Serialize(jsonTextWriter, value);
                }
            }
        });
    }
}

And finally, the CustomJsonResolver:

public class CustomJsonResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, Newtonsoft.Json.MemberSerialization memberSerialization)
    {
        var list = base.CreateProperties(type, memberSerialization);

        // Custom stuff for my app
        if (type == typeof(Foo))
        {
            RemoveProperty(list, "Bar");
            RemoveProperty(list, "Bar2");
        }

        return list;
    }

    private void RemoveProperty(IList<JsonProperty> list, string propertyName)
    {
        var rmc = list.FirstOrDefault(x => x.PropertyName == propertyName);

        if (rmc != null)
        {
            list.Remove(rmc);
        }
    }
}

OTHER TIPS

The JsonNetValueProviderFactory proposed here works better than the others I've tried (I had issues with arrays using Greg Ennis' one for example). This link also propose a solution to return Json from an action.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top