Возможно ли использование JSON.NET в качестве сериализатора JSON по умолчанию в ASP.NET MVC 3?

101

Можно ли использовать JSON.NET в качестве сериализатора JSON по умолчанию в ASP.NET MVC 3?

Согласно моим исследованиям, кажется, что единственный способ добиться этого - расширить ActionResult, поскольку JsonResult в MVC3 не является виртуальным ...

Я надеялся, что в ASP.NET MVC 3 появится способ указать подключаемый поставщик для сериализации в JSON.

Мысли?

zam6ak
источник
связанные: stackoverflow.com/questions/6883204/…
Рубен Бартелинк

Ответы:

106

Я считаю, что лучший способ сделать это - как описано в ваших ссылках - напрямую расширить 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 : Я удалил проверку на отсутствие данных в соответствии с предложениями ниже. Это должно сделать более новые версии JQuery счастливыми и кажется разумным решением, поскольку ответ может быть безоговорочно десериализован. Однако имейте в виду, что это не поведение по умолчанию для ответов JSON от ASP.NET MVC, которое скорее отвечает пустой строкой, когда данных нет.

Asgerhallas
источник
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
Крис Москини
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но с некоторым настраиваемым сообщением) Кроме того, как насчет двух других проверок asp.net mvc по умолчанию - MaxJsonLength и RecursionLimit? Нужны ли они нам, если мы используем json.net?
chromigo
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);
    }
}
MDB
источник
1
Это хорошее решение. Делает это так , родной return Json()в использовании эффекта Json.Net.
OneHoopyFrood
1
Для тех, кому интересно, как это работает, он перехватывает JsonResultиз Json()и конвертирует его в JsonNetResult. Это делается с помощью asключевого слова, которое возвращает null, если преобразование невозможно. Очень здорово. 10 очков Гриффиндору!
OneHoopyFrood
4
Вопрос, однако, запускается ли сериализатор по умолчанию на объекте до его перехвата?
OneHoopyFrood
Это фантастический ответ - с максимальной гибкостью. Поскольку в моем проекте уже выполнялись всевозможные ручные решения для внешнего интерфейса, я не мог добавить глобальный фильтр - это потребовало бы больших изменений. В итоге я просто решил проблему только с действиями контроллера, где это необходимо, используя атрибут действий моего контроллера. Однако я назвал это - [BetterJsonHandler]:-).
Симха Хабинский
возврат this.Json (null); до сих пор ничего не возвращает
Brunis
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 );
            }
        }
    }
}

--- РЕДАКТИРОВАТЬ - Обновлено, чтобы показать регистрацию контейнера для контроллеров. Я использую здесь 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);
    }
}
Роберт Слейни
источник
Хорошо, но как им пользоваться? Или лучше как укололи?
Adaptabi
+1 за использование потоковой формы .Serialize (). Я собирался указать, что вы можете просто использовать JsonConvert, как и другой популярный ответ, но ваш подход постепенно выводит длинные / большие объекты - это бесплатное повышение производительности, особенно если нижестоящий клиент может обрабатывать частичные ответы.
Крис Москини
1
хорошая реализация. Это должен быть ответ!
Кэт Лим Руис
Хорошо, это единственное, для чего я использовал базовый контроллер.
Крис Дайвер,
действительно хорошо - это намного лучше, чем переопределение функции Json (), поскольку в каждом месте, где вы вернете JsonResult, это сработает и сотворит чудеса. Для тех, кто не использует DI, просто добавьте защищенное переопределение IActionInvoker CreateActionInvoker () {return new 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 01
Я использовал это с 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, и вам не нужно ничего делать.
Curtis Yallop