Submitting my first bug after ASP.NET MVC 1.0 RTM Release

The Controller class of ASP.NET MVC Framework has few overloaded methods to return json result from the controller actions. Internally it uses the JavaScriptSerializer which was introduced in ASP.NET AJAX Framework. But unlike the DataContractJsonSerializer which works on attributes, the only way you can control the json output in JavaScriptSerializer class is by registering the JavaScriptConverter classes. In most of the cases you don’t have to control the way the json data is shaping, but in some situation you might need to step in. If you check the overloaded Json methods, none of it takes the JavaScriptConverters as argument.

protected internal JsonResult Json(object data);

protected internal JsonResult Json(object data, string contentType)

protected internal virtual JsonResult Json(object data, string contentType, Encoding contentEncoding)

And in the ExecuteResult method of JsonResult it does not register the JavaScriptConverters from the from the web.config.

public override void ExecuteResult(ControllerContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException("context");
    }

    HttpResponseBase response = context.HttpContext.Response;

    if (!String.IsNullOrEmpty(ContentType))
    {
        response.ContentType = ContentType;
    }
    else
    {
        response.ContentType = "application/json";
    }
    if (ContentEncoding != null)
    {
        response.ContentEncoding = ContentEncoding;
    }
    if (Data != null)
    {
        // The JavaScriptSerializer type was marked as obsolete prior to .NET Framework 3.5 SP1
        #pragma warning disable 0618
        var serializer = new JavaScriptSerializer();
        response.Write(serializer.Serialize(Data));
        #pragma warning restore 0618
    }
}

And I think it is a Designing defect/bug.

The Resolution:

First we will create a class which will inherit from the JsonResult, it will have an extra property Converters, so from Controllers you can pass the JavaScriptConverter classes and it will also lookup in the configuration for the registered JavaScriptConverter classes.

public class CorrectJsonResult : JsonResult
{
    public IList<JavaScriptConverter> Converters
    {
        get;
        set;
    }

    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";

        if (ContentEncoding != null)
        {
            response.ContentEncoding = ContentEncoding;
        }

        if (Data != null)
        {
            JavaScriptSerializer serializer = CreateJsonSerializer();

            response.Write(serializer.Serialize(Data));
        }
    }

    private JavaScriptSerializer CreateJsonSerializer()
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();

        ScriptingJsonSerializationSection section = ConfigurationManager.GetSection("system.web.extensions/scripting/webServices/jsonSerialization") as ScriptingJsonSerializationSection;

        if (section != null)
        {
            if ((section.Converters != null) && (section.Converters.Count > 0))
            {
                IEnumerable<JavaScriptConverter> converters = CreateConvertersFrom(section.Converters);
                serializer.RegisterConverters(converters);
            }

            serializer.MaxJsonLength = section.MaxJsonLength;
            serializer.RecursionLimit = section.RecursionLimit;
        }

        if ((Converters != null) && (Converters.Count > 0))
        {
            serializer.RegisterConverters(Converters);
        }

        return serializer;
    }

    private static IEnumerable<JavaScriptConverter> CreateConvertersFrom(ConvertersCollection typeDefinitions)
    {
        foreach (Converter definition in typeDefinitions)
        {
            Type type = BuildManager.GetType(definition.Type, false);

            if (type == null)
            {
                throw new ArgumentException("Unknown type.", definition.Type);
            }

            if (!typeof(JavaScriptConverter).IsAssignableFrom(type))
            {
                throw new ArgumentException("Unsupported type.", definition.Type);
            }

            yield return (JavaScriptConverter) Activator.CreateInstance(type);
        }

        yield break;
    }
}

As you can see we are not only registering the converters, we are also setting the appropriate MaxJsonLength and RecursionLimit from the configuration section(system.web.extensions/scripting/webServices/jsonSerialization).

Next, we will create an abstract Controller for the above fixed JsonResult.

public abstract class CorrectJsonResultController : Controller
{
    protected override JsonResult Json(object data, string contentType, Encoding contentEncoding)
    {
        return Json(data, contentType, contentEncoding, new List<JavaScriptConverter>());
    }

    protected virtual CorrectJsonResult Json(object data, string contentType, Encoding contentEncoding, IEnumerable<JavaScriptConverter> converters)
    {
        return new CorrectJsonResult
                   {
                       Data = data,
                       ContentType = contentType,
                       ContentEncoding = contentEncoding,
                       Converters = new List<JavaScriptConverter>(converters)
                   };
    }
}

I have filled a bug, if you want it fixed in the next version of ASP.NET MVC, do vote it.

Download: CorrectJson.zip

Shout it

No Comments