การใช้ JSON.NET เป็น Serializer JSON เริ่มต้นใน ASP.NET MVC 3 เป็นไปได้หรือไม่?


101

เป็นไปได้ไหมที่จะใช้JSON.NETเป็น JSON serializer เริ่มต้นใน 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)

แก้ไข 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 เวอร์ชันใหม่มีความสุขและดูเหมือนว่าเป็นสิ่งที่ดีที่ต้องทำเนื่องจากการตอบสนองสามารถถูก deserialized โดยไม่มีเงื่อนไข โปรดทราบว่านี่ไม่ใช่ลักษณะการทำงานเริ่มต้นสำหรับการตอบสนอง 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 object เปล่า? ฉันไม่แน่ใจว่าการส่งคืนค่าที่คาดว่าวัตถุจะปราศจากปัญหาเช่นกัน แต่อย่างใดรหัสปัจจุบันไม่ดีในแง่นี้
asgerhallas

1
มีจุดบกพร่องใน Json.Net ที่ทำให้ IE9 และต่ำกว่าไม่สามารถแยกวิเคราะห์ ISO 8601 Dates Json.Net สร้างขึ้น คำตอบนี้รวมอยู่ในการแก้ไข: 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แต่มีข้อความที่กำหนดเอง) นอกจากนี้การตรวจสอบ mvc เริ่มต้นของ asp.net อีก 2 รายการคือ 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);
    }
}

1
นี่เป็นทางออกที่ดี ทำให้พื้นเมืองreturn Json()มีผลใช้ Json.Net
OneHoopyFrood

1
สำหรับใครที่สงสัยว่ามันทำงานอย่างไรมันจะสกัดกั้นJsonResultจากJson()และแปลงเป็นไฟล์JsonNetResult. โดยใช้asคำหลักซึ่งส่งคืนค่าว่างหากไม่สามารถทำการแปลงได้ ดีมาก 10 คะแนนสำหรับ Gryffindor!
OneHoopyFrood

4
คำถามว่า Serializer เริ่มต้นทำงานบนวัตถุก่อนที่จะถูกดักฟังหรือไม่?
OneHoopyFrood

นี่คือคำตอบที่ยอดเยี่ยม - มีความยืดหยุ่นมากที่สุด เนื่องจากโครงการของฉันทำโซลูชันแบบแมนนวลทุกประเภทที่ส่วนหน้าอยู่แล้วฉันจึงไม่สามารถเพิ่มตัวกรองส่วนกลางได้ซึ่งจะต้องมีการเปลี่ยนแปลงที่ใหญ่กว่านี้ ฉันลงเอยด้วยการแก้ปัญหาเฉพาะในการดำเนินการของคอนโทรลเลอร์ในกรณีที่จำเป็นโดยใช้แอตทริบิวต์ในการกระทำของคอนโทรลเลอร์ของฉัน อย่างไรก็ตามฉันเรียกมันว่า - [BetterJsonHandler]:-)
Simcha Khabinsky

ส่งคืนสิ่งนี้ 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 สำหรับการใช้รูปแบบสตรีมของ. ซีเรียลไลซ์ () ฉันจะชี้ให้เห็นว่าคุณสามารถใช้ JsonConvert เหมือนกับคำตอบยอดนิยมอื่น ๆ แต่วิธีการของคุณจะค่อยๆสตรีมวัตถุยาว / ใหญ่ออกไปนั่นเป็นการเพิ่มประสิทธิภาพฟรีโดยเฉพาะอย่างยิ่งหากไคลเอนต์ดาวน์สตรีมสามารถจัดการการตอบสนองบางส่วนได้
Chris Moschini

1
การใช้งานที่ดี นี่น่าจะเป็นคำตอบ!
Kat Lim Ruiz

ไปได้ดีนี่เป็นสิ่งเดียวที่ฉันใช้ตัวควบคุมพื้นฐานสำหรับ
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

ฉันใช้สิ่งนี้กับ JSON.NET เช่นนี้ JObject jo = GetJSON(); return Content(jo.ToString(), "application/json");
John Mott

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

ฉันสร้างเวอร์ชันที่ทำให้การทำงานของบริการเว็บปลอดภัยและเรียบง่าย คุณใช้สิ่งนี้:

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 เป็น typesafe ทางฝั่งเซิร์ฟเวอร์: เมธอดต้องส่งคืนประเภท MyDataContract ทำให้ฝั่งไคลเอ็นต์ชัดเจนว่าโครงสร้างข้อมูลใดถูกส่งกลับ นอกจากนี้ยังกระชับและอ่านได้ - JsonResult <T> autoconverts ทุกประเภทที่ถูกส่งกลับไปยัง Json และคุณไม่ต้องทำอะไรเลย
Curtis Yallop
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.