Verwenden von JSON.NET als Standard-JSON-Serializer in ASP.NET MVC 3 - ist dies möglich?

101

Ist es möglich, JSON.NET als Standard-JSON-Serializer in ASP.NET MVC 3 zu verwenden?

Nach meinen Recherchen scheint die einzige Möglichkeit, dies zu erreichen, darin zu bestehen , ActionResult zu erweitern, da JsonResult in MVC3 nicht virtuell ist ...

Ich hoffte, dass es mit ASP.NET MVC 3 eine Möglichkeit geben würde, einen steckbaren Anbieter für die Serialisierung in JSON anzugeben.

Gedanken?

zam6ak
quelle
Verwandte: stackoverflow.com/questions/6883204/…
Ruben Bartelink

Antworten:

106

Ich glaube, der beste Weg, dies zu tun, besteht - wie in Ihren Links beschrieben - darin, ActionResult oder JsonResult direkt zu erweitern.

Für die Methode JsonResult, die auf dem Controller nicht virtuell ist und nicht wahr ist, wählen Sie einfach die richtige Überladung. Das funktioniert gut:

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

EDIT 1 : Eine JsonResult-Erweiterung ...

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

BEARBEITEN 2 : Ich habe die Prüfung auf Null gemäß den folgenden Vorschlägen entfernt. Das sollte neuere Versionen von JQuery glücklich machen und scheint vernünftig zu sein, da die Antwort dann bedingungslos deserialisiert werden kann. Beachten Sie jedoch, dass dies nicht das Standardverhalten für JSON-Antworten von ASP.NET MVC ist, die eher mit einer leeren Zeichenfolge antworten, wenn keine Daten vorhanden sind.

Asgerhallas
quelle
1
Der Code bezieht sich auf MySpecialContractResolver, der nicht definiert ist. Diese Frage hilft dabei (und war sehr verwandt mit dem Problem, das ich lösen musste): stackoverflow.com/questions/6700053/…
Elliveny
1
Danke für die tolle Antwort. Warum das if (Data == null) zurückgibt; ? Für meinen Anwendungsfall wollte ich zurückbekommen, was auch immer der JSON-Standard war, was Json.Net treu tut, auch für null (Rückgabe von "null"). Wenn Sie Nullwerte abfangen, senden Sie am Ende die leere Zeichenfolge für diese zurück, was vom Standard abweicht und Downstream-Probleme verursacht - zum Beispiel mit jQuery 1.9.1: stackoverflow.com/a/15939945/176877
Chris Moschini
1
@ Chris Moschini: Du hast absolut recht. Es ist falsch, eine leere Zeichenfolge zurückzugeben. Aber sollte es dann den json-Wert null oder ein leeres json-Objekt zurückgeben? Ich bin mir nicht sicher, ob die Rückgabe eines Werts, bei dem ein Objekt erwartet wird, problemlos ist. In beiden Fällen ist der aktuelle Code in dieser Hinsicht nicht gut.
Asgerhallas
1
Es gibt einen Fehler in Json.Net, der dazu führt, dass IE9 und darunter die von Json.Net erzeugten ISO 8601-Daten nicht analysieren können. Das Problem wurde
Chris Moschini
1
@asgerhallas, @Chris Moschini Was ist mit der Standardprüfung von asp.net mvc JsonResult if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);? Ich denke, diese Check-in-Antwort muss hinzugefügt werden (ohne interne, MvcResources.JsonRequest_GetNotAllowedaber mit einer benutzerdefinierten Nachricht). Und was ist mit 2 anderen Standard-MVC-Prüfungen von asp.net - MaxJsonLength und RecursionLimit? Brauchen wir sie, wenn wir json.net verwenden?
Chromigo
60

Ich habe dies ohne die Notwendigkeit eines Basisreglers oder einer Einspritzung implementiert.

Ich habe Aktionsfilter verwendet, um das JsonResult durch ein JsonNetResult zu ersetzen.

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 () müssten Sie Folgendes hinzufügen:

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

Der Vollständigkeit halber ist hier meine JsonNetResult-Erweiterungsklasse, die ich von einem anderen Ort übernommen und leicht modifiziert habe, um die richtige Unterstützung für das Dämpfen zu erhalten:

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
quelle
1
Dies ist eine schöne Lösung. return Json()Lässt es so sein, dass der native Benutzer tatsächlich Json.Net verwendet.
OneHoopyFrood
1
Für alle, die sich fragen, wie das funktioniert, fängt es das JsonResultvon ab Json()und konvertiert es in ein JsonNetResult. Dies geschieht mit dem asSchlüsselwort, das null zurückgibt, wenn die Konvertierung nicht möglich ist. Sehr geschickt. 10 Punkte für Gryffindor!
OneHoopyFrood
4
Frage: Wird der Standard-Serializer für das Objekt ausgeführt, bevor es abgefangen wird?
OneHoopyFrood
Dies ist eine fantastische Antwort - mit der größten Flexibilität. Da mein Projekt bereits alle Arten von manuellen Lösungen im Frontend ausführte, konnte ich keinen globalen Filter hinzufügen - dies würde eine größere Änderung erfordern. Am Ende habe ich das Problem nur bei den Controller-Aktionen gelöst, wenn dies erforderlich war, indem ich das Attribut für die Aktionen meines Controllers verwendet habe. Ich habe es jedoch genannt - [BetterJsonHandler]:-).
Simcha Khabinsky
Rückgabe this.Json (null); gibt immer noch nichts zurück
Brunis
27

Verwenden Sie den JSON-Konverter von Newtonsoft:

public ActionResult DoSomething()
{
    dynamic cResponse = new ExpandoObject();
    cResponse.Property1 = "value1";
    cResponse.Property2 = "value2";
    return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}
Sami Beyoglu
quelle
7
Ich bin mir nicht sicher, ob dies hackig ist oder nicht, aber heiliger Mist ist es einfacher als das Erstellen von Erweiterungsklassen, nur um einen dummen JSON-String zurückzugeben.
Dennis.Sheppard
21

Ich weiß, dass dies gut ist, nachdem die Frage beantwortet wurde, aber ich verwende einen anderen Ansatz, da ich die Abhängigkeitsinjektion verwende, um meine Controller zu instanziieren.

Ich habe den IActionInvoker (durch Einfügen der ControllerActionInvoker-Eigenschaft des Controllers) durch eine Version ersetzt, die die InvokeActionMethod-Methode überschreibt.

Dies bedeutet keine Änderung der Controller-Vererbung und kann beim Upgrade auf MVC4 leicht entfernt werden, indem die Registrierung des DI-Containers für ALLE Controller geändert wird

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

--- BEARBEITEN - Aktualisiert, um die Containerregistrierung für Controller anzuzeigen. Ich benutze hier Unity.

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
quelle
Schön, aber wie benutzt man es? Oder besser, wie hast du es injiziert?
Adaptabi
+1 für die Verwendung der Stream-Form von .Serialize (). Ich wollte darauf hinweisen, dass Sie JsonConvert wie die andere Top-Antwort verwenden können, aber Ihr Ansatz überträgt nach und nach lange / große Objekte - dies ist eine kostenlose Leistungssteigerung, insbesondere wenn der Downstream-Client Teilantworten verarbeiten kann.
Chris Moschini
1
schöne umsetzung. Dies sollte die Antwort sein!
Kat Lim Ruiz
Gut gemacht, das war das einzige, wofür ich einen Basis-Controller verwendet habe.
Chris Diver
wirklich schön - das ist viel besser als das Überschreiben der Json () - Funktion, da dies an jedem Ort, an dem Sie ein JsonResult zurückgeben, aktiviert wird. Für diejenigen, die DI nicht verwenden, fügen Sie einfach geschützte Überschreibung IActionInvoker CreateActionInvoker () {neuen JsonNetActionInvoker () zurückgeben} zu Ihrem Basis-Controller
Avi Pinto
13

Wenn Sie die Antwort von https://stackoverflow.com/users/183056/sami-beyoglu erweitern und den Inhaltstyp festlegen, kann jQuery die zurückgegebenen Daten in ein Objekt für Sie konvertieren.

public ActionResult DoSomething()
{
    dynamic cResponse = new ExpandoObject();
    cResponse.Property1 = "value1";
    cResponse.Property2 = "value2";
    return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}
StokeStoke
quelle
Danke, ich habe einen Hybrid-Mix und dies ist das einzige, was für mich funktionieren würde.
done_merson
Ich habe dies mit JSON.NET wie folgt verwendet: JObject jo = GetJSON(); return Content(jo.ToString(), "application/json");
John Mott
6

Mein Beitrag kann jemandem helfen.

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);
    }
}    
Nebel
quelle
Ich habe nach einer echten Lösung gesucht und Sie waren die einzig richtige Antwort
Richard Aguirre
Vielen Dank. Nachdem ich meine eigene bereits implementiert hatte BaseController, war dies die Änderung mit den geringsten Auswirkungen - ich musste nur die Klasse hinzufügen und aktualisieren BaseController.
AndrewP
4

Ich habe eine Version erstellt, die Webdienstaktionen typsicher und einfach macht. Sie verwenden es so:

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

Die Klasse:

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
quelle
aber warum sollten Sie einen starken Typ JsonResult haben wollen? : D Sie verlieren die Ergebnisse der anonymen Typen und verdienen auf der Clientseite nichts, da sie sowieso keine C # -Klassen verwenden?
Mikus
1
@mikus Auf der Serverseite ist es typsicher: Die Methode muss den Typ MyDataContract zurückgeben. Es macht der Client-Seite genau klar, welche Datenstruktur zurückgegeben wird. Es ist auch übersichtlich und lesbar - JsonResult <T> konvertiert automatisch jeden Typ, der an Json zurückgegeben wird, und Sie müssen nichts tun.
Curtis Yallop