custom software development to help your business take flight™

Handling Exceptions in ASP.NET MVC

Posted by Joe Wilson on Sunday, August 23, 2009 6:24 PM

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 Http Handler.  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.

Tags: ,
Categories: Technical


kick it on DotNetKicks.com shout it on DotNetShoutOut

Evolution of a View in ASP.NET MVC

Posted by Joe Wilson on Wednesday, August 12, 2009 1:42 PM

Many developers prefer working with ASP.NET MVC over Web Forms because they are more connected with the HTML, have better control over the rendered output, and can easily build their own HTML helpers to get consistent output.

But making the views can be a hassle when your building a CRUD app or something that has a lot of very similar views.  Let's look at some ways around that you can use today and in the future.

Past

When MVC first came out, most people were coding their inputs in views like this.  Just straight HTML with some helpers:

<% using (Html.BeginForm()) { %>
	<fieldset>
		<label for="Name">Name</label>
		<%= Html.TextBox("Name") %>
		<label for="Email">Email address</label>
		<%= Html.TextBox("Email") %>
		<label for="Phone">Phone</label>
		<%= Html.TextBox("Phone") %>
		<%= Html.SubmitButton() %>
	</fieldset>
<% } %>

Simple, but a little too much reliance on strings.  This made the code error prone, so developers made their own HTML Helpers to crank out consistent views with fewer strings by using lambda expressions:

<% using (Html.BeginForm()) { %>
	<fieldset>
		<label for="Name">Name</label>
		<%= Html.TextBoxFor(c => c.Name) %>
		<label for="Email">Email address</label>
		<%= Html.TextBoxFor(c => c.Email) %> 
		<label for="Phone">Phone</label>
		<%= Html.TextBoxFor(c => c.Phone) %> 
		<%= Html.SubmitButton() %>
	</fieldset>
<% } %>

That's better.  We get IntelliSense for the model's fields and we get the leave some of the strings behind.

Present

The next step was to get rid of some of those label statements and get those rolled into the output automatically.  They did this in Code Camp Server and you can see the step-by-step progression in Eric Hexter's blog:

<% using (Html.BeginForm()) { %>
	<fieldset>
		<%= Html.Input(c => c.Name) %>
		<%= Html.Input(c => c.Email) %> 
		<%= Html.Input(c => c.Phone) %> 
		<%= Html.SubmitButton() %>
	</fieldset>
<% } %>

Nice!  Now we're on the road to consistent output in all forms in our MVC app and we won't spend as much time typing up view code.  Eric even shows us he can name that view in one note:

<%= Html.InputForm() %>

Future

If you've been following the latest on ASP.NET MVC, you've seen the announcement for MVC 2 Preview 1 here, here, and here.  Microsoft is going the same direction as these open-source extensions and making it easier to get a consistent view rendered:

<% using (Html.BeginForm()) { %>
	<fieldset>
		<%= Html.LabelFor(c => c.Name) %>
		<%= Html.EditorFor(c => c.Name) %>
		<%= Html.LabelFor(c => c.Email) %>
		<%= Html.EditorFor(c => c.Email) %> 
		<%= Html.LabelFor(c => c.Phone) %>
		<%= Html.EditorFor(c => c.Phone) %> 
		<%= Html.SubmitButton() %>
	</fieldset>
<% } %>

MVC 2 also has an almost one-liner form that you define with templates, one of the new features:

<% using (Html.BeginForm()) { %>
	<fieldset>
		<%= Html.EditorFor(c => c) %>
		<%= Html.SubmitButton() %>
	</fieldset>
<% } %>

So we've seen views getting simpler and smaller and even one-line forms.  Can it get any smaller than a one-line form?  What if there was no view?  Can you have a view with zero lines of code?

Kind of.  Phil Haack took the MVC 2 Preview 1 code out for a spin to try what he calls "default templated views".  The idea is that your controller gets a model and displays a virtual view by using templates from your web project's "~\Views\Shared" folder.  You can make as many templates as you need for different purposes.

This is ideal for CRUD apps or places in your app where the view is the same except for the model data it renders.  You don't need a physical file for each view other than the template itself.  The controller just renders a view that is one of the shared views/templates.

I like this approach because you can crank out code more quickly for the easy stuff, but if you have a view that needs something more complex, you still have the normal HTML inputs and helpers to fall back on.  I hope Microsoft gets this idea fully flushed out and it ends up in the final MVC 2 release.  It would make developers more productive on repetitive views and would have wider acceptance if it was in the MVC base-class libraries.

Tags: ,
Categories: Technical


kick it on DotNetKicks.com shout it on DotNetShoutOut

Image Details