이 문제를 해결하는 데 몇 시간을 보냈습니다. 내 솔루션은 다음과 같은 희망 사항 / 요구 사항을 기반으로합니다.
- 모든 JSON 컨트롤러 작업에 반복적 인 상용구 오류 처리 코드가 없어야합니다.
- HTTP (오류) 상태 코드를 보존합니다. 왜? 높은 수준의 우려는 낮은 수준의 구현에 영향을 미치지 않기 때문입니다.
- 서버에서 오류 / 예외가 발생하면 JSON 데이터를 가져올 수 있습니다. 왜? 풍부한 오류 정보를 원할 수 있기 때문입니다. 예 : 오류 메시지, 도메인 별 오류 상태 코드, 스택 추적 (디버그 / 개발 환경에서).
- 사용 편의성 클라이언트 측-jQuery를 사용하는 것이 좋습니다.
HandleErrorAttribute를 만듭니다 (자세한 내용은 코드 주석 참조). "사용"을 포함한 몇 가지 세부 사항은 생략되었으므로 코드가 컴파일되지 않을 수 있습니다. 다음과 같이 Global.asax.cs에서 응용 프로그램을 초기화하는 동안 전역 필터에 필터를 추가합니다.
GlobalFilters.Filters.Add(new UnikHandleErrorAttribute());
속성:
namespace Foo
{
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class FooHandleErrorAttribute : HandleErrorAttribute
{
private readonly TraceSource _TraceSource;
public FooHandleErrorAttribute(TraceSource traceSource)
{
if (traceSource == null)
throw new ArgumentNullException(@"traceSource");
_TraceSource = traceSource;
}
public TraceSource TraceSource
{
get
{
return _TraceSource;
}
}
public FooHandleErrorAttribute()
{
var className = typeof(FooHandleErrorAttribute).FullName ?? typeof(FooHandleErrorAttribute).Name;
_TraceSource = new TraceSource(className);
}
public override void OnException(ExceptionContext filterContext)
{
var actionMethodInfo = GetControllerAction(filterContext.Exception);
if(actionMethodInfo == null) return;
var controllerName = filterContext.Controller.GetType().FullName;
var actionName = actionMethodInfo.Name;
var traceMessage = string.Format(@"Unhandled exception from {0}.{1} handled in {2}. Exception: {3}", controllerName, actionName, typeof(FooHandleErrorAttribute).FullName, filterContext.Exception);
_TraceSource.TraceEvent(TraceEventType.Error, TraceEventId.UnhandledException, traceMessage);
if (actionMethodInfo.ReturnType != typeof(JsonResult)) return;
var jsonMessage = FooHandleErrorAttributeResources.Error_Occured;
if (filterContext.Exception is MySpecialExceptionWithUserMessage) jsonMessage = filterContext.Exception.Message;
filterContext.Result = new JsonResult
{
Data = new
{
message = jsonMessage,
stacktrace = MyEnvironmentHelper.IsDebugging ? filterContext.Exception.StackTrace : null
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
base.OnException(filterContext);
}
private static MethodInfo GetControllerAction(Exception exception)
{
var stackTrace = new StackTrace(exception);
var frames = stackTrace.GetFrames();
if(frames == null) return null;
var frame = frames.FirstOrDefault(f => typeof(IController).IsAssignableFrom(f.GetMethod().DeclaringType));
if (frame == null) return null;
var actionMethod = frame.GetMethod();
return actionMethod as MethodInfo;
}
}
}
클라이언트 측 사용 편의성을 위해 다음 jQuery 플러그인을 개발했습니다.
(function ($, undefined) {
"using strict"
$.FooGetJSON = function (url, data, success, error) {
/// <summary>
/// **********************************************************
/// * UNIK GET JSON JQUERY PLUGIN. *
/// **********************************************************
/// This plugin is a wrapper for jQuery.getJSON.
/// The reason is that jQuery.getJSON success handler doesn't provides access to the JSON object returned from the url
/// when a HTTP status code different from 200 is encountered. However, please note that whether there is JSON
/// data or not depends on the requested service. if there is no JSON data (i.e. response.responseText cannot be
/// parsed as JSON) then the data parameter will be undefined.
///
/// This plugin solves this problem by providing a new error handler signature which includes a data parameter.
/// Usage of the plugin is much equal to using the jQuery.getJSON method. Handlers can be added etc. However,
/// the only way to obtain an error handler with the signature specified below with a JSON data parameter is
/// to call the plugin with the error handler parameter directly specified in the call to the plugin.
///
/// success: function(data, textStatus, jqXHR)
/// error: function(data, jqXHR, textStatus, errorThrown)
///
/// Example usage:
///
/// $.FooGetJSON('/foo', { id: 42 }, function(data) { alert('Name :' + data.name)
/// </summary>
// Call the ordinary jQuery method
var jqxhr = $.getJSON(url, data, success)
// Do the error handler wrapping stuff to provide an error handler with a JSON object - if the response contains JSON object data
if (typeof error !== "undefined") {
jqxhr.error(function(response, textStatus, errorThrown) {
try {
var json = $.parseJSON(response.responseText)
error(json, response, textStatus, errorThrown)
} catch(e) {
error(undefined, response, textStatus, errorThrown)
}
})
}
// Return the jQueryXmlHttpResponse object
return jqxhr
}
})(jQuery)
이 모든 것에서 무엇을 얻습니까? 최종 결과는
- 내 컨트롤러 작업에는 HandleErrorAttributes에 대한 요구 사항이 없습니다.
- 내 컨트롤러 작업에는 반복적 인 보일러 플레이트 오류 처리 코드가 포함되어 있지 않습니다.
- 로깅 및 기타 오류 처리 관련 항목을 쉽게 변경할 수있는 단일 오류 처리 코드가 있습니다.
- 간단한 요구 사항 : JsonResult를 반환하는 컨트롤러 작업에는 ActionResult와 같은 일부 기본 유형이 아닌 반환 유형 JsonResult가 있어야합니다. 이유 : FooHandleErrorAttribute의 코드 주석을 참조하십시오.
클라이언트 측 예 :
var success = function(data) {
alert(data.myjsonobject.foo);
};
var onError = function(data) {
var message = "Error";
if(typeof data !== "undefined")
message += ": " + data.message;
alert(message);
};
$.FooGetJSON(url, params, onSuccess, onError);
의견을 환영합니다! 언젠가는이 솔루션에 대해 블로그를 작성할 것입니다.