Ich habe einige Stunden damit verbracht, dieses Problem zu lösen. Meine Lösung basiert auf folgenden Wünschen / Anforderungen:
- In allen JSON-Controller-Aktionen ist kein Code zur wiederholten Behandlung von Boilerplate-Fehlern enthalten.
- Bewahren Sie die HTTP-Statuscodes (Fehlercodes) auf. Warum? Weil Bedenken auf höherer Ebene die Implementierung auf niedrigerer Ebene nicht beeinflussen sollten.
- Sie können JSON-Daten abrufen, wenn auf dem Server ein Fehler / eine Ausnahme auftritt. Warum? Weil ich vielleicht reichhaltige Fehlerinformationen haben möchte. ZB Fehlermeldung, domänenspezifischer Fehlerstatuscode, Stack-Trace (in Debug- / Entwicklungsumgebung).
- Benutzerfreundlichkeit clientseitig - vorzugsweise mit jQuery.
Ich erstelle ein HandleErrorAttribute (Erläuterungen zu den Details finden Sie in den Codekommentaren). Einige Details, einschließlich "Verwendungen", wurden weggelassen, sodass der Code möglicherweise nicht kompiliert werden kann. Ich füge den Filter den globalen Filtern während der Anwendungsinitialisierung in Global.asax.cs folgendermaßen hinzu:
GlobalFilters.Filters.Add(new UnikHandleErrorAttribute());
Attribut:
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;
}
}
}
Ich habe das folgende jQuery-Plugin für die clientseitige Benutzerfreundlichkeit entwickelt:
(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)
Was bekomme ich von all dem? Das Endergebnis ist das
- Keine meiner Controller-Aktionen hat Anforderungen an HandleErrorAttributes.
- Keine meiner Steuerungsaktionen enthält einen sich wiederholenden Fehlerbehandlungscode für die Kesselplatte.
- Ich habe einen einzigen Fehlerbehandlungscode, mit dem ich die Protokollierung und andere mit der Fehlerbehandlung verbundene Dinge einfach ändern kann.
- Eine einfache Anforderung: Controller-Aktionen, die JsonResult zurückgeben, müssen den Rückgabetyp JsonResult und keinen Basistyp wie ActionResult haben. Grund: Siehe Codekommentar in FooHandleErrorAttribute.
Client-seitiges Beispiel:
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);
Kommentare sind herzlich willkommen! Ich werde wahrscheinlich eines Tages über diese Lösung bloggen ...