Menggunakan JSON.NET sebagai serializer JSON default di ASP.NET MVC 3 - apakah mungkin?

101

Apakah mungkin menggunakan JSON.NET sebagai serializer JSON default di ASP.NET MVC 3?

Menurut penelitian saya, tampaknya satu-satunya cara untuk mencapai ini adalah dengan memperluas ActionResult karena JsonResult di MVC3 bukanlah virtual ...

Saya berharap bahwa dengan ASP.NET MVC 3 akan ada cara untuk menentukan penyedia pluggable untuk serialisasi ke JSON.

Pikiran?

zam6ak
sumber
terkait: stackoverflow.com/questions/6883204/…
Ruben Bartelink

Jawaban:

106

Saya yakin cara terbaik untuk melakukannya adalah - seperti yang dijelaskan di tautan Anda - dengan memperluas ActionResult atau memperluas JsonResult secara langsung.

Sedangkan untuk metode JsonResult yang tidak virtual pada kontroler itu tidak benar, pilih saja yang kelebihan beban. Ini bekerja dengan baik:

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

EDIT 1 : Ekstensi 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 : Saya menghapus centang untuk Data menjadi nol sesuai saran di bawah ini. Itu seharusnya membuat versi JQuery yang lebih baru senang dan tampak seperti hal yang waras untuk dilakukan, karena responsnya kemudian dapat dideserialisasi tanpa syarat. Sadarilah, bahwa ini bukan perilaku default untuk tanggapan JSON dari ASP.NET MVC, yang merespons dengan string kosong, saat tidak ada data.

asgerhallas
sumber
1
Kode mengacu pada MySpecialContractResolver, yang tidak ditentukan. Pertanyaan ini membantu dengan itu (dan sangat terkait dengan masalah yang harus saya selesaikan): stackoverflow.com/questions/6700053/…
Elliveny
1
Terima kasih atas jawaban yang bagus. Mengapa if (Data == null) return; ? Untuk kasus penggunaan saya, saya ingin mendapatkan kembali apa pun standar JSON, yang dilakukan dengan setia oleh Json.Net, bahkan untuk null (mengembalikan "null"). Dengan mencegat nilai null Anda akhirnya mengirim kembali string kosong untuk ini, yang menyimpang dari standar dan menyebabkan masalah hilir - misalnya dengan jQuery 1.9.1: stackoverflow.com/a/15939945/176877
Chris Moschini
1
@ Chris Moschini: Anda benar sekali. Salah mengembalikan string kosong. Tetapi haruskah itu mengembalikan nilai json null atau objek json kosong? Saya tidak yakin mengembalikan nilai di mana sebuah objek diharapkan juga bebas masalah. Tapi bagaimanapun, kode saat ini tidak bagus dalam hal ini.
asgerhallas
1
Ada bug di Json.Net yang menyebabkan IE9 dan di bawahnya gagal mengurai ISO 8601 Dates yang dihasilkan Json.Net. Perbaikan untuk ini termasuk dalam jawaban ini: stackoverflow.com/a/15939945/176877
Chris Moschini
1
@asgerhallas, @Chris Moschini Bagaimana dengan cek JsonResult mvc asp.net default if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);? Saya pikir perlu menambahkan jawaban cek ini (tanpa internal MvcResources.JsonRequest_GetNotAllowedtetapi dengan beberapa pesan khusus) Juga, bagaimana dengan 2 pemeriksaan mvc asp.net default lainnya - MaxJsonLength dan RecursionLimit? Apakah kami membutuhkannya jika menggunakan json.net?
chromigo
60

Saya menerapkan ini tanpa perlu pengontrol atau injeksi dasar.

Saya menggunakan filter tindakan untuk mengganti JsonResult dengan 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);
    }
}

Di Global.asax.cs Application_Start () Anda perlu menambahkan:

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

Demi penyelesaian, berikut adalah kelas ekstensi JsonNetResult saya yang saya ambil dari tempat lain dan yang saya modifikasi sedikit untuk mendapatkan dukungan penguapan yang benar:

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
sumber
1
Ini adalah solusi yang bagus. Menjadikannya native yang return Json()berlaku menggunakan Json.Net.
OneHoopyFrood
1
Bagi siapa pun yang bertanya-tanya bagaimana ini bekerja, itu memotong JsonResultdari Json()dan mengubahnya menjadi JsonNetResult. Itu dilakukan dengan menggunakan askata kunci yang mengembalikan null jika konversi tidak memungkinkan. Sangat bagus. 10 poin untuk Gryffindor!
OneHoopyFrood
4
Pertanyaannya, apakah serializer default berjalan pada objek sebelum dicegat?
OneHoopyFrood
Ini adalah jawaban yang fantastis - dengan fleksibilitas paling tinggi. Karena proyek saya sudah melakukan semua jenis solusi manual di bagian depan, saya tidak dapat menambahkan filter global - ini akan membutuhkan perubahan yang lebih besar. Saya akhirnya hanya menyelesaikan masalah hanya pada tindakan pengontrol jika perlu dengan menggunakan atribut pada tindakan pengontrol saya. Namun, saya menyebutnya - [BetterJsonHandler]:-).
Simcha Khabinsky
mengembalikan this.Json (null); masih tidak menghasilkan apa
Brunis
27

Gunakan konverter JSON Newtonsoft:

public ActionResult DoSomething()
{
    dynamic cResponse = new ExpandoObject();
    cResponse.Property1 = "value1";
    cResponse.Property2 = "value2";
    return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}
Sami Beyoglu
sumber
7
Tidak yakin apakah ini hacky atau tidak, tetapi omong kosong itu lebih mudah daripada membuat kelas ekstensi, hanya untuk mengembalikan string json yang bodoh.
dennis.sheppard
21

Saya tahu ini baik-baik saja setelah pertanyaan dijawab, tetapi saya menggunakan pendekatan yang berbeda karena saya menggunakan injeksi ketergantungan untuk membuat contoh pengontrol saya.

Saya telah mengganti IActionInvoker (dengan memasukkan Properti ControllerActionInvoker pengontrol) dengan versi yang menggantikan metode InvokeActionMethod.

Ini berarti tidak ada perubahan pada pewarisan pengontrol dan itu dapat dengan mudah dihapus ketika saya meningkatkan ke MVC4 dengan mengubah pendaftaran penampung DI untuk SEMUA pengontrol

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 - Diperbarui untuk menampilkan pendaftaran kontainer untuk pengontrol. Saya menggunakan Unity di sini.

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
sumber
Bagus, tapi bagaimana Anda menggunakannya? Atau lebih baik bagaimana Anda menyuntikkannya?
Adaptabi
+1 untuk menggunakan bentuk Aliran .Serialize (). Saya akan menunjukkan bahwa Anda dapat menggunakan JsonConvert seperti jawaban teratas lainnya, tetapi pendekatan Anda secara bertahap mengalirkan objek panjang / besar - itu adalah peningkatan kinerja gratis, terutama jika klien hilir dapat menangani respons parsial.
Chris Moschini
1
implementasi yang bagus. Ini harus menjadi jawabannya!
Kat Lim Ruiz
Kerja bagus, ini adalah satu-satunya hal yang saya gunakan untuk pengontrol dasar.
Chris Diver
sangat bagus - ini jauh lebih baik daripada mengesampingkan fungsi Json (), karena di setiap lokasi di mana Anda akan mengembalikan JsonResult, ini akan bekerja dan melakukan keajaiban. Bagi mereka yang tidak menggunakan DI, cukup tambahkan pengesampingan yang dilindungi IActionInvoker CreateActionInvoker () {return new JsonNetActionInvoker ();} ke pengontrol dasar Anda
Avi Pinto
13

Memperluas jawaban dari https://stackoverflow.com/users/183056/sami-beyoglu , jika Anda mengatur tipe Konten, maka jQuery akan dapat mengubah data yang dikembalikan menjadi objek untuk Anda.

public ActionResult DoSomething()
{
    dynamic cResponse = new ExpandoObject();
    cResponse.Property1 = "value1";
    cResponse.Property2 = "value2";
    return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}
StokeStoke
sumber
Terima kasih, saya memiliki campuran hybrid dan ini adalah satu-satunya yang akan berhasil untuk saya.
done_merson
Saya menggunakan ini dengan JSON.NET seperti ini: JObject jo = GetJSON(); return Content(jo.ToString(), "application/json");
John Mott
6

Posting Saya dapat membantu seseorang.

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);
    }
}    
Kabut
sumber
Saya menemukan solusi nyata dan Anda adalah satu-satunya jawaban yang benar
Richard Aguirre
Terima kasih. Setelah menerapkan milik saya sendiri BaseController, ini adalah perubahan dampak terendah - hanya perlu menambahkan kelas dan memperbarui BaseController.
AndrewP
4

Saya membuat versi yang membuat tindakan layanan web aman dan sederhana. Anda menggunakannya seperti ini:

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

Kelas:

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
sumber
tetapi mengapa Anda ingin memiliki JsonResult tipe yang kuat? : D Anda kehilangan hasil jenis anonim dan tidak mendapatkan apa-apa di sisi klien, karena tetap tidak menggunakan C # classses?
mikus
1
@mikus Ini adalah jenis aman di sisi server: metode harus mengembalikan jenis MyDataContract. Ini menjelaskan kepada sisi klien dengan tepat struktur data apa yang dikembalikan. Ini juga ringkas dan dapat dibaca - JsonResult <T> mengonversi otomatis semua jenis yang dikembalikan ke Json dan Anda tidak perlu melakukan apa pun.
Curtis Yallop