JSON.NETをASP.NET MVC 3のデフォルトのJSONシリアライザーとして使用することは可能ですか?


101

ASP.NET MVC 3でデフォルトのJSON シリアライザーとしてJSON.NETを使用することは可能ですか?

私の研究によれば、これを実現する唯一の方法はしているようだのActionResultを延ばすようMVC3にするJsonResult仮想ではありません ...

ASP.NET MVC 3では、JSONにシリアル化するためのプラグ可能なプロバイダーを指定する方法があることを期待していました。

考え?


回答:


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:以下の提案に従って、データがnullかどうかのチェックを削除しました。これにより、JQueryの新しいバージョンが幸せになり、無条件に逆シリアル化できるようになるため、正気なことのように思えます。ただし、これはASP.NET MVCからのJSON応答のデフォルトの動作ではなく、データがない場合は空の文字列で応答することに注意してください。


1
このコードは、定義されていないMySpecialContractResolverを参照しています。この質問はそれで役立ちます(そして非常に私が解決しなければならなかった問題に関連していた):stackoverflow.com/questions/6700053/...
Elliveny

1
素晴らしい答えをありがとう。if(Data == null)が返される理由; ?私のユースケースでは、JSON標準が何であれ、Json.Netが忠実に実行する、null( "null"を返す)でも取得したいと考えました。null値をインターセプトすると、これらの空の文字列が返され、標準から逸脱してダウンストリームの問題が発生します。たとえば、jQuery 1.9.1の場合:stackoverflow.com/a/15939945/176877
Chris Moschini

1
@クリス・モスキーニ:あなたの言う通りです。空の文字列を返すのは間違っています。しかし、それはjson値nullまたは空のjsonオブジェクトを返す必要がありますか?オブジェクトが期待される場所に値を返すことが問題がないかどうかもわかりません。しかしどちらにしても、現在のコードはこの点で良くありません。
asgerhallas

1
Json.Netにはバグがあり、IE9以下ではJson.Netが生成するISO 8601日付の解析に失敗します。これに対する修正はこの回答に含まれています:stackoverflow.com/a/15939945/176877
Chris Moschini

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ませんが、いくつかのカスタムメッセージがあります)また、他の2つのデフォルトのasp.net mvcチェック-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 2015年

1
これがどのように機能するのか疑問に思っている人は、JsonResultfrom Json()をインターセプトしてに変換しJsonNetResultます。これはas、変換が不可能な場合にnullを返すキーワードを使用して行われます。とても気の利いた。グリフィンドールが10ポイント!
OneHoopyFrood 2015年

4
しかし質問ですが、デフォルトのシリアライザーはインターセプトされる前にオブジェクトで実行されますか?
OneHoopyFrood 2015年

これは素晴らしい答えです-最も柔軟性があります。私のプロジェクトはすでにフロントエンドですべての種類の手動ソリューションを実行していたため、グローバルフィルターを追加できませんでした。これには大きな変更が必要になります。コントローラーのアクションの属性を使用して、必要なコントローラーアクションのみで問題を解決しただけです。しかし、私はそれを呼びました- [BetterJsonHandler]:-)。
Simcha Khabinsky 2017

this.Json(null);を返します。それでも何も返さない
Brunis

27

NewtonsoftのJSONコンバーターを使用します。

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

7
これがハックであるかどうかはわかりませんが、愚かなjson文字列を返すだけで、拡張機能クラスを作成するよりも簡単です。
dennis.sheppard 2014

21

これは質問への回答が得られた後もよくわかりますが、依存関係注入を使用してコントローラーをインスタンス化しているため、別のアプローチを使用しています。

IActionInvokerを(コントローラーのControllerActionInvokerプロパティを挿入することにより)InvokeActionMethodメソッドをオーバーライドするバージョンに置き換えました。

これは、コントローラーの継承に変更がないことを意味し、すべてのコントローラーのDIコンテナーの登録を変更することで、MVC4にアップグレードするときに簡単に削除できます

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 2012年

+1は、.Serialize()のストリーム形式を使用します。私はあなたがJsonConvertを他のトップアンサーのように使用できることを指摘しましたが、あなたのアプローチは長い/大きなオブジェクトを徐々にストリームアウトします-特にダウンストリームクライアントが部分的な応答を処理できる場合、それは無料のパフォーマンス向上です。
Chris Moschini 2013

1
素晴らしい実装。これが答えになるはずです!
Kat Lim Ruiz 2013

順調です、これは私がベースコントローラーを使用していた唯一のものでした。
Chris Diver

本当に素晴らしい-これは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 2015

:私はこのような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);
    }
}    

私は実際の解決策を見つけていましたが、あなたが唯一の正解でした
Richard Aguirre

ありがとう。すでに自分BaseControllerで実装しているので、これは変更の影響が最も少なかったので、クラスを追加して更新するだけで済みましたBaseController
AndrewP

4

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#クラスを使用していないため、匿名型の結果が失われ、クライアント側で何も得られませんか?
2015年

1
@mikusサーバー側ではタイプセーフです。メソッドはタイプMyDataContractを返す必要があります。これにより、クライアント側に正確にどのデータ構造が返されているかがわかります。また、簡潔で読みやすい-JsonResult <T>は、Jsonに返されるすべての型を自動変換するため、何もする必要はありません。
Curtis Yallop、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.