ASP.Net MVC Custom Error Pages

I have recently spent quite a bit of time getting my ASP.Net MVC application to display errors to the user the way I want and this post outlines the steps I took to get there.  This includes general application errors, page not found errors and stupid user errors.  A basic understanding of ASP.Net and the MVC framework will be required to follow this post.  For reference my environment looks like this:

  • Windows Server 2008
  • IIS7 using the Integrated Pipeline
  • Visual Studio 2008
  • ASP.Net MVC Beta

If you have a different setup the details provided here might not work exactly as described, especially if you are running a lesser version of IIS like 5 or 6.  Upgrade, seriously.

CustomErrors Web.config Settings

Remember, ASP.Net MVC is just a new layer on top of the same old ASP.Net we know and love hate put up with.  The custom errors section of the Web.config was something we always had to set up for web forms projects so it seems like a good place to start for MVC.  Here's what my settings look like:

    <customErrors mode="On" defaultRedirect="~/Error/Unknown" />

Nice and simple.  All I'm saying here is that should anything crazy happen that my application doesn't deal with, redirect the user to my really bad error page.  The URL I provide is going to map to one on my MVC controllers which I'll get to shortly.

ASP.Net allows you to add rules for specific HTTP status codes but I'm going to ignore all that because I want these settings to be used as little as possible.  If you're interested in why there are already plenty of people out there talking about the issues with custom errors, e.g. this guy, that guy and another guy also.

ErrorController

In my application I have a controller dedicated to serving up application level errors.  Here's a simplified version:

    public class ErrorController : Controller

    {

        [AcceptVerbs(HttpVerbs.Get)]

        public ViewResult Unknown()

        {

            Response.StatusCode = (int)HttpStatusCode.InternalServerError;

            return View("Unknown");

        }

 

        [AcceptVerbs(HttpVerbs.Get)]

        public ViewResult NotFound(string path)

        {

            Response.StatusCode = (int)HttpStatusCode.NotFound;

            return View("NotFound", path);

        }

    }

The Unknown action is the target of my custom errors redirect in the Web.config.  The NotFound action is going to be used by the routing system which I will describe below.  Note that both actions set the HTTP status code to make sure our response is absolutely clear to the client.

There are intentionally few types of errors here because if the user is seeing these it probably means there is an improvement I can make to the application.  The views that these actions render are very generic and don't have enough context to be very useful to the user.  They may as well just say Oops!

IIS7 Detailed Errors

IIS7, trying to be helpful, likes to steal your custom errors pages and replace them with it's own:

IIS7 500 Internal Server Error

Annoyingly these errors will be swapped in whenever you set the HTTP status code to something interesting.  To crack down on this insubordination you need to open the Error Pages feature in IIS and click Edit Feature Settings on the right hand menu to get this dialog:

IIS7 Edit Error Pages Settings

You might be tempted to think that the Custom error pages option is what you should choose to display your custom error pages... but you'd be wrong.  These are IIS7 custom errors pages and something else entirely from what we are after.  Set the Detailed errors option to have IIS pass through whatever errors we serve up from ASP.Net.

Internet Explorer Friendly Errors

Internet Explorer, trying to be helpful like it's big brother IIS, wont display your error pages if they are smaller than 512 bytes.  By default IE has a setting called Show friendly HTTP error messages turned on and this causes your nice custom errors to be replaced with the standard canned reply:

IE Error

You can find the option to turn this off in IE under Tools, Internet Options, Advanced, Browsing, Show friendly HTTP error messages.  Unfortunately you can't set this for all of your users (or install Firefox for them) so just make sure that all of your error pages are larger than 512 bytes and you should be fine.

I find this one to be quite annoying during development when I only have stub pages in place.

Page Not Found

One of the most basic errors all sites will need to serve is the standard 404, page not found.  I could let the custom errors settings deal with this but as well as adding an extra redirect it also requires all requests to be mapped to ASP.Net in IIS, even those without a standard extension like .aspx.

Keeping things in code is my preference as it makes them easier to test and easier to move to new environments.  The MVC routing features let me deal with this nicely by adding a simple catch all route after all my standard application routes:

    routes.MapRoute("Default", "{controller}/{action}/{id}",

        new { controller = "Home", action = "Index", id = "" });

 

    routes.MapRoute("Catch All", "{*path}",

        new { controller = "Error", action = "NotFound" });

This maps any completely invalid routes to ErrorController.NotFound which we've already seen.  This happens in a single request, without any redirects, works with all URLs and doesn't require a special mapping in IIS.

Route Matched But Parameters Are Invalid

If you have controller actions that take parameters you need to take some additional steps with your routing.  An error that kept popping up for me early on was something like this:

The parameters dictionary does not contain a valid value of type 'System.Int32' for parameter 'id' which is required for method 'System.Web.Mvc.ActionResult Edit(Int32)' in 'Example.ProductController'. To make a parameter optional its type should either be a reference type or a Nullable type.

Ouch, what?!  To get rid of these errors you can add constraints to your routes using regular expressions, e.g.

    routes.MapRoute("Products - Edit", "Products/{id}/Edit",

        new { controller = "Products", action = "Edit" },

        new { id = @"\d{1,}" });

The last line specifies that the id parameter must be one or more digits an not contain any crazy stuff like letters or tilde symbols.  Should someone decide to be sneaky and type rubbish which the ID should go, they will pass right over this route and the catch all route will send them to the 404 page.

Errors In Controllers

Even once a request has successfully been mapped to an action there is plenty that could go wrong.  Here's a simple example where the requested product isn't found in the database:

    public ProductController : Controller

    {

        [AcceptVerbs(HttpVerbs.Get)]

        public ActionResult ViewDetails(int id)

        {

            var product = GetProductFromDB(id);

 

            if (product == null)

            {

                Response.StatusCode = (int)HttpStatusCode.NotFound;

                return View("NotFound");

            }

 

            return View("Details", product);

        }

One of the advantages MVC has over the web forms model is that you can delay choosing which view to render right until the last moment.  Here I am using a not found page tailored specifically for the products section of the site so that I can try and guess what the user was looking for and give them as much help as possible.

There are more advanced ways of catching errors in controller but they all basically have the same effect, rendering a different view.

Conclusion

That's all for today.  If you have any questions, comments or corrections I'd love to hear from you so leave a comment below.

27/01/2009 04:34 AM (UTC -08:00)

From ASP.Net Webforms to MVC

This post outlines some of my experiences moving from ASP.Net web forms to the new ASP.Net MVC framework coming from Microsoft.  There is plenty of talk about all the advantages of the new framework but it comes at a price.

Should You Use It?

The Microsofties are being quite clear that the new framework isn't for everyone and that the old one isn't going away.  I still use both and which one is better is way too subjective for anyone but you to answer.  Many of my projects will continue to use the old model, particularly if the project sounds like this:

  • Small project.
  • Few technical challenges.
  • Lots of content, little functionality.
  • Fast delivery is a priority.
  • Low budget is a priority.
  • Existing web forms based CMS.
  • Team not experienced with MVC.

This describes a lot of the quick marketing style sites that I do.  For some of my new projects I'm moving the the new MVC framework.  There is a large gray area in between but I will prefer the new framework for the following sorts of projects:

  • Large project.
  • Quality is a priority.
  • Difficult technical problems to solve.
  • Integration with external systems.
  • The code base is expected to live for several years.
  • The code will be reused or deployed in many places.
  • Experienced team or one willing to invest time learning.

Now before I make anyone angry, yes you can do a great job of large complex projects using the web forms model, I just hate doing it.

Learning Curve

If you're like me and you've never worked with a similar framework before (for example MonoRail or Rails) you're suddenly going to find yourself very unproductive as you relearn how to do all the things you've been doing for years in web forms.  Here are some of the things you will want to spend time on:

  • Learning the key architecture concepts in the framework.  You will need a solid understanding of how a URL makes its way through routing, a controller and finally the view.
  • Form input and validation is very different to what you are used to.  Spend time coming up with some patterns you like.
  • Postbacks and viewstate no longer exist as you knew them.  You can still do similar things but it will be a bit more manual than you are used to.
  • You need to understand more about how HTML and HTTP work.  Details such as the difference between GET and POST become important.
  • Many of the existing controls you use are not going to suit the MVC model.  As well as the usual display controls this is going to affect things like AJAX.

Expect to spend a couple of weeks before you are really comfortable working with the new framework.  The good news is that once you figure these things out you will be back to your old self again.

What Helps?

The new framework isn't really based on new ideas.  In fact it's really built on tried and tested concepts, even if they are new to the average Microsoft developer.  Here are some things from my past experience that help ease my transition:

  • Knowing ASP.Net and IIS very well.  Most of your knowledge about these technologies will still be useful.  Though you wont be using web forms you will still have the other ASP.Net features to work with such as session state and output caching.
  • Experience with unit testing.  One of the key goals on the new framework is testability and if you aren't doing it you're probably missing out on a lot of the advantages.  Unit testing can be tough to pick up in a hurry so any knowledge will help.
  • Familiarity with domain driven design.  I had already been working with patterns for a strong domain model distinctly separate from the user interface.  Most of my existing work in this area carried over very smoothly.
  • jQuery (or your JavaScript library of choice).  It will definitely help if you're used to getting your hands dirty with JavaScript as you will find yourself building many features from scratch, features web forms has out of the box.

Working With Beta Bits

The framework is still in beta so it's not done and there will be changes.  The core feels very solid and I haven't encountered any major bugs that have stopped me.  More importantly realise that there is not yet good support from third party libraries to fill in the holes so things feel very bare bones in a lot of cases.  One of the few resources out there is the MVC Contrib library.

Because the framework has already had 6 releases (5 previews, 1 beta) with breaking changes most of the information in blogs and forums is out of date and wont work exactly as described.  Finding information on how things are now can be tricky and keeping Reflector nearby is recommended.  The best information is still in Scott Gu's blog.

Freedom

I'm starting to settle in with MVC and it feels good.  My XHTML validates as strict and I haven't had to do anything remotely resembling a hack or fudge.  The framework is extensible in so many ways that web forms isn't so the few things I haven't liked I've been able to change.

Yes, it's worth it.

15/01/2009 02:20 AM (UTC -08:00)