ASP.NET MVC 3에서 JSON.NET을 기본 JSON 직렬 변환기로 사용하는 것이 가능합니까?


101

ASP.NET MVC 3에서 JSON.NET 을 기본 JSON 직렬 변환기로 사용할 수 있습니까?

내 연구에 따르면이를 수행하는 유일한 방법 은 MVC3의 JsonResult가 가상이 아니기 때문에 ActionResult확장 하는 것 같습니다 .

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를 참조합니다. 이 질문은 이에 도움이됩니다 (그리고 제가 해결
해야하는

1
훌륭한 답변에 감사드립니다. if (Data == null)이 반환되는 이유; ? 내 사용 사례에서는 JSON 표준이 무엇이든 되찾고 싶었습니다. Json.Net은 null ( "null"반환)에 대해서도 충실하게 수행합니다. : 예를 들어, jQuery를 1.9.1로 - 널 (null) 값을 차단함으로써 당신은 표준에서 벗어나는 및 다운 스트림 문제가 발생 이들에 대한 빈 문자열 다시 보내기, 결국 stackoverflow.com/a/15939945/176877
크리스 Moschini

1
@Chris Moschini : 당신이 절대적으로 옳습니다. 빈 문자열을 반환하는 것은 잘못되었습니다. 하지만 그러면 json 값 null 또는 빈 json 객체를 반환해야합니까? 객체가 예상되는 값을 반환하는 것이 문제가 없는지 확실하지 않습니다. 그러나 어느 쪽이든 현재 코드는이 점에서 좋지 않습니다.
asgerhallas apr

1
Json.Net에는 IE9 이하에서 Json.Net이 생성하는 ISO 8601 날짜를 구문 분석하지 못하는 버그가 있습니다. 이것에 대한 수정이 대답에 포함되어 stackoverflow.com/a/15939945/176877을
크리스 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을 사용하는 경우 필요합니까?
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 2015

1
이 작품이 얼마나 궁금 누군가를 위해, 그것은 차단 JsonResult에서 Json()A와 개종자를 JsonNetResult. as변환이 불가능한 경우 null을 반환 하는 키워드를 사용 합니다. 아주 멋져요. 그리핀도르에게 10 점!
OneHoopyFrood 2015

4
그러나 질문, 기본 직렬 변환기는 가로 채기 전에 개체에서 실행됩니까?
OneHoopyFrood 2015

이것은 가장 융통성있는 환상적인 대답입니다. 내 프로젝트는 이미 프런트 엔드에서 모든 종류의 수동 솔루션을 수행하고 있었기 때문에 전역 필터를 추가 할 수 없었습니다.이 경우 더 큰 변경이 필요합니다. 필자는 내 컨트롤러의 작업에 속성을 사용하여 필요한 경우 컨트롤러 작업에서만 문제를 해결했습니다. 그러나 나는 그것을- [BetterJsonHandler]:-) 라고 불렀다 .
Simcha Khabinsky

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 Property 주입)를 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

.Serialize ()의 스트림 형식을 사용하는 경우 +1. 다른 최고 답변처럼 JsonConvert를 사용할 수 있지만 접근 방식은 점차 길거나 큰 객체를 스트리밍합니다. 이는 특히 다운 스트림 클라이언트가 부분 응답을 처리 할 수있는 경우 무료 성능 향상입니다.
Chris Moschini 2013

1
멋진 구현입니다. 이것이 답이되어야합니다!
Kat Lim Ruiz

좋아, 이것이 내가 기본 컨트롤러를 사용했던 유일한 것입니다.
Chris Diver

정말 좋습니다-이것은 Json () 함수를 재정의하는 것보다 훨씬 낫습니다. JsonResult를 반환하는 모든 위치에서 이것이 시작되고 마술이기 때문입니다. 베이스 컨트롤러, DI를 사용하지 않는 사람들을 위해, 단지 보호 된 재정의 IActionInvoker CreateActionInvoker를 (추가) {새로운 JsonNetActionInvoker ()를 호출}
아비 핀토

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

난 진짜 해결책을 찾는 한 당신은 유일한 정답이었다
리처드 아구

감사. 이미 직접 구현 한 변경 사항 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를 원합니까? : 어쨌든 C # 클래스를 사용하지 않기 때문에 익명 유형 결과를 풀고 클라이언트 측에서 아무것도 얻지 못합니까?
mikus

1
@mikus 서버 측에서는 형식이 안전합니다. 메서드는 MyDataContract 형식을 반환해야합니다. 반환되는 데이터 구조를 정확히 클라이언트 측에 명확하게합니다. 또한 간결하고 읽기 쉽습니다. JsonResult <T>는 Json으로 반환되는 모든 유형을 자동 변환하며 사용자는 아무것도 할 필요가 없습니다.
Curtis Yallop 2015
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.