Utiliser JSON.NET comme sérialiseur JSON par défaut dans ASP.NET MVC 3 - est-ce possible?

101

Est-il possible d'utiliser JSON.NET comme sérialiseur JSON par défaut dans ASP.NET MVC 3?

D'après mes recherches, il semble que le seul moyen d'y parvenir soit d' étendre ActionResult car JsonResult dans MVC3 n'est pas virtuel ...

J'espérais qu'avec ASP.NET MVC 3, il y aurait un moyen de spécifier un fournisseur enfichable pour la sérialisation vers JSON.

Pensées?

zam6ak
la source
en relation: stackoverflow.com/questions/6883204/…
Ruben Bartelink

Réponses:

106

Je pense que la meilleure façon de le faire est - comme décrit dans vos liens - d'étendre ActionResult ou d'étendre JsonResult directement.

Quant à la méthode JsonResult qui n'est pas virtuelle sur le contrôleur ce n'est pas vrai, il suffit de choisir la bonne surcharge. Cela fonctionne bien:

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

EDIT 1 : Une extension 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 : J'ai supprimé la vérification des données étant nulles selon les suggestions ci-dessous. Cela devrait rendre les nouvelles versions de JQuery heureuses et semble être la chose saine à faire, car la réponse peut alors être désérialisée de manière inconditionnelle. Sachez cependant que ce n'est pas le comportement par défaut pour les réponses JSON d'ASP.NET MVC, qui répond plutôt avec une chaîne vide, lorsqu'il n'y a pas de données.

Asgerhallas
la source
1
Le code fait référence à MySpecialContractResolver, qui n'est pas défini. Cette question aide avec ça (et était très liée au problème que je devais résoudre): stackoverflow.com/questions/6700053/…
Elliveny
1
Merci pour la bonne réponse. Pourquoi le if (Data == null) return; ? Pour mon cas d'utilisation, je voulais récupérer quel que soit le standard JSON, ce que Json.Net fait fidèlement, même pour null (renvoyer "null"). En interceptant les valeurs nulles, vous finissez par renvoyer la chaîne vide pour celles-ci, ce qui s'écarte de la norme et cause des problèmes en aval - par exemple avec jQuery 1.9.1: stackoverflow.com/a/15939945/176877
Chris Moschini
1
@Chris Moschini: Vous avez absolument raison. Il est erroné de renvoyer une chaîne vide. Mais devrait-il retourner la valeur json null ou un objet json vide alors? Je ne suis pas sûr que renvoyer une valeur où un objet est attendu soit sans problème non plus. Mais de toute façon, le code actuel n'est pas bon à cet égard.
asgerhallas
1
Il existe un bogue dans Json.Net qui empêche IE9 et les versions antérieures d'analyser les dates ISO 8601 produites par Json.Net. Le correctif pour cela est inclus dans cette réponse: stackoverflow.com/a/15939945/176877
Chris Moschini
1
@asgerhallas, @Chris Moschini Qu'en est-il de la vérification par défaut asp.net mvc JsonResult if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);? Je pense qu'il faut ajouter cette réponse d'enregistrement (sans MvcResources.JsonRequest_GetNotAllowedmessage interne mais avec un message personnalisé) En outre, qu'en est-il de 2 autres vérifications par défaut asp.net mvc - MaxJsonLength et RecursionLimit? En avons-nous besoin si nous utilisons json.net?
chromigo
60

J'ai implémenté cela sans avoir besoin d'un contrôleur de base ou d'une injection.

J'ai utilisé des filtres d'action pour remplacer le JsonResult par 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);
    }
}

Dans Global.asax.cs Application_Start (), vous devrez ajouter:

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

Pour terminer, voici ma classe d'extention JsonNetResult que j'ai récupérée ailleurs et que j'ai légèrement modifiée pour obtenir un support correct de la vapeur:

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
la source
1
C'est une bonne solution. Fait en sorte que le natif return Json()utilise en fait Json.Net.
OneHoopyFrood
1
Pour tous ceux qui se demandent comment cela fonctionne, il intercepte le fichier JsonResultfrom Json()et le convertit en fichier JsonNetResult. Il le fait en utilisant le asmot - clé qui renvoie null si la conversion n'est pas possible. Très chouette. 10 points pour Gryffondor!
OneHoopyFrood
4
Question cependant, le sérialiseur par défaut s'exécute-t-il sur l'objet avant qu'il ne soit intercepté?
OneHoopyFrood
C'est une réponse fantastique - avec le plus de flexibilité. Étant donné que mon projet faisait déjà toutes sortes de solutions manuelles sur le front-end, je ne pouvais pas ajouter de filtre global - cela nécessiterait un changement plus important. J'ai fini par résoudre le problème uniquement sur les actions du contrôleur lorsque cela était nécessaire en utilisant l'attribut sur les actions de mon contrôleur. Cependant, je l'ai appelé - [BetterJsonHandler]:-).
Simcha Khabinsky
renvoyer this.Json (null); retourne toujours rien
Brunis
27

Utilisez le convertisseur 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
la source
7
Je ne sais pas si c'est piraté ou non, mais la merde est plus facile que de créer des classes d'extension, juste pour renvoyer une chaîne json stupide.
dennis.sheppard
21

Je sais que c'est bien après la réponse à la question, mais j'utilise une approche différente car j'utilise l'injection de dépendances pour instancier mes contrôleurs.

J'ai remplacé IActionInvoker (en injectant la propriété ControllerActionInvoker du contrôleur) par une version qui remplace la méthode InvokeActionMethod.

Cela ne signifie aucun changement dans l'héritage du contrôleur et il peut être facilement supprimé lorsque je mets à niveau vers MVC4 en modifiant l'enregistrement du conteneur DI pour TOUS les contrôleurs

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 - Mis à jour pour afficher l'enregistrement des conteneurs pour les contrôleurs. J'utilise Unity ici.

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
la source
Bien, mais comment l'utilisez-vous? Ou mieux comment l'avez-vous injecté?
Adaptabi
+1 pour utiliser la forme Stream de .Serialize (). J'allais souligner que vous pouvez simplement utiliser JsonConvert comme l'autre réponse principale, mais votre approche diffuse progressivement les objets longs / volumineux - c'est une amélioration gratuite des performances, surtout si le client en aval peut gérer des réponses partielles.
Chris Moschini
1
belle mise en œuvre. Cela devrait être la réponse!
Kat Lim Ruiz
Bon, c'était la seule chose pour laquelle j'utilisais un contrôleur de base.
Chris Diver
vraiment sympa - c'est bien mieux que de surcharger la fonction Json (), car dans chaque endroit où vous retournerez un JsonResult, cela se déclenchera et fera de la magie. Pour ceux qui n'utilisent pas DI, ajoutez simplement le remplacement protégé IActionInvoker CreateActionInvoker () {return new JsonNetActionInvoker ();} à votre contrôleur de base
Avi Pinto
13

En développant la réponse de https://stackoverflow.com/users/183056/sami-beyoglu , si vous définissez le type de contenu, jQuery pourra convertir les données renvoyées en un objet pour vous.

public ActionResult DoSomething()
{
    dynamic cResponse = new ExpandoObject();
    cResponse.Property1 = "value1";
    cResponse.Property2 = "value2";
    return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}
StokeStoke
la source
Merci, j'ai un mix hybride et c'est la seule chose qui fonctionnerait pour moi.
done_merson
J'ai utilisé ceci avec JSON.NET comme ceci: JObject jo = GetJSON(); return Content(jo.ToString(), "application/json");
John Mott
6

Ma publication peut aider quelqu'un.

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);
    }
}    
Brouillard
la source
Je cherchais une vraie solution et vous étiez la seule bonne réponse
Richard Aguirre
Merci. Ayant déjà implémenté le mien BaseController, c'était le changement le moins impactant - il suffisait d'ajouter la classe et de mettre à jour BaseController.
AndrewP
4

J'ai créé une version qui rend les actions de service Web simples et sécurisées. Vous l'utilisez comme ceci:

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);
    }
}
Curtis Yallop
la source
mais pourquoi voudriez-vous avoir un JsonResult fortement types? : Avez-vous perdu les résultats des types anonymes et ne gagnez rien du côté client, car il n'utilise pas de classes C # de toute façon?
mikus
1
@mikus Il est de type sécurisé côté serveur: la méthode doit renvoyer le type MyDataContract. Il indique clairement au client quelle structure de données est renvoyée. Il est également concis et lisible - JsonResult <T> convertit automatiquement tout type renvoyé à Json et vous n'avez rien à faire.
Curtis Yallop