Використання JSON.NET як серіалізатора JSON за замовчуванням у ASP.NET MVC 3 - чи можливо це?


101

Чи можна використовувати JSON.NET в якості серіалізатора JSON за замовчуванням в ASP.NET MVC 3?

Згідно з моїм дослідженням, здається, що єдиний спосіб досягти цього - це розширити ActionResult, оскільки JsonResult у MVC3 - це не віртуальний ...

Я сподівався, що з ASP.NET MVC 3 з'явиться спосіб вказати підключається постачальника для серіалізації до JSON.

Думки?


Відповіді:


106

Я вважаю, що найкращий спосіб зробити це - як описано у ваших посиланнях - розширити ActionResult або продовжити JsonResult безпосередньо.

Що стосується методу JsonResult, який не є віртуальним на контролері, це неправда, просто виберіть правильну перевантаження. Це добре працює:

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

EDIT 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 : Я видалив чек на предмет "Дані" недійсним відповідно до наведених нижче пропозицій. Це повинно зробити нові версії JQuery щасливими і, здається, розумною справою, оскільки відповідь потім можна беззастережно дезаріалізувати. Однак майте на увазі, що це не поведінка за замовчуванням для відповідей JSON від ASP.NET MVC, яка, скоріше, відповідає порожньою рядком, коли немає даних.


1
Код посилається на MySpecialContractResolver, який не визначений. Це питання допомагає з цим (і дуже пов'язана з проблемою мені довелося вирішувати): stackoverflow.com/questions/6700053 / ...
Elliveny

1
Дякую за чудову відповідь. Чому повернення if (Data == null); ? Для мого використання я хотів повернути те, що було стандартом JSON, що Json.Net сумлінно робить, навіть для null (повернення "null"). Перехоплюючи нульові значення, ви в кінцевому підсумку відправляєте порожню рядок для них, яка відхиляється від стандартної та спричиняє проблеми вниз за течією - наприклад, з jQuery 1.9.1: stackoverflow.com/a/15939945/176877
Chris Moschini

1
@Chris Moschini: Ви абсолютно праві. Повернути порожню рядок неправильно. Але чи повинен він повертати значення json null або порожній об'єкт json? Я не впевнений, що повернення значення там, де очікується об'єкт, також без проблем. Але в будь-якому випадку, поточний код не є гарним у цьому відношенні.
asgerhallas

1
У Json.Net є помилка, яка призводить до того, що IE9 і нижче не можуть проаналізувати дати ISO 8601, які виробляє Json.Net. Виправлення цього входить у цю відповідь: stackoverflow.com/a/15939945/176877
Кріс Москіні

1
@asgerhallas, @Chris Moschini Що робити за замовчуванням asp.net mvc Перевірка JsonResult if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);? Думаю, потрібно додати цю відповідь у відповідь (без внутрішнього, MvcResources.JsonRequest_GetNotAllowedале з певним користувальницьким повідомленням) Крім того, як щодо двох інших перевірок mvc asp.net за замовчуванням - MaxJsonLength та RecursionLimit? Чи потрібні вони нам, якщо ми використовуємо json.net?
хроміго

60

Я реалізував це без необхідності базового контролера чи вприскування.

Я використав фільтри дій для заміни 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);
    }
}

1
Це приємне рішення. Робить так, що власне return Json()фактично використовує Json.Net.
OneHoopyFrood

1
Для тих , хто цікаво тільки , як це працює, він перехоплює JsonResultвід Json()і перетворює його в JsonNetResult. Це робиться за допомогою asключового слова, яке повертає null, якщо перетворення неможливе. Дуже витончений. 10 балів за Гриффіндора!
OneHoopyFrood

4
Питання, однак, чи запускається серіалізатор за замовчуванням на об’єкт до його перехоплення?
OneHoopyFrood

Це фантастична відповідь - з найбільшою гнучкістю. Оскільки мій проект вже робив усілякі ручні рішення на передній частині, я не зміг додати глобальний фільтр - це вимагало б більшої зміни. Я врешті-решт вирішив проблему лише на дії контролера, де це необхідно, використовуючи атрибут дій мого контролера. Однак я назвав це - [BetterJsonHandler]:-).
Сімча Хабінський

повернення цього.Json (null); досі нічого не повертає
Бруніс

27

Використовуйте конвертер JSON Newtonsoft:

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

7
Не впевнений, хакі це чи ні, але святе лайно легше, ніж створювати розширення класів, просто повернути дурну рядок json.
dennis.sheppard

21

Я знаю, що це добре після відповіді на запитання, але я використовую інший підхід, оскільки використовую ін'єкцію залежності, щоб інстанціювати свої контролери.

Я замінив 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 );
            }
        }
    }
}

--- EDIT - оновлено для показу реєстрації контейнерів для контролерів. Я тут використовую 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 за використання форми Stream .Serialize (). Я хотів би зазначити, що ви можете просто використовувати JsonConvert, як і інший верхній варіант відповіді, але ваш підхід поступово розповсюджує довгі / великі об'єкти - це безкоштовне підвищення продуктивності, особливо якщо клієнт нижче за течією може обробляти часткові відповіді.
Кріс Москіні

1
приємна реалізація. Це повинна бути відповідь!
Kat Lim Ruiz

Добре, це було єдине, для чого я використовував базовий контролер.
Кріс Дівер

дійсно приємно - це набагато краще, ніж перекриття функції Json (), оскільки в усіх місцях, де ви повернете JsonResult, це запуститься і зробить це магією. Для тих, хто не використовує DI, просто додайте захищене переопределення IActionInvoker CreateActionInvoker () {поверніть новий JsonNetActionInvoker ();} до базового контролера
Avi Pinto

13

Розширюючи відповідь з 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");
}

Дякую, у мене є гібридна суміш, і це єдине, що могло б працювати для мене.
done_merson

Я використовував це з 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, це було найменшим зміною впливу - просто довелося додати клас та оновити BaseController.
AndrewP

4

Я створив версію, яка робить дії веб-сервісу безпечними і простими. Ви використовуєте його так:

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

1
@mikus Це безпечно на сервері: метод повинен повернути тип MyDataContract. Це дає зрозуміти клієнтові, яка саме структура даних повертається. Він також стислий і читабельний - JsonResult <T> автоматично перетворює будь-який тип, який повертається в Json, і вам нічого не потрібно робити.
Кертіс Яллоп
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.