Thursday 28 July 2011

MVC3/Razor - Global Error Handling


Here is a technique you can use within your MVC application to control global error handling. The technique is quite similar to aspx pages where you populate the Application_Error function within Global.asax, however, the routing is completely new!


Global.asax
Code Snippet
  1. /// <summary>
  2. /// Handle application error on a global level.
  3. /// Passes handling off to the ErrorController
  4. /// </summary>
  5. protected void Application_Error()
  6. {
  7.     var exception = Server.GetLastError();
  8.     var httpException = exception as HttpException;
  9.     Response.Clear();
  10.     Server.ClearError();
  11.     var routeData = new RouteData();
  12.     routeData.Values["controller"] = "Errors";
  13.     routeData.Values["action"] = "General";
  14.     routeData.Values["exception"] = exception;
  15.     Response.StatusCode = 500;
  16.  
  17.     if (httpException != null)
  18.     {
  19.         Response.StatusCode = httpException.GetHttpCode();
  20.  
  21.         switch (Response.StatusCode)
  22.         {
  23.             case 403:
  24.                 routeData.Values["action"] = "Http403";
  25.                 break;
  26.  
  27.             case 404:
  28.                 routeData.Values["action"] = "Http404";
  29.                 break;
  30.         }
  31.     }
  32.  
  33.     IController errorsController = new ErrorController();
  34.     var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
  35.     errorsController.Execute(rc);
  36. }
End of Code Snippet


Note: This will route our errors to the ErrorController. Lets take a look at the error controller...


ErrorController.cs
Code Snippet
  1. // <copyright file="ErrorController.cs" company="GinkoSolutions.com">
  2. // Copyright (c) 2011 All Right Reserved
  3. // </copyright>
  4. // <author>Sean Greasley</author>
  5. // <email>sean@ginkosolutions.com/sean@tutorialgenius.com</email>
  6. // <summary>Controller for handling errors within the application.</summary>
  7. namespace MVCEmailExample.Controllers
  8. {
  9.     using System;
  10.     using System.Collections.Generic;
  11.     using System.Linq;
  12.     using System.Web;
  13.     using System.Web.Mvc;
  14.     using MVCEmailExample.Models;
  15.  
  16.     /// <summary>
  17.     /// Controller for handling errors within the application.
  18.     /// </summary>
  19.     public class ErrorController : Controller
  20.     {
  21.         public ActionResult General(Exception exception)
  22.         {
  23.             return View("Error", new ErrorModel() { ErrorTitle = "General Error", ExceptionDetail = exception });
  24.         }
  25.  
  26.         public ActionResult Http404()
  27.         {
  28.             return View("Error", new ErrorModel() { ErrorTitle = "Not found" });
  29.         }
  30.        
  31.         public ActionResult Http403()
  32.         {
  33.             return View("Error", new ErrorModel() { ErrorTitle = "Forbidden" });
  34.         }
  35.     }
  36. }
End of Code Snippet

So now were handling our errors and throwing them out to the Error View (in Views/Shared!). However, were now using a strongly typed view and passing a custom class into it. This custom class contains details of our error!


ErrorModel.cs (Our custom error class!)
Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5.  
  6. namespace MVCEmailExample.Models
  7. {
  8.     public class ErrorModel
  9.     {
  10.         public string ErrorTitle { get; set; }
  11.         public Exception ExceptionDetail { get; set; }
  12.     }
  13. }
End of Code Snippet

Very simple! All it does it holds information really.
Now lets take a look at our error view...


Error View (This will already exist with a new MVC3 application) P.s. I'm using Razor syntax!
Code Snippet
  1. @using MVCEmailExample.Models
  2. @model ErrorModel
  3. @{
  4.     Layout = "~/Views/Shared/_Layout.cshtml";
  5. }
  6.  
  7.  
  8. <h2>@Model.ErrorTitle</h2>
  9.  
  10. Sorry, an error occurred while processing your request.
  11.  
  12. <br />
  13.  
  14. @if (Model.ExceptionDetail != null)
  15. {
  16.       @Model.ExceptionDetail.Message
  17. }
End of Code Snippet

This is our strongly typed view (Hint: @model ErrorModel). We simply extract the error info here and display it in a very (unstylish) form!

This sample below is for a sample email application. This uses the code described above. Just hit the button without entering any information and (assuming you don't have local mail server) u'll see some errors appearing!

MVC3 Razor - Global Error Handling

6 comments:

rogier21 said...

Works like a charm! Nice going, decent piece of work!

KoderWoman said...

Works perfectly.
thanks for the tutorial.
i have one problem with this code.
i'm trying to provide a link in the error view, which will take the user to the home page.
but every time i click on the link it takes me to the same error module again and i end up back in the error view.
i cant seem to navigate away from this error page with the help of a link.i tried different ways of doing this. but so far no luck.
any comments will be appreciated.

Bryan Ray said...

Should probably use the System.Web.Mvc.HandleErrorInfo rather than creating a new custom view model (ErrorModel).

Anonymous said...

Not working. When you put http://domain.com/http://domain.com then all path are illegal and routes are invalid.

Chaz said...

Nice! Works great. Didn't know it was this easy!

Anonymous said...

Appreciated.
Very well done!