【问题标题】:Using JSON.NET as the default JSON serializer in ASP.NET MVC 3 - is it possible?使用 JSON.NET 作为 ASP.NET MVC 3 中的默认 JSON 序列化程序 - 可能吗?
【发布时间】:2011-08-18 15:25:25
【问题描述】:

是否可以在 ASP.NET MVC 3 中使用 JSON.NET 作为默认 JSON 序列化程序?

根据我的研究,似乎实现此目的的唯一方法是将extend ActionResult 设为JsonResult in MVC3 is not virtual...

我希望通过 ASP.NET MVC 3 可以指定可插入的提供程序以序列化为 JSON。

想法?

【问题讨论】:

标签: asp.net-mvc-3 json.net jsonresult


【解决方案1】:

我相信最好的方法是 - 如您的链接中所述 - 扩展 ActionResult 或直接扩展 JsonResult 。

至于方法JsonResult在控制器上不是虚的那不是真的,选择合适的重载就好了。这很好用:

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

编辑 1:一个 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);
    }

编辑 2:根据以下建议,我删除了对 Data 为 null 的检查。这应该会让 JQuery 的较新版本感到高兴,并且看起来是明智的做法,因为响应可以被无条件反序列化。但请注意,这不是来自 ASP.NET MVC 的 JSON 响应的默认行为,而是在没有数据时以空字符串响应。

【讨论】:

  • 代码引用了MySpecialContractResolver,没有定义。这个问题对此有所帮助(并且与我必须解决的问题非常相关):stackoverflow.com/questions/6700053/…
  • 感谢您的出色回答。为什么 if (Data == null) 返回; ?对于我的用例,我想取回 JSON 标准是什么,Json.Net 忠实地做到了,即使是 null(返回“null”)。通过拦截空值,您最终会为这些返回空字符串,这会偏离标准并导致下游问题 - 例如使用 jQuery 1.9.1:stackoverflow.com/a/15939945/176877
  • @Chris Moschini:你完全正确。返回空字符串是错误的。但是它应该返回 json 值 null 还是一个空的 json 对象呢?我也不确定返回预期对象的值是否没有问题。但不管怎样,目前的代码在这方面做得不好。
  • Json.Net 中存在一个错误,导致 IE9 及以下版本无法解析 Json.Net 生成的 ISO 8601 日期。此答案中包含对此的修复:stackoverflow.com/a/15939945/176877
  • @asgerhallas, @Chris Moschini 默认 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); 怎么样?我认为需要在答案中添加此检查(没有内部 MvcResources.JsonRequest_GetNotAllowed 但带有一些自定义消息)另外,另外 2 个默认的 asp.net mvc 检查 - MaxJsonLength 和 RecursionLimit 呢?如果我们使用 json.net,我们需要它们吗?
【解决方案2】:

我在不需要基本控制器或注入的情况下实现了这一点。

我使用操作过滤器将 JsonResult 替换为 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);
    }
}

您需要在 Global.asax.cs Application_Start() 中添加:

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

为了完整起见,这是我从其他地方获取的 JsonNetResult 扩展类,我稍作修改以获得正确的蒸汽支持:

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

【讨论】:

  • 这是一个不错的解决方案。使得原生 return Json() 实际上使用 Json.Net。
  • 对于任何想知道它是如何工作的人,它会从Json() 中截取JsonResult 并将其转换为JsonNetResult。它使用as 关键字执行此操作,如果无法进行转换,则返回 null。非常漂亮。格兰芬多得 10 分!
  • 问题是,默认的序列化程序在被拦截之前是否在对象上运行?
  • 这是一个绝妙的答案 - 具有最大的灵活性。由于我的项目已经在前端做各种手动解决方案,我无法添加全局过滤器——这需要更大的改变。我最终只在必要时通过使用控制器操作的属性解决了控制器操作的问题。但是,我称它为 - [BetterJsonHandler] :-)。
  • 返回 this.Json(null);仍然没有返回任何内容
【解决方案3】:

使用 Newtonsoft 的 JSON 转换器:

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

【讨论】:

  • 不确定这是否是 hacky,但天哪,它比创建扩展类更容易,只是返回一个愚蠢的 json 字符串。
【解决方案4】:

我知道这个问题已经得到解答,但我正在使用不同的方法,因为我正在使用依赖注入来实例化我的控制器。

我已将 IActionInvoker(通过注入控制器的 ControllerActionInvoker 属性)替换为覆盖 InvokeActionMethod 方法的版本。

这意味着控制器继承没有变化,当我升级到 MVC4 时,可以通过更改所有控制器的 DI 容器注册来轻松删除它

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

--- 编辑 - 更新以显示控制器的容器注册。我在这里使用 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);
    }
}

【讨论】:

  • 很好,但你如何使用它?或者更好的是你是怎么注射的?
  • +1 用于使用 .Serialize() 的流形式。我要指出的是,您可以像其他最佳答案一样使用 JsonConvert,但您的方法会逐渐流出长/大对象 - 这是一个免费的性能提升,特别是如果下游客户端可以处理部分响应。
  • 不错的实现。这应该是答案!
  • 干得好,这是我使用基本控制器的唯一目的。
  • 真的很好 - 这比重写 Json() 函数要好得多,因为在您将返回 JsonResult 的每个位置,这都会发挥作用并发挥作用。对于那些不使用 DI 的人,只需将 protected override IActionInvoker CreateActionInvoker() { return new JsonNetActionInvoker();} 添加到您的基本控制器
【解决方案5】:

扩展https://stackoverflow.com/users/183056/sami-beyoglu的答案,如果你设置了内容类型,那么jQuery将能够为你将返回的数据转换成一个对象。

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

【讨论】:

  • 谢谢,我有一个混合组合,这是唯一对我有用的东西。
  • 我将它与 JSON.NET 一起使用,如下所示:JObject jo = GetJSON(); return Content(jo.ToString(), "application/json");
【解决方案6】:

我的帖子可能对某人有所帮助。

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

【讨论】:

  • 谢谢。已经实现了我自己的 BaseController,这是影响最小的 chang - 只需添加类并更新 BaseController
【解决方案7】:

我制作了一个使 Web 服务操作类型安全且简单的版本。你可以这样使用它:

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

班级:

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

【讨论】:

  • 但是你为什么想要一个强类型的 JsonResult 呢? :D 你失去了匿名类型的结果并且在客户端一无所获,因为它没有使用 C# 类?
  • @mikus 在服务器端是类型安全的:该方法必须返回类型 MyDataContract。它让客户端清楚地知道返回的是什么数据结构。它也简洁易读 - JsonResult 自动转换返回给 Json 的任何类型,您无需执行任何操作。
  • 没错。我发现我返回的所有内容都是 JsonResultstring(对于 Newtonsoft)很愚蠢,即使对于返回通用集合的操作也是如此。这对于任何单元测试都是一个额外的步骤,因为您必须再次反序列化所有内容。似乎在 Core 中至少您现在可以返回一个 IEnumerable 并且它会序列化,但我认为它不适用于 .NET Framework。 (我很想错了,但我无法让它发挥作用。)
  • 为了补充答案,我最终添加了一个扩展方法:public static JsonResult&lt;T&gt; Preserialize&lt;T&gt;(this T data) where T: class =&gt; new JsonResult&lt;T&gt;(data). 这样您就不必每次都写出长类型。所以示例用法是Enumerable.Range(0, 100).Preserialize() 我尝试定义一个隐式转换,但它们不适用于接口类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-13
  • 1970-01-01
  • 2012-11-19
  • 1970-01-01
  • 2020-04-01
相关资源
最近更新 更多