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

7

103

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?

Scammony answered 18/8, 2011 at 15:25 Comment(1)
related: #6883704Toomer
U
106

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.

Upstairs answered 22/8, 2011 at 17:5 Comment(10)
Thanks - I though I was on the right track, and you confirmed it...As for JsonResult not being virtual, I didn't even bother to check for other method signatures :), I just took it as granted based on the comment from Maxim here - #6883704Scammony
The code refers to MySpecialContractResolver, which isn't defined. This question helps with that (and was very related to the problem I had to solve): #6700553Gummy
Oh yes, setting a special ContractResolver as I do in the code above, actually has nothing to do with the solution to this question - just happened to be, what I needed at the time :) Thanks for pointing that out. The serializer can be configured any way one sees fit.Upstairs
I edited the code sample to call the standard SerializeObject method, so that it will compile as-is... less confusing to new JSON.Net users.Hightension
Thanks for the great answer. Why the if (Data == null) return; ? For my use case I wanted to get back whatever the JSON standard was, which Json.Net faithfully does, even for null (returning "null"). By intercepting null values you end up sending the empty string back for these, which deviates from the standard and causes downstream problems - for example with jQuery 1.9.1: https://mcmap.net/q/45695/-jquery-post-json-fails-when-returning-null-from-asp-net-mvcWellmeaning
@Chris Moschini: You're absolutely right. It is wrong to return an empty string. But should it return the json value null or an empty json object then? I'm not sure returning a value where an object is expected is problem free either. But either way, the current code is not good in this respect.Upstairs
@Upstairs It should return "null" which is valid JSON for null (and parses properly in jQuery 1.9.1 as a null). Json.Net accurately produces exactly that.Wellmeaning
There's a bug in Json.Net that causes IE9 and below to fail to parse the ISO 8601 Dates Json.Net produces. Fix for this is included in this answer: https://mcmap.net/q/45695/-jquery-post-json-fails-when-returning-null-from-asp-net-mvcWellmeaning
@asgerhallas, @Chris Moschini What about default asp.net mvc JsonResult check if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);? I think need to add this check in answer(without internal MvcResources.JsonRequest_GetNotAllowed but with some custom message) Also, what about 2 other default asp.net mvc checks - MaxJsonLength and RecursionLimit? Do we need them if we use json.net?Mclean
I am having trouble taking this answer and implementing it into my controller methods. Can someone sketch out the usage fully?Epigynous
C
67

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);
    }
}
Chansoo answered 8/5, 2014 at 14:37 Comment(6)
This is a nice solution. Makes it so the native return Json() in effect uses Json.Net.Perionychium
For anyone wondering just how this works, it intercepts the JsonResult from Json() and converts it to a JsonNetResult. It does so using the as keyword which returns null if the conversion isn't possible. Very nifty. 10 points for Gryffindor!Perionychium
Question though, does the default serializer run on the object before it's intercepted?Perionychium
This is a fantastic answer - with the most flexibility. Since my project was already doing all kinds of manual solutions on the front end, I could not add a global filter - this would require a bigger change. I ended up just solving the problem only on the controller actions where necessary by using the attribute on my controller's actions. However, I called it - [BetterJsonHandler] :-).Stinger
returning this.Json(null); still returns nothingPolyandry
@Perionychium Yes, the controller uses the default JavaScriptSerializer to serialize first, later the filter comes in place and converts that to JsonNetResult. I don't think would improve performance in any way, just formatting though.Austenite
E
29

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");
}
Erkan answered 25/5, 2013 at 17:37 Comment(2)
Not sure if this is hacky or not, but holy crap is it easier than creating extension classes, just to return a stupid json string.Cornellcornelle
I don't think this is a hack, but if you create your own CustomJsonSerializer then you don't need repeat this line Content(JsonConvert.SerializeObject(cResponse), "application/json"); in every Action method that returns json. You would put the serialization logic in the CustomJsonSerializer. Also note that CustomJsonSerializer does some extra error handling which you probably don't want to repeat in your Action method.Sniff
K
21

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);
    }
}
Kropotkin answered 9/8, 2012 at 7:33 Comment(7)
Nice, but how do you use it? Or better how did you inject it?Inept
+1 for using the Stream form of .Serialize(). I was going to point out you can just use JsonConvert like the other top answer, but your approach gradually streams out long/large objects - that's a free performance boost, especially if the downstream client can handle partial responses.Wellmeaning
nice implementation. This should be the answer!Hermaphroditism
Good going, this was the only thing i was using a base controller for.Christoffer
really nice - this is much better then overriding the Json() function, since in every location where you will return a JsonResult this will kick in and do it's magic. For those who don't use DI, just add protected override IActionInvoker CreateActionInvoker() { return new JsonNetActionInvoker();} to your base controllerTectrix
Only problem i suspect there might be is serialization occurs twice. One with JavaScriptSerializer and one with JSONNet. I guess ExecuteResult method in JsonResult will execute anyway because of "base.InvokeActionMethod(controllerContext, actionDescriptor, parameters)". As far as I see, you just overwrite the return value.Leitmotif
I only invoke the base implementation if I need to raise an exception because of HttpVerb issues.Kropotkin
A
13

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");
}
Angiology answered 21/4, 2015 at 10:30 Comment(2)
Thank you, I have a hybrid mix and this is the only thing that would work for me.Myself
I used this with JSON.NET like this: JObject jo = GetJSON(); return Content(jo.ToString(), "application/json");Colombo
R
7

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);
    }
}    
Rowney answered 7/5, 2018 at 11:8 Comment(1)
Thanks. Having already implemented my own BaseController, this was the lowest impact chang - just had to add the class and update BaseController.Bounded
C
5

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);
    }
}
Commonwealth answered 22/5, 2015 at 4:2 Comment(4)
but why would you want to have a strongly types JsonResult? :D you loose the anonymous types results and earn nothing on the client side, as its not using C# classses anyway?Citron
@Citron It is typesafe on the server side: the method must return type MyDataContract. It makes it clear to the client side exactly what data structure is being returned. It is also concise and readable - JsonResult<T> autoconverts any type being returned to Json and you don't have to do anything.Commonwealth
Exactly. I find it silly that everything I return is a JsonResult or string (for Newtonsoft) even for actions that return generic collections. That's an extra added step for any unit testing because you have to deserialize everything again. It seems that in Core at least you can return an IEnumerable<T> now and it will serialize, but I don't think it works .NET Framework. (I'd love to be wrong about that, but I can't get it to work.)Fireeater
To add to the answer, I ended up adding an extension method: public static JsonResult<T> Preserialize<T>(this T data) where T: class => new JsonResult<T>(data). That way you don't have to write long types out every time. So example usage would be Enumerable.Range(0, 100).Preserialize() I tried defining an implicit conversion, but they don't work on interface types.Fireeater

© 2022 - 2024 — McMap. All rights reserved.