Here is a quick run down of the various approaches for handling exceptions.  I use a combination of these that I'll show at the end.

Try-Catch everywhere

You've seen this code before, right?

public void DoSomething()
{
    try
    {
        var x = 1;
    } 
    catch (Exception ex) 
    {
        ShowExceptionToUser(ex.Message);
    }
}

The approach of putting try-catch block around every ounce of code seems like a good idea intuitively.  Sure, you're showing the user an ugly error message, but it's better than crashing the server.

But what is the user supposed to do with the information you show them?  How are they supposed to fix your null reference exception?

The other knock on this approach is there is so much ceremony code!

Generic error page

This approach goes the other way with a generic error page displayed to the user for any unhandled error.  It's easy to implement in the Web.config file, and the user never sees a null reference exception or stack trace.

Here's what I'm using:

<customErrors mode="RemoteOnly" defaultRedirect="~/Error">
  <error statusCode="403" redirect="~/Error" />
  <error statusCode="404" redirect="~/Error" />
</customErrors>

There are two problems with the generic error page approach.  First, the user has no information about what happened.  Maybe it's something they could have resolved?  Maybe they should try again later?  Just saying "Pardon our mess" doesn't give them any details.

Second, it doesn't give you any details either.  The next time you talk to the user, they will tell you they got an error in your app.  "Oh, what happened?" you'll ask.

"Uh, it was on that screen with the customer info.  I forgot what I was doing, but it just showed the error screen, so I rebooted and went home for the day."  Not much to go on.  Something about the customer screen?  This will be a fun one to try to reproduce!

Just log it

The last approach is to log all errors.  You collect as much detail as you need.  The date and time the exception was thrown, which user/IP address, which server they were on, the stack trace, etc.

Error logging can be extensive and can be stored in rolling text files or database tables.  I prefer using the database so I don't need to grant write permissions to the ASP.NET process running that web app.

Here's an Application_Error event from a couple projects back:

protected void Application_Error(object sender, EventArgs e) 
{
    var ctx = HttpContext.Current;
    var ex = ctx.Server.GetLastError();
    if (ex != null) 
    {
        _logService.LogExceptionToDatabase(ex);
    }
}

I've done lots of applications like this, where the Application_Error event in Global.asax calls into a logger like log4net or Enterprise Library to store exception details.  It works great.  You get the details you need to fix the bug.

But what about the user?  What are the users looking at when this exception is thrown?

#3 Combo Platter

Today, I'm using bits and pieces of all of these.

I show the user a generic error page for all unhandled exceptions.  It may not have much info, but something weird just happened in the app and I wasn't expecting it.  I need more information about what happened so I can decide what needs to be done.

That's why I'm also using ELMAH to log all unhandled exceptions.  The exception details get stored in my database and I even get a fancy exception viewer since ELMAH works as an HttpHandler.  My favorite ELMAH feature is you can usually see the yellow screen you would have seen if running locally on your box.

Finally, sometimes there are handled exceptions.  This is where an exception occurred before in the code (database is down, file not found, etc.), it might occur again, and I've got some thoughts about what to do next time it happens.

In my view, the only place you need try-catch blocks is when you are worried that an exception might happen and you want to give the user some information, give the user instructions, or silently fix the problem yourself.  If I can't do one of these, I just show the "Oops" page try to come up with a plan later.

For instance, if the database is down, I'd tell the user to try again later, but I couldn't save their record right now.  If the user is trying to upload a file and it's not there, they can try to correct the file upload input box and resubmit.

How to wire this up in ASP.NET MVC

Set up ELMAH

First, download ELMAH binaries, add a reference to the ELMAH assembly in your web project, and follow the instructions to get your Web.config set up correctly.

Now test out ELMAH.  You should be able to browse to elmah.axd and see the list of exceptions.  Try throwing one in your code somewhere and see if ELMAH logs it.

You can make the elmah.axd page secure, log to a database, etc.

Set up the Generic Error Page

Once ELMAH is logging, wire up your Web.config to show the generic error page:

<customErrors mode="RemoteOnly" defaultRedirect="~/Error">
  <error statusCode="403" redirect="~/Error" />
  <error statusCode="404" redirect="~/Error" />
</customErrors>

I'm using an Error controller with a single Index action so I can send the user to the root and to the Error route to show the error page.  Here's the controller:

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

It's calling a view also called "Error" that is in ~\Views\Shared\Error.aspx.  Make your version of that page.  Remember, this is the page for unhandled exceptions.

Next, you need to register this route.  I've moved my route registration out of my Global.asax, but if you have a default ASP.NET MVC install, look for your routing registrations in Application\_Start:

routes.MapRoute("UnhandledExceptions", "Error", new { controller = "Error", action = "Index" });

This is just setting up some defaults in the route to simplify redirects to the generic error page.  At this point, you should be able to browse to any controller with a thrown exception in the action and see your generic error page displayed.

Set up the ElmahHandleError attribute

There's one step left!  You've now got two ways of dealing with unhandled exceptions.  We need to consolidate these so the generic page is show AND the ELMAH logging is called.

You know how you have to add the HandleError attribute to your controller classes to get the generic error page to appear?  This attribute doesn't call ELMAH.  This is by design, since the HandleError attribute is, well, handling the error.  ELMAH is for unhandled errors and doesn't log otherwise.

But you can have it both ways and use ELMAH as your handled exception logging tool as well.  Follow these steps to create an ElmahHandleError attribute and replace the HandleError attribute on your controllers with this new attribute.

That's it!  You should be all set with an exception handling strategy.