Sử dụng JSON.NET làm trình tuần tự JSON mặc định trong ASP.NET MVC 3 - có khả thi không?


101

Có thể sử dụng JSON.NET làm bộ tuần tự JSON mặc định trong ASP.NET MVC 3 không?

Theo nghiên cứu của tôi, có vẻ như cách duy nhất để thực hiện điều này là mở rộng ActionResultJsonResult trong MVC3 không phải là ảo ...

Tôi hy vọng rằng với ASP.NET MVC 3 sẽ có một cách để chỉ định một nhà cung cấp có thể cắm thêm để tuần tự hóa thành JSON.

Suy nghĩ?


Câu trả lời:


106

Tôi tin rằng cách tốt nhất để làm điều đó, là - như được mô tả trong các liên kết của bạn - để mở rộng ActionResult hoặc mở rộng JsonResult trực tiếp.

Đối với phương thức JsonResult không ảo trên bộ điều khiển là không đúng, chỉ cần chọn quá tải phù hợp. Điều này hoạt động tốt:

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

CHỈNH SỬA 1 : Một phần mở rộng 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);
    }

CHỈNH SỬA 2 : Tôi đã xóa kiểm tra Dữ liệu là trống theo các đề xuất bên dưới. Điều đó sẽ làm cho các phiên bản mới hơn của JQuery hài lòng và có vẻ như là điều nên làm, vì phản hồi sau đó có thể được giải phóng không điều kiện. Tuy nhiên, hãy lưu ý rằng đây không phải là hành vi mặc định cho các phản hồi JSON từ ASP.NET MVC, mà phản hồi bằng một chuỗi trống, khi không có dữ liệu.


1
Mã đề cập đến MySpecialContractResolver, không được xác định. Câu hỏi này giúp giải quyết vấn đề đó (và rất liên quan đến vấn đề tôi phải giải quyết): stackoverflow.com/questions/6700053/…
Elliveny 19/02/12

1
Cảm ơn vì câu trả lời tuyệt vời. Tại sao trả về if (Data == null); ? Đối với trường hợp sử dụng của tôi, tôi muốn lấy lại bất kỳ tiêu chuẩn JSON nào, mà Json.Net trung thành thực hiện, ngay cả đối với null (trả về "null"). Bằng cách chặn các giá trị null bạn kết thúc gửi chuỗi lại trống rỗng, cho các, mà lệch khỏi tiêu chuẩn và gây ra vấn đề hạ lưu - ví dụ với jQuery 1.9.1: stackoverflow.com/a/15939945/176877
Chris Moschini

1
@Chris Moschini: Bạn hoàn toàn đúng. Trả về một chuỗi rỗng là sai. Nhưng sau đó nó nên trả về giá trị json null hay một đối tượng json trống? Tôi không chắc việc trả về một giá trị mà một đối tượng được mong đợi cũng không gặp sự cố. Nhưng dù bằng cách nào thì mã hiện tại cũng không tốt về mặt này.
asgerhallas

1
Có một lỗi trong Json.Net khiến IE9 trở xuống không thể phân tích cú pháp ISO 8601 Dates mà Json.Net tạo ra. Fix cho điều này được bao gồm trong câu trả lời này: stackoverflow.com/a/15939945/176877
Chris Moschini

1
@asgerhallas, @Chris Moschini Điều gì về kiểm tra mặc định của asp.net mvc JsonResult if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);? Tôi nghĩ cần phải thêm câu trả lời kiểm tra này (không có nội bộ MvcResources.JsonRequest_GetNotAllowednhưng với một số thông báo tùy chỉnh) Ngoài ra, còn 2 kiểm tra mvc asp.net mặc định khác - MaxJsonLength và RecursionLimit thì sao? Chúng ta có cần chúng nếu chúng ta sử dụng json.net không?
chromigo

60

Tôi đã thực hiện điều này mà không cần bộ điều khiển cơ sở hoặc tiêm.

Tôi đã sử dụng bộ lọc hành động để thay thế JsonResult bằng 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);
    }
}

Trong Global.asax.cs Application_Start (), bạn cần thêm:

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

Vì lợi ích của việc hoàn thành, đây là lớp mở rộng JsonNetResult của tôi mà tôi đã chọn từ một nơi khác và tôi đã sửa đổi một chút để nhận được hỗ trợ hấp chính xác:

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
Đây là một giải pháp tốt. Làm cho nó để bản địa return Json()có hiệu lực sử dụng Json.Net.
OneHoopyFrood

1
Đối với bất kỳ ai tự hỏi điều này hoạt động như thế nào, nó sẽ chặn JsonResulttừ Json()và chuyển đổi nó thành a JsonNetResult. Nó làm như vậy bằng cách sử dụng astừ khóa trả về null nếu không thể chuyển đổi. Rất tiện lợi. 10 điểm cho Gryffindor!
OneHoopyFrood

4
Tuy nhiên, câu hỏi đặt ra là bộ tuần tự hóa mặc định có chạy trên đối tượng trước khi nó bị chặn không?
OneHoopyFrood

Đây là một câu trả lời tuyệt vời - với tính linh hoạt nhất. Vì dự án của tôi đã thực hiện tất cả các loại giải pháp thủ công trên giao diện người dùng, tôi không thể thêm bộ lọc toàn cục - điều này sẽ yêu cầu một sự thay đổi lớn hơn. Tôi đã kết thúc chỉ giải quyết vấn đề trên các hành động của bộ điều khiển khi cần thiết bằng cách sử dụng thuộc tính trên các hành động của bộ điều khiển của tôi. Tuy nhiên, tôi đã gọi nó - [BetterJsonHandler]:-).
Simcha Khabinsky

trả về this.Json (null); vẫn không trả lại gì
Brunis

27

Sử dụng trình chuyển đổi JSON của Newtonsoft:

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

7
Không chắc đây có phải là hacky hay không, nhưng thánh tào lao là nó dễ dàng hơn việc tạo các lớp mở rộng, chỉ để trả về một chuỗi json ngu ngốc.
dennis.sheppard

21

Tôi biết điều này là tốt sau khi câu hỏi đã được trả lời, nhưng tôi đang sử dụng một cách tiếp cận khác vì tôi đang sử dụng phương pháp tiêm phụ thuộc để khởi tạo bộ điều khiển của mình.

Tôi đã thay thế IActionInvoker (bằng cách đưa Thuộc tính ControllerActionInvoker của bộ điều khiển) bằng một phiên bản ghi đè phương thức InvokeActionMethod.

Điều này có nghĩa là không có thay đổi đối với sự kế thừa bộ điều khiển và nó có thể dễ dàng bị xóa khi tôi nâng cấp lên MVC4 bằng cách thay đổi đăng ký của vùng chứa DI cho TẤT CẢ bộ điều khiển

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 - Đã cập nhật để hiển thị đăng ký vùng chứa cho bộ điều khiển. Tôi đang sử dụng Unity ở đây.

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);
    }
}

Đẹp, nhưng bạn sử dụng nó như thế nào? Hay tốt hơn là bạn đã tiêm nó như thế nào?
Adaptabi vào

+1 để sử dụng dạng Luồng của .Serialize (). Tôi sẽ chỉ ra rằng bạn chỉ có thể sử dụng JsonConvert giống như câu trả lời hàng đầu khác, nhưng cách tiếp cận của bạn dần dần phát ra các đối tượng dài / lớn - đó là một biện pháp tăng hiệu suất miễn phí, đặc biệt nếu ứng dụng khách phía dưới có thể xử lý các phản hồi một phần.
Chris Moschini

1
triển khai tốt đẹp. Đây nên là câu trả lời!
Kat Lim Ruiz

Tốt thôi, đây là thứ duy nhất tôi sử dụng bộ điều khiển cơ bản.
Chris Diver

thực sự tốt - điều này tốt hơn nhiều khi ghi đè hàm Json (), vì ở mọi vị trí mà bạn sẽ trả về một JsonResult, hàm này sẽ hoạt động và làm nó kỳ diệu. Đối với những người không sử dụng DI, chỉ cần thêm ghi đè bảo vệ IActionInvoker CreateActionInvoker () {return JsonNetActionInvoker mới ();} để điều khiển cơ sở của bạn
Avi Pinto

13

Mở rộng trên câu trả lời từ https://stackoverflow.com/users/183056/sami-beyoglu , nếu bạn đặt Loại nội dung, thì jQuery sẽ có thể chuyển đổi dữ liệu trả về thành đối tượng cho bạn.

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

Cảm ơn bạn, tôi có một hỗn hợp lai và đây là điều duy nhất sẽ làm việc cho tôi.
done_merson

Tôi đã sử dụng cái này với JSON.NET như thế này: JObject jo = GetJSON(); return Content(jo.ToString(), "application/json");
John Mott

6

Bài viết của tôi có thể giúp ai đó.

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);
    }
}    

Tôi đã tìm kiếm một giải pháp thực tế và bạn là câu trả lời duy nhất đúng
Richard Aguirre

Cảm ơn. Sau khi triển khai của riêng tôi BaseController, đây là thay đổi tác động thấp nhất - chỉ cần thêm lớp và cập nhật BaseController.
AndrewP

4

Tôi đã tạo một phiên bản giúp các thao tác dịch vụ web trở nên an toàn và đơn giản. Bạn sử dụng nó như thế này:

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

Lớp:

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);
    }
}

nhưng tại sao bạn muốn có một loại JsonResult mạnh mẽ? : D bạn mất kết quả loại ẩn danh và không kiếm được gì ở phía máy khách, vì nó không sử dụng C # classses dù sao?
mikus

1
@mikus Đây là loại an toàn ở phía máy chủ: phương thức phải trả về loại MyDataContract. Nó làm cho phía máy khách rõ ràng chính xác cấu trúc dữ liệu nào đang được trả về. Nó cũng ngắn gọn và dễ đọc - JsonResult <T> tự động chuyển đổi bất kỳ kiểu nào được trả lại cho Json và bạn không phải làm gì cả.
Curtis Yallop
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.