http://blog.naver.com/techshare/100145191355
위에서 퍼온 글입니다.
그래서, Controller 측에서 다음과 같이 간단하게 메서드를 만들어 두면,
클라이언트 측에서 ContentType = "application/json"으로 지정하는 것만으로 자연스럽게 호출하는 것이 가능합니다. (이 정도면, JSON 용 서비스를 굳이 WCF 에 맡길 필요가 없을 정도로 편리하군요. ^^)
그런데, 한가지 문제가 있습니다. 예를 들어, 전달되는 데이터를 다음과 같이 크게 해주면,
이후의 HTTP 호출에서 클라이언트 측에 예외가 발생합니다.
별다른 오류 원인을 알 수 없어 답답한데요, 원인 규명을 위해 서버 측의 MVC Controller 에 Execute 메서드를 다음과 같이 재정의 해주면,
오류 메시지로 다음과 같은 내용을 얻을 수 있습니다.
실제로 MSDN 문서에서 JavaScriptSerializer.MaxJsonLength 속성을 보면,
2,097,152 (바이트 수가 아닌) 정수값이 기본이고 유니코드 2바이트 기준으로 4MB 정도에 해당하는 입력을 받을 수 있다고 나옵니다. 불행히도 JavaScriptSerializer 개체를 개발자가 정의한 것이 아니라 MVC 내부적으로 생성되는 것이기 때문에 아마도 이 제한을 벗어나는 방법이 '외부 설정'값으로 존재해야만 할텐데요.
이에 대해 웹 상에서 검색해 보면,
아래와 같은 설정값을 발견할 수 있습니다.
하지만, MSDN 문서 및 위의 덧글에서도 나오지만,
이 값은 웹 메서드가 비동기로 호출되었을 때에나 내부적으로 적용되는 값일 뿐 MVC3에서의 JSON 바인딩에서는 사용되지 않습니다.
실제로 .NET Reflector 를 이용하여 System.Web.Mvc.JsonValueProviderFactory 개체에 정의된 GetDeserializedObject 메서드를 살펴보면 JavaScriptSerializer 개체를 생성하고 곧바로 DeserializeObject 메서드를 호출하는 것을 볼 수 있습니다.
따라서, 이 문제를 해결하려면 MVC3 에서 JavaScriptSerializer를 사용하는 JSON 바인딩 지원모듈을 사용자 정의해야 하는데요. 다행히 검색을 해보니, 이에 대한 방법이 제공되고 있습니다.
자료는 삭제되었지만, 구글 검색의 "저장된 페이지" 기능을 이용해서 보면 다음과 같은 내용을 확인할 수 있습니다.
(비록 본문의 버그가 JavaScriptSerializer.MaxJsonLength 값을 늘리는 것과는 상관없는 이야기이지만!) ValueProviderFactory 를 임의로 변경하는 것이 가능하다는 이야기인데요. 그렇다면 우리는 JavaScriptSerializer.MaxJsonLength 값을 변경해서 반환하는 ValueProviderFactory를 만들면 되는데... 어렵지 않게 다음과 같이 MVC 에서 제공되는 JsonValueProviderFactory 타입의 모든 코드를 재사용해주면 됩니다.
이제 이렇게 재정의한 JsonValueProviderFactory2 타입을 Global.asax 에서 다음과 같이 추가합니다.
최종적으로 빌드하고 다시 테스트를 해보면 Action 메서드가 정상적으로 실행되는 것을 확인할 수 있습니다. ^^
(첨부된 파일은 위의 코드를 포함한 예제 프로젝트입니다.)
위에서 퍼온 글입니다.
MVC3 에 새롭게 추가된 기능 중의 하나가 바로 JSON 개체 바인딩을 내장하고 있는 것입니다.
Introducing ASP.NET MVC 3 (Preview 1) - JavaScript and AJAX Improvements ; http://weblogs.asp.net/scottgu/archive/2010/07/27/introducing-asp-net-mvc-3-preview-1.aspx
그래서, Controller 측에서 다음과 같이 간단하게 메서드를 만들어 두면,
public class HomeController : Controller { public JsonResult TestJson(MyObject param) { // System.Diagnostics.Trace.WriteLine(param.Text); return Json(null, JsonRequestBehavior.AllowGet); } } public class MyObject { public string Text { get; set; } }
클라이언트 측에서 ContentType = "application/json"으로 지정하는 것만으로 자연스럽게 호출하는 것이 가능합니다. (이 정도면, JSON 용 서비스를 굳이 WCF 에 맡길 필요가 없을 정도로 편리하군요. ^^)
string url = "http://localhost:2509/Home/TestJson";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = "application/json; charset=utf-8";
request.Method = "POST";
string txt = "{\"Text\":\"test\"}";
StreamWriter sw = new StreamWriter(request.GetRequestStream());
sw.Write(txt);
sw.Close();
HttpWebResponse resp = request.GetResponse() as HttpWebResponse;
StreamReader sr = new StreamReader(resp.GetResponseStream());
string result = sr.ReadToEnd();
resp.Close();
그런데, 한가지 문제가 있습니다. 예를 들어, 전달되는 데이터를 다음과 같이 크게 해주면,
string txt = "{\"Text\":\"" + new string('c', 2097153) + "\"}";
이후의 HTTP 호출에서 클라이언트 측에 예외가 발생합니다.
System.Net.WebException occurred
Message=The remote server returned an error: (500) Internal Server Error.
Source=System
StackTrace:
at System.Net.HttpWebRequest.GetResponse()
at ConsoleApplication1.Program.Main(String[] args) in D:\...\Program.cs:line 35
InnerException:
별다른 오류 원인을 알 수 없어 답답한데요, 원인 규명을 위해 서버 측의 MVC Controller 에 Execute 메서드를 다음과 같이 재정의 해주면,
protected override void Execute(System.Web.Routing.RequestContext requestContext)
{
try
{
base.Execute(requestContext);
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.ToString());
throw;
}
}
오류 메시지로 다음과 같은 내용을 얻을 수 있습니다.
System.ArgumentException was unhandled by user code
Message=Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property.
Parameter name: input
Source=System.Web.Extensions
ParamName=input
StackTrace:
at System.Web.Script.Serialization.JavaScriptSerializer.Deserialize(JavaScriptSerializer serializer, String input, Type type, Int32 depthLimit)
at System.Web.Script.Serialization.JavaScriptSerializer.DeserializeObject(String input)
at System.Web.Mvc.JsonValueProviderFactory.GetDeserializedObject(ControllerContext controllerContext)
at System.Web.Mvc.JsonValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.ValueProviderFactoryCollection.<>c__DisplayClassc.<GetValueProvider>b__7(ValueProviderFactory factory)
...[생략]...
at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
InnerException:
실제로 MSDN 문서에서 JavaScriptSerializer.MaxJsonLength 속성을 보면,
JavaScriptSerializer.MaxJsonLength ; http://msdn.microsoft.com/ko-kr/library/system.web.script.serialization.javascriptserializer.maxjsonlength.aspx
2,097,152 (바이트 수가 아닌) 정수값이 기본이고 유니코드 2바이트 기준으로 4MB 정도에 해당하는 입력을 받을 수 있다고 나옵니다. 불행히도 JavaScriptSerializer 개체를 개발자가 정의한 것이 아니라 MVC 내부적으로 생성되는 것이기 때문에 아마도 이 제한을 벗어나는 방법이 '외부 설정'값으로 존재해야만 할텐데요.
이에 대해 웹 상에서 검색해 보면,
Can I set an unlimited length for maxJsonLength in web.config? ; http://stackoverflow.com/questions/1151987/can-i-set-an-unlimited-length-for-maxjsonlength-in-web-config
아래와 같은 설정값을 발견할 수 있습니다.
<configuration> <system.web.extensions> <scripting> <webServices> <jsonSerialization maxJsonLength="50000000"/> </webServices> </scripting> </system.web.extensions> </configuration>
하지만, MSDN 문서 및 위의 덧글에서도 나오지만,
The value of the MaxJsonLength property applies only to the internal JavaScriptSerializer instance that is used by the asynchronous communication layer to invoke Web services methods. (MSDN: ScriptingJsonSerializationSection.MaxJsonLength Property)
Basically, the "internal" JavaScriptSerializer respects the value of maxJsonLength when called from a web method; direct use of a JavaScriptSerializer (or use via an MVC action-method/Controller) does not respect the maxJsonLength property, at least not from the systemWebExtensions.scripting.webServices.jsonSerialization section of web.config.
Basically, the "internal" JavaScriptSerializer respects the value of maxJsonLength when called from a web method; direct use of a JavaScriptSerializer (or use via an MVC action-method/Controller) does not respect the maxJsonLength property, at least not from the systemWebExtensions.scripting.webServices.jsonSerialization section of web.config.
이 값은 웹 메서드가 비동기로 호출되었을 때에나 내부적으로 적용되는 값일 뿐 MVC3에서의 JSON 바인딩에서는 사용되지 않습니다.
실제로 .NET Reflector 를 이용하여 System.Web.Mvc.JsonValueProviderFactory 개체에 정의된 GetDeserializedObject 메서드를 살펴보면 JavaScriptSerializer 개체를 생성하고 곧바로 DeserializeObject 메서드를 호출하는 것을 볼 수 있습니다.
private static object GetDeserializedObject(ControllerContext controllerContext)
{
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
{
return null;
}
string str = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();
if (string.IsNullOrEmpty(str))
{
return null;
}
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.DeserializeObject(str);
}
따라서, 이 문제를 해결하려면 MVC3 에서 JavaScriptSerializer를 사용하는 JSON 바인딩 지원모듈을 사용자 정의해야 하는데요. 다행히 검색을 해보니, 이에 대한 방법이 제공되고 있습니다.
JSON / MVC (3P1) HttpPost - not getting it to work on my EF class ; http://tech-question.com/json-mvc-3p1-httppost-not-getting-it-to-work-on-my-ef-class-362041 (현재, 위의 링크에 대한 자료는 삭제된 상태입니다.)
자료는 삭제되었지만, 구글 검색의 "저장된 페이지" 기능을 이용해서 보면 다음과 같은 내용을 확인할 수 있습니다.
There's a bug in MVC 3 Preview 1 where the JsonValueProviderFactory is not registered by default. Having something like this in your Global.asax should help: ValueProviderFactories.Factories.Add(new JsonValueProviderFactory())
(비록 본문의 버그가 JavaScriptSerializer.MaxJsonLength 값을 늘리는 것과는 상관없는 이야기이지만!) ValueProviderFactory 를 임의로 변경하는 것이 가능하다는 이야기인데요. 그렇다면 우리는 JavaScriptSerializer.MaxJsonLength 값을 변경해서 반환하는 ValueProviderFactory를 만들면 되는데... 어렵지 않게 다음과 같이 MVC 에서 제공되는 JsonValueProviderFactory 타입의 모든 코드를 재사용해주면 됩니다.
using System.Web.Mvc; using System.Collections.Generic; using System.Collections; using System.IO; using System; using System.Web.Script.Serialization; using System.Globalization; public sealed class JsonValueProviderFactory2 : ValueProviderFactory { // Methods private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value) { ...[생략: JsonValueProviderFactory 의 AddToBackingStore 코드를 복사]... } private static object GetDeserializedObject(ControllerContext controllerContext) { ...[생략: JsonValueProviderFactory 의 AddToBackingStore 코드를 복사]... JavaScriptSerializer serializer = new JavaScriptSerializer(); serializer.MaxJsonLength = Int32.MaxValue; return serializer.DeserializeObject(str); } public override IValueProvider GetValueProvider(ControllerContext controllerContext) { ...[생략: JsonValueProviderFactory 의 GetValueProvider 코드를 복사]... } private static string MakeArrayKey(string prefix, int index) { ...[생략: JsonValueProviderFactory 의 MakeArrayKey 코드를 복사]... } private static string MakePropertyKey(string prefix, string propertyName) { ...[생략: JsonValueProviderFactory 의 MakePropertyKey 코드를 복사]... } }
이제 이렇게 재정의한 JsonValueProviderFactory2 타입을 Global.asax 에서 다음과 같이 추가합니다.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
ValueProviderFactory jsonFactory = null;
foreach (ValueProviderFactory factory in ValueProviderFactories.Factories)
{
if (factory.GetType().FullName == "System.Web.Mvc.JsonValueProviderFactory")
{
jsonFactory = factory;
break;
}
}
if (jsonFactory != null)
{
ValueProviderFactories.Factories.Remove(jsonFactory);
}
JsonValueProviderFactory2 factory2 = new JsonValueProviderFactory2();
ValueProviderFactories.Factories.Add(factory2);
}
최종적으로 빌드하고 다시 테스트를 해보면 Action 메서드가 정상적으로 실행되는 것을 확인할 수 있습니다. ^^
(첨부된 파일은 위의 코드를 포함한 예제 프로젝트입니다.)