Tôi đã dành vài giờ để giải quyết vấn đề này. Giải pháp của tôi dựa trên những mong muốn / yêu cầu sau:
- Không có mã xử lý lỗi biên soạn lặp lại trong tất cả các hành động của bộ điều khiển JSON.
- Giữ nguyên mã trạng thái HTTP (lỗi). Tại sao? Vì mối quan tâm của cấp cao hơn sẽ không ảnh hưởng đến việc thực hiện cấp dưới.
- Có thể lấy dữ liệu JSON khi xảy ra lỗi / ngoại lệ trên máy chủ. Tại sao? Bởi vì tôi có thể muốn thông tin lỗi phong phú. Ví dụ: thông báo lỗi, mã trạng thái lỗi miền cụ thể, dấu vết ngăn xếp (trong môi trường gỡ lỗi / phát triển).
- Phía máy khách dễ sử dụng - thích sử dụng jQuery.
Tôi tạo một HandleErrorAttribute (xem phần nhận xét mã để giải thích chi tiết). Một vài chi tiết bao gồm "usings" đã bị bỏ sót, vì vậy mã có thể không được biên dịch. Tôi thêm bộ lọc vào bộ lọc chung trong quá trình khởi tạo ứng dụng trong Global.asax.cs như sau:
GlobalFilters.Filters.Add(new UnikHandleErrorAttribute());
Thuộc tính:
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;
}
}
}
Tôi đã phát triển plugin jQuery sau để dễ sử dụng cho phía khách hàng:
(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)
Tôi nhận được gì từ tất cả những điều này? Kết quả cuối cùng là
- Không có hành động nào trong bộ điều khiển của tôi có yêu cầu trên HandleErrorAttributes.
- Không có hành động nào trong bộ điều khiển của tôi chứa bất kỳ mã xử lý lỗi tấm lò hơi lặp lại nào.
- Tôi có một điểm mã xử lý lỗi duy nhất cho phép tôi dễ dàng thay đổi ghi nhật ký và các nội dung liên quan đến xử lý lỗi khác.
- Một yêu cầu đơn giản: Các hành động của bộ điều khiển trả về JsonResult phải có kiểu trả về JsonResult chứ không phải một số kiểu cơ sở như ActionResult. Lý do: Xem bình luận mã trong FooHandleErrorAttribute.
Ví dụ về phía khách hàng:
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);
Nhận xét được hoan nghênh nhất! Có thể một ngày nào đó tôi sẽ viết blog về giải pháp này ...