61

I'm working on an existing application that has been partially converted over to MVC. Whenever a controller responds with a JSON ActionResult, the enums are sent as numbers opposed to the string name. It sounds like the default serializer should be JSON.Net, which should be sending the enums over as their names opposed to the integer representation, but that's not the case here.

Am I missing a web.config setting that sets this as the default serializer? Or is there another setting that needs to be changed?

Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
John
  • 17,163
  • 16
  • 65
  • 83

2 Answers2

73

In ASP.Net MVC4 the default JavaScript serializer which is used in the JsonResult class is still the JavaScriptSerializer (you can check it in the code)

I think you have confused it with the ASP.Net Web.API where JSON.Net is the default JS serializer but MVC4 doesn't use it.

So you need to configure JSON.Net to work with MVC4 (basically you need to create your own JsonNetResult), there are plenty of articles about it:

If you also want to use JSON.Net for controller action parameters so during the model binding then you need write your own ValueProviderFactory implementation.

And you need to register your implementation with:

ValueProviderFactories.Factories
    .Remove(ValueProviderFactories.Factories
                                  .OfType<JsonValueProviderFactory>().Single());
ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory());

You can use the built in JsonValueProviderFactory as an example or this article: ASP.NET MVC 3 – Improved JsonValueProviderFactory using Json.Net

Community
  • 1
  • 1
nemesv
  • 138,284
  • 16
  • 416
  • 359
  • 1
    That helps with sending JSON responses, but not with deserializing incoming JSON as the framework attempts to map incoming calls into parameters passed into your action methods . . . how do you change the serializer used by MVC for that purpose? – blaster Oct 06 '13 at 00:14
  • 5
    If you want to use Json.NET for incoming paramters you need write your own `ValueProviderFactory` implementation. You can use the built in `JsonValueProviderFactory` as an example. And you need to register your implementation with: `ValueProviderFactories.Factories.Remove( ValueProviderFactories.Factories.OfType().Single()); ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory());`. See also: http://www.dalsoft.co.uk/blog/index.php/2012/01/10/asp-net-mvc-3-improved-jsonvalueproviderfactory-using-json-net/ – nemesv Oct 06 '13 at 06:49
  • +1 for how to bind controller action parameters, most question/answers i find are about just sending JSON. – snajahi Apr 12 '14 at 05:43
  • I used a slightly modified version of the referenced code, and on an action with a single string input, like `public ResultObject DoThis(string input)` the line `JSONReader.Read` dies with an exception like "Inappropriate character, the text isn't JSON". Weirder is that as a URL param it's fine, but it throws in the POST body. Any thoughts? – drzaus Aug 14 '14 at 17:11
  • @drzaus to answer my question, it seems that a form-data encoded request, rather than a raw JSON (via [POSTMAN](https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en)), will trigger my issue, since it tries to deserialize essentially gibberish – drzaus Aug 14 '14 at 17:31
  • @nemesv Is JavaScript serializer the default serializer in MVC 5? – Marc Sep 09 '15 at 13:25
  • 3
    @Marc according to the source code: https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/JsonResult.cs the `JsonResult` in MVC5 still uses the old `JavaScriptSerializer` and **does not** uses JSON.net – nemesv Sep 09 '15 at 18:59
  • When I try to register the new ValueProvider I get an error in the Factories.OfType. It says the OfType does not exist. Does this still work in MVC4? – jpgrassi Sep 24 '15 at 13:25
  • 1
    @jpgrassi `OfType` is an extension method definied in the `System.Linq` namespace. Do you have the `using System.Linq;` in your cs file? – nemesv Sep 24 '15 at 17:04
  • Yeah forgot about that. Solved now! – jpgrassi Sep 24 '15 at 17:20
  • 3
    For the record, MVC 6 finally uses Json.NET as the default. – John Apr 28 '16 at 14:30
  • clarify ambiguity, public override System.Web.Mvc.IValueProvider GetValueProvider(ControllerContext controllerContext) – Aerokneeus Jun 09 '16 at 17:13
7

ASP.NET MVC 5 Fix:

I wasn't ready to change to Json.NET just yet and in my case the error was occurring during the request. Best approach in my scenario was modifying the actual JsonValueProviderFactory which applies the fix to the global project and can be done by editing the global.cs file as such.

JsonValueProviderConfig.Config(ValueProviderFactories.Factories);

add a web.config entry:

<add key="aspnet:MaxJsonLength" value="20971520" />

and then create the two following classes

public class JsonValueProviderConfig
{
    public static void Config(ValueProviderFactoryCollection factories)
    {
        var jsonProviderFactory = factories.OfType<JsonValueProviderFactory>().Single();
        factories.Remove(jsonProviderFactory);
        factories.Add(new CustomJsonValueProviderFactory());
    }
}

This is basically an exact copy of the default implementation found in System.Web.Mvc but with the addition of a configurable web.config appsetting value aspnet:MaxJsonLength.

public class CustomJsonValueProviderFactory : ValueProviderFactory
{

    /// <summary>Returns a JSON value-provider object for the specified controller context.</summary>
    /// <returns>A JSON value-provider object for the specified controller context.</returns>
    /// <param name="controllerContext">The controller context.</param>
    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        if (controllerContext == null)
            throw new ArgumentNullException("controllerContext");

        object deserializedObject = CustomJsonValueProviderFactory.GetDeserializedObject(controllerContext);
        if (deserializedObject == null)
            return null;

        Dictionary<string, object> strs = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        CustomJsonValueProviderFactory.AddToBackingStore(new CustomJsonValueProviderFactory.EntryLimitedDictionary(strs), string.Empty, deserializedObject);

        return new DictionaryValueProvider<object>(strs, CultureInfo.CurrentCulture);
    }

    private static object GetDeserializedObject(ControllerContext controllerContext)
    {
        if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
            return null;

        string fullStreamString = (new StreamReader(controllerContext.HttpContext.Request.InputStream)).ReadToEnd();
        if (string.IsNullOrEmpty(fullStreamString))
            return null;

        var serializer = new JavaScriptSerializer()
        {
            MaxJsonLength = CustomJsonValueProviderFactory.GetMaxJsonLength()
        };
        return serializer.DeserializeObject(fullStreamString);
    }

    private static void AddToBackingStore(EntryLimitedDictionary backingStore, string prefix, object value)
    {
        IDictionary<string, object> strs = value as IDictionary<string, object>;
        if (strs != null)
        {
            foreach (KeyValuePair<string, object> keyValuePair in strs)
                CustomJsonValueProviderFactory.AddToBackingStore(backingStore, CustomJsonValueProviderFactory.MakePropertyKey(prefix, keyValuePair.Key), keyValuePair.Value);

            return;
        }

        IList lists = value as IList;
        if (lists == null)
        {
            backingStore.Add(prefix, value);
            return;
        }

        for (int i = 0; i < lists.Count; i++)
        {
            CustomJsonValueProviderFactory.AddToBackingStore(backingStore, CustomJsonValueProviderFactory.MakeArrayKey(prefix, i), lists[i]);
        }
    }

    private class EntryLimitedDictionary
    {
        private static int _maximumDepth;

        private readonly IDictionary<string, object> _innerDictionary;

        private int _itemCount;

        static EntryLimitedDictionary()
        {
            _maximumDepth = CustomJsonValueProviderFactory.GetMaximumDepth();
        }

        public EntryLimitedDictionary(IDictionary<string, object> innerDictionary)
        {
            this._innerDictionary = innerDictionary;
        }

        public void Add(string key, object value)
        {
            int num = this._itemCount + 1;
            this._itemCount = num;
            if (num > _maximumDepth)
            {
                throw new InvalidOperationException("The length of the string exceeds the value set on the maxJsonLength property.");
            }
            this._innerDictionary.Add(key, value);
        }
    }

    private static string MakeArrayKey(string prefix, int index)
    {
        return string.Concat(prefix, "[", index.ToString(CultureInfo.InvariantCulture), "]");
    }

    private static string MakePropertyKey(string prefix, string propertyName)
    {
        if (string.IsNullOrEmpty(prefix))
        {
            return propertyName;
        }
        return string.Concat(prefix, ".", propertyName);
    }

    private static int GetMaximumDepth()
    {
        int num;
        NameValueCollection appSettings = ConfigurationManager.AppSettings;
        if (appSettings != null)
        {
            string[] values = appSettings.GetValues("aspnet:MaxJsonDeserializerMembers");
            if (values != null && values.Length != 0 && int.TryParse(values[0], out num))
            {
                return num;
            }
        }
        return 1000;
    }

    private static int GetMaxJsonLength()
    {
        int num;
        NameValueCollection appSettings = ConfigurationManager.AppSettings;
        if (appSettings != null)
        {
            string[] values = appSettings.GetValues("aspnet:MaxJsonLength");
            if (values != null && values.Length != 0 && int.TryParse(values[0], out num))
            {
                return num;
            }
        }
        return 1000;
    }
}
Maxim Gershkovich
  • 45,951
  • 44
  • 147
  • 243