ASP.NET MVC’s controllers have great error handling capabilities and can be easily extended to cater for application specific requirements. However, many developer don’t want to manage errors at a controller level and would rather manage 99 percent of errors from a single location. This allows for a single point of error logging, notification and handling.
The Application_Error event within the global.asax is the perfect place for this, but there are a few issues to consider when used in an MVC project.
- Server.Transfer is not available to serve an error message as it requires a physical file to serve. Your MVC project more than likely doesn’t have one of theses as it uses routes, controllers and views. We need to find a workaround for this in order to return suitable response headers.
- Reponse.Redirect is not suitable as ’500: Internal Server Error’ and ’404: Not Found’ pages should serve a suitable response header, not a 301 redirect.
- Response.Clear() should be called to ensure that any content written to the response stream before the error occurred is removed.
- Server.ClearError() must be called to stop ASP.NET from serving the yellow screen of death.
With these points in mind the following steps can be coded into the Application_Error event for error handling and logging.
- Get the last error raised.
- Get the error code to respond with.
- Log the error (I’m ignoring 404′s).
- Clear the response stream.
- Clear the server error.
- Render the error handling controller without a redirect.
Here is the Application_Error code…
void Application_Error(object sender, EventArgs e) { var error = Server.GetLastError(); var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500; if (code != 404) { // Generate email with error details and send to administrator // I'm using RazorMail which can be downloaded from the Nuget Gallery // I also have an extension method on type Exception that creates a string representation var email = new RazorMailMessage("Website Error"); email.To.Add("errors@wduffy.co.uk"); email.Templates.Add(error.GetAsHtml(new HttpRequestWrapper(Request))); Kernel.Get<IRazorMailSender>().Send(email); } Response.Clear(); Server.ClearError(); string path = Request.Path; Context.RewritePath(string.Format("~/Errors/Http{0}", code), false); IHttpHandler httpHandler = new MvcHttpHandler(); httpHandler.ProcessRequest(Context); Context.RewritePath(path, false); }
And here is the ErrorsController code. Notice each action sets the response status error code before rendering the view. Be careful with this controller as any errors will result in an infinite loop between itself and the Application_Error event!
public class ErrorsController : Controller { [HttpGet] public ActionResult Http404(string source) { Response.StatusCode = 404; return View(); } [HttpGet] public ActionResult Http500(string source) { Response.StatusCode = 500; return View(); } }