Utilizzare JSON.NET come serializzatore JSON predefinito in ASP.NET MVC 3: è possibile?


101

È possibile utilizzare JSON.NET come serializzatore JSON predefinito in ASP.NET MVC 3?

Secondo la mia ricerca, sembra che l'unico modo per ottenere ciò sia estendere ActionResult poiché JsonResult in MVC3 non è virtuale ...

Speravo che con ASP.NET MVC 3 ci fosse un modo per specificare un provider collegabile per la serializzazione in JSON.

Pensieri?


Risposte:


106

Credo che il modo migliore per farlo sia, come descritto nei tuoi link, estendere ActionResult o estendere JsonResult direttamente.

Per quanto riguarda il metodo JsonResult che non è virtuale sul controller non è vero, basta scegliere l'overload giusto. Funziona bene:

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

EDIT 1 : un'estensione 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);
    }

EDIT 2 : ho rimosso il controllo per i dati nulli secondo i suggerimenti di seguito. Ciò dovrebbe rendere felici le versioni più recenti di JQuery e sembra la cosa sensata da fare, poiché la risposta può quindi essere deserializzata incondizionatamente. Tieni presente, tuttavia, che questo non è il comportamento predefinito per le risposte JSON da ASP.NET MVC, che risponde piuttosto con una stringa vuota, quando non sono presenti dati.


1
Il codice fa riferimento a MySpecialContractResolver, che non è definito. Questa domanda aiuta con quello (ed era molto legato al problema che ho avuto da risolvere): stackoverflow.com/questions/6700053/...
Elliveny

1
Grazie per la magnifica risposta. Perché il ritorno if (Data == null); ? Per il mio caso d'uso, volevo recuperare qualunque fosse lo standard JSON, cosa che Json.Net fa fedelmente, anche per null (restituendo "null"). Intercettando i valori nulli si finisce per inviare indietro la stringa vuota per questi, che devia dallo standard e causa problemi a valle, ad esempio con jQuery 1.9.1: stackoverflow.com/a/15939945/176877
Chris Moschini

1
@Chris Moschini: hai perfettamente ragione. È sbagliato restituire una stringa vuota. Ma dovrebbe restituire il valore json null o un oggetto json vuoto allora? Non sono sicuro che la restituzione di un valore in cui è previsto un oggetto sia senza problemi. In ogni caso, il codice attuale non è valido sotto questo aspetto.
asgerhallas

1
C'è un bug in Json.Net che impedisce a IE9 e versioni precedenti di analizzare le date ISO 8601 prodotte da Json.Net. La correzione per questo è inclusa in questa risposta: stackoverflow.com/a/15939945/176877
Chris Moschini

1
@asgerhallas, @Chris Moschini E il controllo JsonResult di asp.net mvc predefinito if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);? Penso che sia necessario aggiungere questa risposta di controllo (senza interno MvcResources.JsonRequest_GetNotAllowedma con qualche messaggio personalizzato) Inoltre, che dire di altri 2 controlli mvc asp.net predefiniti: MaxJsonLength e RecursionLimit? Ne abbiamo bisogno se usiamo json.net?
Chromigo

60

L'ho implementato senza la necessità di un controller di base o di un'iniezione.

Ho usato i filtri di azione per sostituire 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);
    }
}

In Global.asax.cs Application_Start () dovresti aggiungere:

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

Per amor di completamento, ecco la mia classe di estensione JsonNetResult che ho raccolto da qualche altra parte e che ho modificato leggermente per ottenere il supporto corretto per lo steaming:

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

1
Questa è una bella soluzione. Fa in modo che il nativo return Json()in effetti utilizzi Json.Net.
OneHoopyFrood

1
Per chiunque si chieda come funziona, intercetta il JsonResultfrom Json()e lo converte in un file JsonNetResult. Lo fa utilizzando la asparola chiave che restituisce null se la conversione non è possibile. Molto elegante. 10 punti per Grifondoro!
OneHoopyFrood

4
Domanda, tuttavia, il serializzatore predefinito viene eseguito sull'oggetto prima che venga intercettato?
OneHoopyFrood

Questa è una risposta fantastica, con la massima flessibilità. Poiché il mio progetto stava già realizzando tutti i tipi di soluzioni manuali sul front-end, non potevo aggiungere un filtro globale: ciò richiederebbe un cambiamento più grande. Ho finito per risolvere il problema solo sulle azioni del controller, ove necessario, utilizzando l'attributo sulle azioni del mio controller. Tuttavia, l'ho chiamato - [BetterJsonHandler]:-).
Simcha Khabinsky

restituendo this.Json (null); ancora non restituisce nulla
Brunis

27

Usa il convertitore JSON di Newtonsoft:

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

7
Non sono sicuro che sia hacky o meno, ma è più facile che creare classi di estensione, solo per restituire una stupida stringa json.
dennis.sheppard

21

So che questo è bene dopo che la domanda è stata risolta, ma sto usando un approccio diverso poiché sto usando l'inserimento delle dipendenze per istanziare i miei controller.

Ho sostituito IActionInvoker (iniettando la proprietà ControllerActionInvoker del controller) con una versione che sostituisce il metodo InvokeActionMethod.

Ciò significa nessuna modifica all'ereditarietà del controller e può essere facilmente rimosso quando eseguo l'aggiornamento a MVC4 modificando la registrazione del contenitore DI per TUTTI i controller

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 - Aggiornato per mostrare la registrazione del contenitore per i controller. Sto usando Unity qui.

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

Bello, ma come lo usi? O meglio come l'hai iniettato?
Adaptabi

+1 per l'utilizzo della forma Stream di .Serialize (). Stavo per sottolineare che puoi semplicemente usare JsonConvert come l'altra risposta principale, ma il tuo approccio trasmette gradualmente oggetti lunghi / grandi: questo è un aumento gratuito delle prestazioni, soprattutto se il client a valle può gestire risposte parziali.
Chris Moschini

1
bella realizzazione. Questa dovrebbe essere la risposta!
Kat Lim Ruiz

Bene, questa era l'unica cosa per cui stavo usando un controller di base.
Chris Diver

davvero bello - questo è molto meglio che sovrascrivere la funzione Json (), poiché in ogni posizione in cui restituirai un JsonResult questo si attiverà e farà la sua magia. Per coloro che non utilizzano DI, è sufficiente aggiungere IActionInvoker CreateActionInvoker () {return new JsonNetActionInvoker ();} di override protetto al controller di base
Avi Pinto

13

Espandendo la risposta da https://stackoverflow.com/users/183056/sami-beyoglu , se imposti il ​​tipo di contenuto, jQuery sarà in grado di convertire i dati restituiti in un oggetto per te.

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

Grazie, ho un mix ibrido e questa è l'unica cosa che funzionerebbe per me.
done_merson

L'ho usato con JSON.NET in questo modo: JObject jo = GetJSON(); return Content(jo.ToString(), "application/json");
John Mott,

6

Il mio post può aiutare qualcuno.

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

Stavo cercando una soluzione reale e tu eri l'unica risposta corretta
Richard Aguirre

Grazie. Avendo già implementato il mio BaseController, questo è stato il cambiamento di impatto più basso: dovevo solo aggiungere la classe e aggiornare BaseController.
AndrewP

4

Ho creato una versione che rende le azioni del servizio web semplici e sicure. Lo usi in questo modo:

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

La classe:

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

ma perché vorresti avere un JsonResult fortemente digitato? : D perdi i risultati dei tipi anonimi e non guadagni nulla sul lato client, dato che non usa comunque le classi C #?
mikus

1
@mikus È typesafe sul lato server: il metodo deve restituire il tipo MyDataContract. Rende chiaro al lato client esattamente quale struttura dati viene restituita. È anche conciso e leggibile: JsonResult <T> converte automaticamente qualsiasi tipo restituito a Json e non devi fare nulla.
Curtis Yallop
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.