Jan 06, 2015, 11:17 AM
Global AJAX error handling in MVC Controllers
Hi,
I think the following could be a useful implementation to handle unexpected errors globally inside an MVC Controller.
The idea is that by default any uncaught exceptions in a controller will redirect to your configured error page (assuming you have set that up). That may be fine for Controllers that are returning Views.
But if your Controllers are returning JSON, such as Ext.NET DirectResult instances, then you may not want a redirect to the default error page, especially if you have set up error handling in your Direct Method invocation or global AJAX error handling in your JavaScript layer, etc.
So, to handle that, we can extend the default HandleErrorAttribute and register it as a global filter:
Hope that helps.
Question: while this works for my needs, can this be improved? Are there some conditions/scenarios missed out, in particular for Ext.NET based AJAX request handling?
I think the following could be a useful implementation to handle unexpected errors globally inside an MVC Controller.
The idea is that by default any uncaught exceptions in a controller will redirect to your configured error page (assuming you have set that up). That may be fine for Controllers that are returning Views.
But if your Controllers are returning JSON, such as Ext.NET DirectResult instances, then you may not want a redirect to the default error page, especially if you have set up error handling in your Direct Method invocation or global AJAX error handling in your JavaScript layer, etc.
So, to handle that, we can extend the default HandleErrorAttribute and register it as a global filter:
//
// Idea courtesy http://stackoverflow.com/questions/12705345/mvc3-return-json-on-error-instead-of-html
//
public class HandleAndLogErrorAttribute : HandleErrorAttribute
{
// I am using Log4Net for logging purposes, so getting a logger here
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled)
return;
Log.Error("Unhandled exception", filterContext.Exception);
if (RequestManager.IsAjaxRequest)
{
filterContext.Result = new DirectResult
{
Success = false,
ErrorMessage = Resources.UnexpectedError
};
// Note, Direct Method exceptions currently expect HTTP Status Code of 200, not 500
// More info: http://forums.ext.net/showthread.php?49481-Can-DirectResponse-set-the-http-status-code-to-500-in-case-of-errors
UpdateFilterContext(filterContext, (int)HttpStatusCode.OK);
}
else if (filterContext.HttpContext.Request.IsAjaxRequest())
{
// This section (if needed) would be for any AJAX requests that are
// not via Ext.NET - you could remove this if you don't need it
// In addition, you may want to change the Data to suit your needs
filterContext.Result = new JsonResult
{
Data = new { success = false, error = Resources.UnexpectedError },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
UpdateFilterContext(filterContext);
}
else
{
base.OnException(filterContext);
}
}
private static void UpdateFilterContext(ExceptionContext filterContext, int statusCode = (int)HttpStatusCode.InternalServerError)
{
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
Then in global.asax in Application_Start:FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
And FilterConfig: public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleAndLogErrorAttribute());
}
}
With the above you do not need boilerplate try/catch/log code for all your MVC Controllers that are Direct Method handlers. However, if you want to, you can still do that, assuming that when you catch unexpected exceptions you still return this.Direct(false, [error message]) after you have logged the exception or done whatever it is you need to.Hope that helps.
Question: while this works for my needs, can this be improved? Are there some conditions/scenarios missed out, in particular for Ext.NET based AJAX request handling?