Update – for folks who learn best visually, I’ve posted a follow-up screencast of the demo steps discussed below, as a DevNuggets video. You can view the video here.
If you’re just getting started with ASP.NET MVC, you may have heard of something called action filters, but haven’t had the chance to use them yet. Action filters provide a convenient mechanism for attaching code to your controllers and/or action methods that implements what are referred to as cross-cutting concerns, that is, functionality that isn’t specific to one particular action method, but rather is something you’d want to re-use across multiple actions.
An action filter is a .NET class that inherits from FilterAttribute or one of its subclasses, usually ActionFilterAttribute, which adds the OnActionExecuting, OnActionExecuted, OnResultExecuting, and OnResultExecuted methods, providing hooks for code to be executed both before and after the action and result are processed.
Because action filters are subclasses of the System.Attribute class (via either FilterAttribute or one of its subclasses), they can be applied to your controllers and action methods using the standard .NET metadata attribute syntax:
1: [MyNamedAttribute(MyParam = MyValue)]
2: public ActionResult MyActionMethod()
4: // do stuff
1: <MyNamedAttribute(MyParam:=MyValue)> _
2: Public Function MyActionMethod() As ActionResult
3: ' do stuff
4: End Function
This makes action filters an easy way to add frequently-used functionality to your controllers and action methods, without intruding into the controller code, and without unnecessary repetition.
To be clear, action filters aren’t new to MVC 3, but there’s a new way to apply them in MVC 3 that I’ll discuss later on in this post.
ASP.NET MVC provides several action filters out of the box:
I often find that the best way for me to learn something new is by actually implementing it, so to that end, I’m going to walk through the process of handling a specific error using the HandleError action filter, and in the process explain a few more things about how these filters work.
First, let’s create a new ASP.NET MVC 3 Web Application (if you don’t have ASP.NET MVC 3 installed, you can grab it quickly and painlessly using the Web Platform Installer):
We’ll go with the Internet Application template, using the Razor View engine. Visual Studio helpfully opens up our HomeController for us when the project is loaded:
1: namespace HandleErrorTut.Controllers
3: public class HomeController : Controller
5: public ActionResult Index()
7: ViewModel.Message = "Welcome to ASP.NET MVC!";
9: return View();
12: public ActionResult About()
14: return View();
Nothing in there about handling errors, though, right? Yes, and no. Thanks to a new feature of ASP.NET MVC 3 called Global Filters, our application is already wired up to use the HandleErrorAttribute. How? Simple. In global.asax.cs, you’ll find the following code:
1: public static void RegisterGlobalFilters(GlobalFilterCollection filters)
3: filters.Add(new HandleErrorAttribute());
6: protected void Application_Start()
By adding the HandleErrorAttribute to the GlobalFilters.Filters collection, it will be applied to every action in our application, and any exception not handled by our code will cause it to be invoked. By default, it will simply return a View template by the name of Error, which conveniently has already been placed in Views > Shared for us:
So what does this look like? Let’s find out. Add code to the About action to cause an exception, as shown below:
1: public ActionResult About()
3: throw new DivideByZeroException();
4: return View();
Then run the application using Ctrl+F5, and click the About link in the upper-right corner of the page. Whoops! That doesn’t look much like a View template, does it?
Why are we getting the yellow screen of death? Because by default, CustomErrors is set to RemoteOnly, meaning that we get to see the full details of any errors when running locally. The HandleErrors filter only gets invoked when CustomErrors is enabled. To see the HandleErrors filter in action when running locally, we need to add the following line to web.config, in the system.web section:
1: <customErrors mode="On"/>
Now run the application again, and click About. You should see the following screen:
Now HandleError has been invoked and is returning the default Error View template. In the process, HandleError also marks the exception as being handled, thus avoiding the dreaded yellow screen of death.
Now let’s assume that the generic error view is fine for most scenarios, but we want a more specific error view when we try to divide by zero. We can do this by adding the HandleError attribute to our controller or action, which will override the global version of the attribute.
First, though, let’s create a new View template. Right-click the Views > Shared folder and select Add > View, specifying the name as DivByZero, and strongly typing the view to System.Web.Mvc.HandleErrorInfo, which is passed by the HandleError filter to the view being invoked:
Using a strongly-typed view simplifies our view code significantly by binding the view to the HandleErrorInfo instance passed to the view, which can then be accessed via the @Model.propertyname syntax, as shown below:
1: @model System.Web.Mvc.HandleErrorInfo
4: View.Title = "DivByZero";
5: Layout = "~/Views/Shared/_Layout.cshtml";
11: Controller: @Model.ControllerName
14: Action: @Model.ActionName
17: Message: @Model.Exception.Message
20: Stack Trace: @Model.Exception.StackTrace
Next, go back to HomeController, and add the HandleError attribute to the About action method, specifying that it should return the DivByZero View template:
2: public ActionResult About()
3: // remaining code omitted
Now, if you run the application and click the About link, you’ll see the following:
But we now have a problem…the filter attached to the About action method will return the DivByZero View template regardless of which exception occurs. Thankfully, this is easy to fix by adding the ExceptionType parameter to the HandleError attribute:
1: [HandleError(View="DivByZero", ExceptionType = typeof(DivideByZeroException))]
You can get even more control over how and when your filters are invoked by passing in the Order parameter to the attribute, or even creating your own custom filters. For example, you could create a custom filter that inherits from FilterAttribute, and implements IExceptionFilter, then implement IExceptionFilter’s OnException method to provide your own custom error handling, such as logging the error information, or performing a redirect. This is left as an exercise for the reader (or perhaps, for a future blog post!).
In this post, I’ve explained what action filters are, and some of the things they can do for you, and demonstrated, through the use of the HandleError filter, how you can customize their behavior. I’ve also shown how you can apply these filters across all of your controllers and actions through the use of the new global action filters feature of ASP.NET MVC 3. I also encourage you to read through the MSDN docs on Filtering in ASP.NET MVC for additional information.
I hope you’ve found this information useful, and welcome your feedback via the comments or email.
Nice post - specifically, I like the strongly-typed model binding to the System.Web.Mvc.HandleErrorInfo in the view. Never even thought about doing that; however, I do see that the default Error view does the same. Oh how we can learn just by discovering and analyzing the obvious (what's already there). Thanks...
Glad you like the post...and yes, there's lots of stuff that can be gleaned simply by example from what's already there. Although I'm a longtime Web Forms developer, I have to say that learning by example is an area that I find MVC to be superior.
Thanks for your comment!
You've been kicked (a good thing) - Trackback from DotNetKicks.com
Thank you for submitting this cool story - Trackback from progg.ru
I've been searching for a solution online for a while now, on how to properly return a 404 error, but without the ugly default looking 404 page. I could always throw a new HttpException(404, "Resource not found"), but that oddly returns a 500 error.
Can anyone point me in the right direction?
There are several approaches you could take, from the simple, using CustomErrors:
<error statusCode="404" redirect="~/Error/NotFound"/>
You could also use a combination of customErrors and HandleError, as described here:
If you're seeing a 500 HTTP status code and you're using HandleError, that's to be expected, since HandleError by default sets the status code to 500. If you want the status code to be 404, you may want to look at using HttpNotFoundResult, as described here:
That gives you a very clean way to return a 404.
Keep in mind that depending on how your application is configured, IIS may be responding to the exception as well, so be sure you understand which part of the platform (IIS or ASP.NET) is responding to the exception you're throwing.
Hope that helps!
While preparing to record a video walkthrough of the action filters tutorial I recently published , I
I’ve published a new DevNugget screencast on Channel 9, and linked below. In it, I demonstrate the use
Hey, great post - but for some reason, this one particular project I have doesn't work. I've tried following the steps as you've outlined, but when customErrors is on - it can't find the view. Frustrating .. since it works for all other projects, just not this particular one. Any ideas?
When you say "it can't find the view," what are the symptoms you're seeing? Are you getting a yellow screen of death indicating that the view is missing?