Usar JSON.NET como serializador JSON predeterminado en ASP.NET MVC 3, ¿es posible?

Respuestas:

106

Creo que la mejor manera de hacerlo es, como se describe en sus enlaces, extender ActionResult o extender JsonResult directamente.

En cuanto al método JsonResult que no es virtual en el controlador, eso no es cierto, simplemente elija la sobrecarga correcta. Esto funciona bien:

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

EDITAR 1 : Una extensión JsonResult ...

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);
    }

EDITAR 2 : eliminé la verificación de que los datos eran nulos según las sugerencias a continuación. Eso debería hacer felices a las versiones más nuevas de JQuery y parece lo más sensato, ya que la respuesta se puede deserializar incondicionalmente. Sin embargo, tenga en cuenta que este no es el comportamiento predeterminado para las respuestas JSON de ASP.NET MVC, que más bien responde con una cadena vacía, cuando no hay datos.

asgerhallas
fuente
1
El código se refiere a MySpecialContractResolver, que no está definido. Esta pregunta ayuda con eso (y estaba muy relacionada con el problema que tuve que resolver): stackoverflow.com/questions/6700053/…
Elliveny
1
Gracias por la gran respuesta. Por qué el retorno if (Data == null); ? Para mi caso de uso, quería recuperar lo que fuera el estándar JSON, lo que Json.Net hace fielmente, incluso para nulo (devolviendo "nulo"). Al interceptar valores nulos, termina enviando la cadena vacía para estos, lo que se desvía del estándar y causa problemas posteriores, por ejemplo, con jQuery 1.9.1: stackoverflow.com/a/15939945/176877
Chris Moschini
1
@Chris Moschini: Tienes toda la razón. Es incorrecto devolver una cadena vacía. Pero, ¿debería devolver el valor json nulo o un objeto json vacío entonces? Tampoco estoy seguro de que devolver un valor donde se espera un objeto esté libre de problemas. Pero de cualquier manera, el código actual no es bueno a este respecto.
asgerhallas
1
Hay un error en Json.Net que hace que IE9 y versiones posteriores no puedan analizar las fechas ISO 8601 que produce Json.Net. La solución para esto se incluye en esta respuesta: stackoverflow.com/a/15939945/176877
Chris Moschini
1
@asgerhallas, @Chris Moschini ¿Qué pasa con la comprobación predeterminada de asp.net mvc JsonResult if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);? Creo que es necesario agregar esta respuesta de verificación (sin interna MvcResources.JsonRequest_GetNotAllowedpero con algún mensaje personalizado) Además, ¿qué pasa con otras 2 verificaciones de mvc de asp.net predeterminadas: MaxJsonLength y RecursionLimit? ¿Los necesitamos si usamos json.net?
chromigo
60

Implementé esto sin la necesidad de un controlador base o inyección.

Usé filtros de acción para reemplazar el JsonResult con un 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);
    }
}

En Global.asax.cs Application_Start () necesitaría agregar:

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

Para completar, aquí está mi clase de extensión JsonNetResult que recogí de otro lugar y que modifiqué ligeramente para obtener el soporte de vaporización correcto:

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);
    }
}
MDB
fuente
1
Ésta es una buena solución. Hace que el nativo return Json()utilice Json.Net.
OneHoopyFrood
1
Para cualquiera que se pregunte cómo funciona esto, intercepta el JsonResultde Json()y lo convierte en un JsonNetResult. Lo hace utilizando la aspalabra clave que devuelve nulo si la conversión no es posible. Muy ingenioso. ¡10 puntos para Gryffindor!
OneHoopyFrood
4
Sin embargo, pregunta, ¿el serializador predeterminado se ejecuta en el objeto antes de ser interceptado?
OneHoopyFrood
Esta es una respuesta fantástica, con la mayor flexibilidad. Dado que mi proyecto ya estaba realizando todo tipo de soluciones manuales en la interfaz, no podía agregar un filtro global; esto requeriría un cambio mayor. Terminé resolviendo el problema solo en las acciones del controlador cuando fuera necesario utilizando el atributo en las acciones de mi controlador. Sin embargo, lo llamé - [BetterJsonHandler]:-).
Simcha Khabinsky
devolviendo this.Json (nulo); todavía no devuelve nada
Brunis
27

Utilice el convertidor JSON de Newtonsoft:

public ActionResult DoSomething()
{
    dynamic cResponse = new ExpandoObject();
    cResponse.Property1 = "value1";
    cResponse.Property2 = "value2";
    return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}
Sami Beyoglu
fuente
7
No estoy seguro de si esto es hacky o no, pero santa mierda es más fácil que crear clases de extensión, solo para devolver una cadena json estúpida.
dennis.sheppard
21

Sé que esto sucede mucho después de que se haya respondido la pregunta, pero estoy usando un enfoque diferente ya que estoy usando la inyección de dependencia para crear instancias de mis controladores.

Reemplacé IActionInvoker (inyectando la propiedad ControllerActionInvoker del controlador) con una versión que anula el método InvokeActionMethod.

Esto significa que no hay cambios en la herencia del controlador y se puede eliminar fácilmente cuando actualizo a MVC4 modificando el registro del contenedor DI para TODOS los controladores

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 );
            }
        }
    }
}

--- EDITAR: actualizado para mostrar el registro de contenedores para los controladores. Estoy usando Unity aquí.

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);
    }
}
Robert Slaney
fuente
Bien, pero ¿cómo lo usas? O mejor ¿cómo lo inyectaste?
Adaptabi
+1 para usar la forma Stream de .Serialize (). Iba a señalar que puede usar JsonConvert como la otra respuesta principal, pero su enfoque transmite gradualmente objetos largos / grandes; eso es un aumento de rendimiento gratuito, especialmente si el cliente descendente puede manejar respuestas parciales.
Chris Moschini
1
buena implementación. ¡Esta debería ser la respuesta!
Kat Lim Ruiz
Bien, esto era lo único para lo que estaba usando un controlador base.
Chris Diver
realmente agradable, esto es mucho mejor que anular la función Json (), ya que en cada ubicación donde devolverá un JsonResult, esto se activará y hará su magia. Para aquellos que no usan DI, simplemente agregue la anulación protegida IActionInvoker CreateActionInvoker () {return new JsonNetActionInvoker ();} a su controlador base
Avi Pinto
13

Ampliando la respuesta de https://stackoverflow.com/users/183056/sami-beyoglu , si establece el tipo de contenido, jQuery podrá convertir los datos devueltos en un objeto para usted.

public ActionResult DoSomething()
{
    dynamic cResponse = new ExpandoObject();
    cResponse.Property1 = "value1";
    cResponse.Property2 = "value2";
    return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}
StokeStoke
fuente
Gracias, tengo una mezcla híbrida y esto es lo único que funcionaría para mí.
done_merson
Usé esto con JSON.NET así: JObject jo = GetJSON(); return Content(jo.ToString(), "application/json");
John Mott
6

Mi publicación puede ayudar a alguien.

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);
    }
}    
Niebla
fuente
Estaba encontrando una solución real y tú eras la única respuesta correcta
Richard Aguirre
Gracias. Habiendo implementado el mío BaseController, este fue el cambio de menor impacto: solo tuve que agregar la clase y actualizar BaseController.
AndrewP
4

Hice una versión que hace que las acciones de servicios web sean seguras y sencillas. Lo usas así:

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

La clase:

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);
    }
}
Curtis Yallop
fuente
pero ¿por qué querría tener un JsonResult de tipos fuertes? : ¿D pierde los resultados de tipos anónimos y no gana nada en el lado del cliente, ya que de todos modos no usa clases de C #?
mikus
1
@mikus Es tipo seguro en el lado del servidor: el método debe devolver el tipo MyDataContract. Deja claro al lado del cliente exactamente qué estructura de datos se está devolviendo. También es conciso y legible: JsonResult <T> convierte automáticamente cualquier tipo que se devuelve a Json y no tiene que hacer nada.
Curtis Yallop