Using JSON.NET as the default JSON serializer in ASP.NET MVC 3 - is it possible?

asp.net Mvc-3json.netJsonresult

asp.net Mvc-3 Problem Overview


Is it possible to use JSON.NET as default JSON serializer in ASP.NET MVC 3?

According to my research, it seems that the only way to accomplish this is to extend ActionResult as JsonResult in MVC3 is not virtual...

I hoped that with ASP.NET MVC 3 that there would be a way to specify a pluggable provider for serializing to JSON.

Thoughts?

asp.net Mvc-3 Solutions


Solution 1 - asp.net Mvc-3

I believe the best way to do it, is - as described in your links - to extend ActionResult or extend JsonResult directly.

As for the method JsonResult that is not virtual on the controller that's not true, just choose the right overload. This works well:

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

EDIT 1: A JsonResult extension...

public class JsonNetResult : JsonResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        var response = context.HttpContext.Response;

        response.ContentType = !String.IsNullOrEmpty(ContentType) 
            ? ContentType 
            : "application/json";

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

        // If you need special handling, you can call another form of SerializeObject below
        var serializedObject = JsonConvert.SerializeObject(Data, Formatting.Indented);
        response.Write(serializedObject);
    }

EDIT 2: I removed the check for Data being null as per the suggestions below. That should make newer versions of JQuery happy and seems like the sane thing to do, as the response can then be unconditionally deserialized. Be aware though, that this is not the default behavior for JSON responses from ASP.NET MVC, which rather responds with an empty string, when there's no data.

Solution 2 - asp.net Mvc-3

I implemented this without the need of a base controller or injection.

I used action filters to replace the JsonResult with a JsonNetResult.

public class JsonHandlerAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
       var jsonResult = filterContext.Result as JsonResult;

        if (jsonResult != null)
        {
            filterContext.Result = new JsonNetResult
            {
                ContentEncoding = jsonResult.ContentEncoding,
                ContentType = jsonResult.ContentType,
                Data = jsonResult.Data,
                JsonRequestBehavior = jsonResult.JsonRequestBehavior
            };
        }

        base.OnActionExecuted(filterContext);
    }
}

In the Global.asax.cs Application_Start() you would need to add:

GlobalFilters.Filters.Add(new JsonHandlerAttribute());

For completion's sake, here is my JsonNetResult extention class that I picked up from somewhere else and that I modified slightly to get correct steaming support:

public class JsonNetResult : JsonResult
{
    public JsonNetResult()
    {
        Settings = new JsonSerializerSettings
        {
            ReferenceLoopHandling = ReferenceLoopHandling.Error
        };
    }

    public JsonSerializerSettings Settings { get; private set; }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            throw new InvalidOperationException("JSON GET is not allowed");

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

        if (this.ContentEncoding != null)
            response.ContentEncoding = this.ContentEncoding;
        if (this.Data == null)
            return;

        var scriptSerializer = JsonSerializer.Create(this.Settings);
        scriptSerializer.Serialize(response.Output, this.Data);
    }
}

Solution 3 - asp.net Mvc-3

Use Newtonsoft's JSON converter:

public ActionResult DoSomething()
{
    dynamic cResponse = new ExpandoObject();
    cResponse.Property1 = "value1";
    cResponse.Property2 = "value2";
    return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}

Solution 4 - asp.net Mvc-3

I know this is well after the question has been answered, but I'm using a different approach as I am using dependency injection to instantiate my controllers.

I have replaced the IActionInvoker ( by injecting the controller's ControllerActionInvoker Property ) with a version that overrides the InvokeActionMethod method.

This means no change to controller inheritance and it can be easily removed when I upgrade to MVC4 by altering the DI container's registration for ALL controllers

public class JsonNetActionInvoker : ControllerActionInvoker
{
    protected override ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
    {
        ActionResult invokeActionMethod = base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);

        if ( invokeActionMethod.GetType() == typeof(JsonResult) )
        {
            return new JsonNetResult(invokeActionMethod as JsonResult);
        }

        return invokeActionMethod;
    }

    private class JsonNetResult : JsonResult
    {
        public JsonNetResult()
        {
            this.ContentType = "application/json";
        }

        public JsonNetResult( JsonResult existing )
        {
            this.ContentEncoding = existing.ContentEncoding;
            this.ContentType = !string.IsNullOrWhiteSpace(existing.ContentType) ? existing.ContentType : "application/json";
            this.Data = existing.Data;
            this.JsonRequestBehavior = existing.JsonRequestBehavior;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            if ((this.JsonRequestBehavior == JsonRequestBehavior.DenyGet) && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            {
                base.ExecuteResult(context);                            // Delegate back to allow the default exception to be thrown
            }

            HttpResponseBase response = context.HttpContext.Response;
            response.ContentType = this.ContentType;
            
            if (this.ContentEncoding != null)
            {
                response.ContentEncoding = this.ContentEncoding;
            }

            if (this.Data != null)
            {
                // Replace with your favourite serializer.  
                new Newtonsoft.Json.JsonSerializer().Serialize( response.Output, this.Data );
            }
        }
    }
}

--- EDIT - Updated to show container registration for controllers. I'm using Unity here.

private void RegisterAllControllers(List<Type> exportedTypes)
{
    this.rootContainer.RegisterType<IActionInvoker, JsonNetActionInvoker>();
    Func<Type, bool> isIController = typeof(IController).IsAssignableFrom;
    Func<Type, bool> isIHttpController = typeof(IHttpController).IsAssignableFrom;

    foreach (Type controllerType in exportedTypes.Where(isIController))
    {
        this.rootContainer.RegisterType(
            typeof(IController),
            controllerType, 
            controllerType.Name.Replace("Controller", string.Empty),
            new InjectionProperty("ActionInvoker")
        );
    }

    foreach (Type controllerType in exportedTypes.Where(isIHttpController))
    {
        this.rootContainer.RegisterType(typeof(IHttpController), controllerType, controllerType.Name);
    }
}

public class UnityControllerFactory : System.Web.Mvc.IControllerFactory, System.Web.Http.Dispatcher.IHttpControllerActivator
{
    readonly IUnityContainer container;

    public UnityControllerFactory(IUnityContainer container)
    {
        this.container = container;
    }

    IController System.Web.Mvc.IControllerFactory.CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        return this.container.Resolve<IController>(controllerName);
    }

    SessionStateBehavior System.Web.Mvc.IControllerFactory.GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
    {
        return SessionStateBehavior.Required;
    }

    void System.Web.Mvc.IControllerFactory.ReleaseController(IController controller)
    {
    }

    IHttpController IHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
    {
        return this.container.Resolve<IHttpController>(controllerType.Name);
    }
}

Solution 5 - asp.net Mvc-3

Expanding on the answer from https://stackoverflow.com/users/183056/sami-beyoglu, if you set the Content type, then jQuery will be able to convert the returned data into an object for you.

public ActionResult DoSomething()
{
    dynamic cResponse = new ExpandoObject();
    cResponse.Property1 = "value1";
    cResponse.Property2 = "value2";
    return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}

Solution 6 - asp.net Mvc-3

My Post may help someone.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
namespace MultipleSubmit.Service
{
    public abstract class BaseController : Controller
    {
        protected override JsonResult Json(object data, string contentType,
            Encoding contentEncoding, JsonRequestBehavior behavior)
        {
            return new JsonNetResult
            {
                Data = data,
                ContentType = contentType,
                ContentEncoding = contentEncoding,
                JsonRequestBehavior = behavior
            };
        }
    }
}


using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MultipleSubmit.Service
{
    public class JsonNetResult : JsonResult
    {
        public JsonNetResult()
        {
            Settings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Error
            };
        }
        public JsonSerializerSettings Settings { get; private set; }
        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals
(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
                throw new InvalidOperationException("JSON GET is not allowed");
            HttpResponseBase response = context.HttpContext.Response;
            response.ContentType = string.IsNullOrEmpty(this.ContentType) ? 
"application/json" : this.ContentType;
            if (this.ContentEncoding != null)
                response.ContentEncoding = this.ContentEncoding;
            if (this.Data == null)
                return;
            var scriptSerializer = JsonSerializer.Create(this.Settings);
            using (var sw = new StringWriter())
            {
                scriptSerializer.Serialize(sw, this.Data);
                response.Write(sw.ToString());
            }
        }
    }
} 

public class MultipleSubmitController : BaseController
{
   public JsonResult Index()
    {
      var data = obj1;  // obj1 contains the Json data
      return Json(data, JsonRequestBehavior.AllowGet);
    }
}    

Solution 7 - asp.net Mvc-3

I made a version that makes web service actions type-safe and simple. You use it like this:

public JsonResult<MyDataContract> MyAction()
{
    return new MyDataContract();
}

The class:

public class JsonResult<T> : JsonResult
{
    public JsonResult(T data)
    {
        Data = data;
        JsonRequestBehavior = JsonRequestBehavior.AllowGet;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        // Use Json.Net rather than the default JavaScriptSerializer because it's faster and better

        if (context == null)
            throw new ArgumentNullException("context");

        var response = context.HttpContext.Response;

        response.ContentType = !String.IsNullOrEmpty(ContentType)
            ? ContentType
            : "application/json";

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

        var serializedObject = JsonConvert.SerializeObject(Data, Formatting.Indented);
        response.Write(serializedObject);
    }

    public static implicit operator JsonResult<T>(T d)
    {
        return new JsonResult<T>(d);
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionzam6akView Question on Stackoverflow
Solution 1 - asp.net Mvc-3asgerhallasView Answer on Stackoverflow
Solution 2 - asp.net Mvc-3MDBView Answer on Stackoverflow
Solution 3 - asp.net Mvc-3Sami BeyogluView Answer on Stackoverflow
Solution 4 - asp.net Mvc-3Robert SlaneyView Answer on Stackoverflow
Solution 5 - asp.net Mvc-3StokeStokeView Answer on Stackoverflow
Solution 6 - asp.net Mvc-3MistView Answer on Stackoverflow
Solution 7 - asp.net Mvc-3Curtis YallopView Answer on Stackoverflow