Welcome to the CodeSmith Community!

Error Handling and CustomErrors and MVC3, oh my!

CodeSmith Community

A description has not yet been added to this group.

Error Handling and CustomErrors and MVC3, oh my!

  • Comments 26

So, what else is new in MVC 3?
MVC 3 now has a GlobalFilterCollection that is automatically populated with a HandleErrorAttribute. This default FilterAttribute brings with it a new way of handling errors in your web applications. In short, you can now handle errors inside of the MVC pipeline. 

What does that mean?
This gives you direct programmatic control over handling your 500 errors in the same way that ASP.NET and CustomErrors give you configurable control of handling your HTTP error codes.

How does that work out?
Think of it as a routing table specifically for your Exceptions, it's pretty sweet!

Global Filters

The new Global.asax file now has a RegisterGlobalFilters method that is used to add filters to the new GlobalFilterCollection, statically located at System.Web.Mvc.GlobalFilter.Filters. By default this method adds one filter, the HandleErrorAttribute.

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }

HandleErrorAttributes

The HandleErrorAttribute is pretty simple in concept: MVC has already adjusted us to using Filter attributes for our AcceptVerbs and RequiresAuthorization, now we are going to use them for (as the name implies) error handling, and we are going to do so on a (also as the name implies) global scale.

The HandleErrorAttribute has properties for ExceptionType, View, and Master. The ExceptionType allows you to specify what exception that attribute should handle. The View allows you to specify which error view (page) you want it to redirect to. Last but not least, the Master allows you to control which master page (or as Razor refers to them, Layout) you want to render with, even if that means overriding the default layout specified in the view itself.

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute
        {
            ExceptionType = typeof(DbException),
            // DbError.cshtml is a view in the Shared folder.
            View = "DbError",
            Order = 2
        });
        filters.Add(new HandleErrorAttribute());
    }

Error Views

All of your views still work like they did in the previous version of MVC (except of course that they can now use the Razor engine). However, a view that is used to render an error can not have a specified model! This is because they already have a model, and that is System.Web.Mvc.HandleErrorInfo

@model System.Web.Mvc.HandleErrorInfo
           
@{
    ViewBag.Title = "DbError";
}

<h2>A Database Error Has Occurred</h2>

@if (Model != null)
{
    <p>@Model.Exception.GetType().Name<br />
    thrown in @Model.ControllerName @Model.ActionName</p>
}

Errors Outside of the MVC Pipeline

The HandleErrorAttribute will only handle errors that happen inside of the MVC pipeline, better known as 500 errors. Errors outside of the MVC pipeline are still handled the way they have always been with ASP.NET. You turn on custom errors, specify error codes and paths to error pages, etc.

It is important to remember that these will happen for anything and everything outside of what the HandleErrorAttribute handles. Also, these will happen whenever an error is not handled with the HandleErrorAttribute from inside of the pipeline.

<system.web>
  <customErrors mode="On" defaultRedirect="~/error">
    <error statusCode="404" redirect="~/error/notfound"></error>
  </customErrors>

Sample Controllers

public class ExampleController : Controller
{
    public ActionResult Exception()
    {
        throw new ArgumentNullException();
    }
    public ActionResult Db()
    {
        // Inherits from DbException
        throw new MyDbException();
    }
}

public class ErrorController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
    public ActionResult NotFound()
    {
        return View();
    }
}

Putting It All Together

If we have all the code above included in our MVC 3 project, here is how the following scenario's will play out:

  1. A controller action throws an Exception.
    • You will remain on the current page and the global HandleErrorAttributes will render the Error view.
  2. A controller action throws any type of DbException.
    • You will remain on the current page and the global HandleErrorAttributes will render the DbError view.
  3. Go to a non-existent page.
    • You will be redirect to the Error controller's NotFound action by the CustomErrors configuration for HTTP StatusCode 404.

But don't take my word for it, download the sample project and try it yourself.

Three Important Lessons Learned

For the most part this is all pretty straight forward, but there are a few gotcha's that you should remember to watch out for:

1) Error views have models, but they must be of type HandleErrorInfo.

It is confusing at first to think that you can't control the M in an MVC page, but it's for a good reason. Errors can come from any action in any controller, and no redirect is taking place, so the view engine is just going to render an error view with the only data it has: The HandleError Info model. Do not try to set the model on your error page or pass in a different object through a controller action, it will just blow up and cause a second exception after your first exception!

2) When the HandleErrorAttribute renders a page, it does not pass through a controller or an action.

The standard web.config CustomErrors literally redirect a failed request to a new page. The HandleErrorAttribute is just rendering a view, so it is not going to pass through a controller action. But that's ok! Remember, a controller's job is to get the model for a view, but an error already has a model ready to give to the view, thus there is no need to pass through a controller.

That being said, the normal ASP.NET custom errors still need to route through controllers. So if you want to share an error page between the HandleErrorAttribute and your web.config redirects, you will need to create a controller action and route for it. But then when you render that error view from your action, you can only use the HandlerErrorInfo model or ViewData dictionary to populate your page.

3) The HandleErrorAttribute obeys if CustomErrors are on or off, but does not use their redirects.

If you turn CustomErrors off in your web.config, the HandleErrorAttributes will stop handling errors. However, that is the only configuration these two mechanisms share. The HandleErrorAttribute will not use your defaultRedirect property, or any other errors registered with customer errors.

In Summary

The HandleErrorAttribute is for displaying 500 errors that were caused by exceptions inside of the MVC pipeline. The custom errors are for redirecting from error pages caused by other HTTP codes. 

Also, if you are going to be handling all these errors, why not report them too?

kick it on DotNetKicks.com

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • * Please enter your name
  • * Please enter a comment
  • Post
  • You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Thank you for submitting this cool story - Trackback from DotNetShoutout

  • Thanks for sharing!

  • This method returns an HTTP 301 followed by a 200.  You should really return a 404 for SEO purposes.

  • Hello Chris,

    What changes did you have to make for this behavior? Did you just remove the custom errors section in the web.config?

    Thanks

    -Blake Niemyjski

  • I've been trying to set up this error handling feature, but never managed to get it working. So i downloaded your sample to see what i was doing wrong, but guess what, your sample failed too. :S

    I keep getting this "HTTP 500 Internal Server Error" page in my IE instead of the custom error pages.

    Any ideas?

  • Hi again!

    I belive i finaly found what i was doing wrong. It seems that IE does not display the error page if the returned status code is 500 and the pages size does not exceed a certain ammout (512 bytes or so). After inserting some junk html comments into my error page it did finaly display. This sounds ridiculous, but... Well... Yeah...

    By the way used IE 8.0 for tests. Probably i would have found out sooner whats wrong if i used more browsers for testing.

    Being a junior developer does not allways mean learning exciting things. :S

    Many thanks for your article, it helped me a lot.

    Regards,

    Gabe

  • Thanks for your comment and your solution. I'll forward this onto Tom. Good luck with your projects and keep up the research and passion to develop software!

  • Hey Tom,

    Great article!

    I implemented it in my project exaclly. But I'm having an issue, for http exceptions it works as expected however for application exceptions, I see with the debugger that the code is excuted as it is supposed to but the error page is not displayed.

    Any ideas? I can send you the code if needed.

    Thx

    A

  • What about authorization errors?  If you use the [Authorize()] attribute, aren't those checks handled within the pipeline?  I'd think you could catch 401 errors as well using this method, but it doesn't appear to be working.

    Essentially, how do you capture authorization errors within MVC 3 and redirect to a custom Access Denied page?  I've tried using an error controller to display a message as well as using <customErrors mode="On" defaultRedirect="error.html" /> as a test to see if I could redirect at all, but it keeps returning the default IIS 401 or 401.2 error code.

  • Hello,

    I'd recommend proposing these questions in the Official MVC3 forums located here (forums.asp.net/1146.aspx).

    Thanks

    -Blake Niemyjski

  • Unfortunately,  another example that just doesn't work. 500 errors aren't trapped, regardless of the size of the page (>512 as posted above). 404 works fine.

  • I'm having the same issues as Arnold. I've downloaded the sample application and only the 404 exceptions get redirected. The 500 errors just give me the standard IIS exception page.  I'm using VS2010. Any thoughts?

  • Hello,

    I'd recommend proposing these questions in the Official MVC3 forums located here (forums.asp.net/1146.aspx).

    Thanks

    -Blake Niemyjski

  • i have downloaded the code and executing, but always itsshowing "The website cannot display the page" because of redirecting to the "http://localhost:50031/example/exception"

Page 1 of 2 (26 items) 12
Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • * Please enter your name
  • * Please enter a comment
  • Post