Action Filter Attributes are a useful extensibility
capability in ASP.NET MVC that was first added with the "Preview 2"
release. These enable you to inject code interceptors into the request of a
MVC controller that can execute before and after a Controller or its Action
methods execute. This enables some nice encapsulation scenarios where you can
easily package-up and re-use functionality in a clean declarative way.
Below is an example of a super simple "ScottGuLog"
filter that I could use to log details about exceptions raised during the
execution of a request. Implementing a custom filter class is easy - just
subclass the "ActionFilterAttribute" type and override the
appropriate methods to run code before or after an Action method on the
Controller is invoked, and/or before or after an ActionResult is processed into
a response.
Figure 1
Using a filter within a ASP.NET MVC Controller
is easy - just declare it as an attribute on an Action method, or alternatively
on the Controller class itself (in which case it will apply to all Action
methods within the Controller):
Figure 2
Above you can see an example of two filters being
applied. I've indicated that I want my "ScottGuLog" to be
applied to the "About" action method, and that I want the
"HandleError" filter to be applied to all Action methods on the
HomeController.
Previous preview releases of ASP.NET MVC enabled this filter
extensibility, but didn't ship with pre-built filters. ASP.NET Preview 4
now includes several useful filters for handling output caching, error handling
and security scenarios.
OutputCache Filter
The [OutputCache] filter provides an easy way to integrate
ASP.NET MVC with the output caching features of ASP.NET (with ASP.NET MVC
Preview 3 you had to write code to achieve this).
To try this out, modify the "Message" value set
within the "Index" action method of the HomeController (created by
the VS ASP.NET MVC project template) to display the current time:
Figure 3
When you run your application you'll see that
a timestamp updates each time you refresh the page:
Figure 4
We can enable output caching for this URL by
adding the [OutputCache] attribute to the our Action method. We'll
configure it to cache the response for a 10 second duration using the declaration
below:
Figure 5
Now when you hit refresh on the page you'll
see that the timestamp only updates every 10 seconds. This is because the
action method is only being called once every 10 seconds - all requests between
those time intervals are served out of the ASP.NET output cache (meaning no
code needs to run - which makes it super fast).
In addition to supporting time duration, the
OutputCache attribute also supports the standard ASP.NET output cache vary
options (vary by params, headers, content encoding, and custom logic).
For example, the sample below would save different cached versions of the page
depending on the value of an optional "PageIndex" QueryString
parameter, and automatically render the correct version depending on the incoming
URL's querystring value:
Figure 6
You can also integrate with the ASP.NET Database Cache
Invalidation feature - which allows you to automatically invalidate the cache
when a database the URL depends on is modified (tip: the best way to-do this is
to setup a CacheProfile section in your web.config and then point to it in the
OutputCache attribute).
HandleError Filter
The [HandleError] filter provides a way to declaratively
indicate on a Controller or Action method that a friendly error response should
be displayed if an error occurs during the processing of a ASP.NET MVC
request.
To try this out, add a new "TestController" to a
project and implement an action method that raise an exception like below:
Figure 7
By default when you point your browser at this
URL, it will display a default ASP.NET error page to remote users (unless
you've gone in and configured a <customErrors> section in your web.config
file):
Figure 8
We can change the HTML error displayed to be a
more friendly end-user message by adding a [HandleError] attribute to either
our Controller or to an Action method on our Controller:
Figure 9
The HandleError filter will catch all exceptions (including
errors raised when processing View templates), and display a custom Error view
response when they occur. By default it attempts to resolve a View
template in your project called "Error" to generate the
response. You can place the "Error" view either in the same
directory as your other Controller specific views (for example: \Views\Test for
the TestController above), or within the \Views\Shared folder (it will look
first for a controller specific error view, and then if it doesn't find one it
will look in the shared folder - which contains views that are shared across
all controllers).
Visual Studio now automatically adds a default
"Error" view template for you inside the \Views\Shared folder when
you create new ASP.NET MVC Projects starting with Preview 4:
Figure 10
When we add a [HandleError] attribute to our TestController,
this will by default show remote users an html error page like below (note that
it picks up the master page template from the project so that the error message
is integrated into the site). You can obviously go in and customize the
Error view template to display whatever HTML and/or friendlier customer error
message you want - below is simply what you get out of the box:
Figure 11
To help developers, the default Error view template provided
by the new project template in Visual Studio is written to display additional
error stack trace information when you are browsing the application locally:
Figure 12
You can turn this off either by deleting the code from the
Error view template, or by setting <customErrors> to "off"
inside your web.config file.
By default the [HandleError] filter will catch and handle
all exceptions that get raised during the request. You can alternatively
specify specific exception types you are interested in catching, and specify
custom error views for them by specifying the "ExceptionType" and
"View" properties on [HandleError] attributes:
Figure 13
In the code above I'm choosing to display custom error views
for SqlExceptions and NullReferenceExceptions. All other exceptions will
then use the default "Error" view template.
Authorize Filter
The [Authorize] filter provides a way to declaratively
control security access on a Controller or Action method. It allows you
to indicate that a user must be logged in, and optionally require that they are
a specific user or in a specific security role in order to gain access.
The filter works with all types of authentication (including Windows as well as
Forms based authentication), and provides support for automatically redirecting
anonymous users to a login form as needed.
To try this out, add an [Authorize] filter to the
"About" action in the HomeController created by default with Visual
Studio:
Figure 14
Declaring an [Authorize] attribute like above indicates that
a user must be logged into the site in order for them to request the
"About" action. When non-logged-in users attempt to hit the
/Home/About URL, they will be blocked from gaining access. If the web
application is configured to use Windows based authentication, ASP.NET will
automatically authenticate the user using their Windows login identity, and if
successful allow them to proceed. If the web application is configured to
use Forms based authentication, the [Authorize] attribute will automatically
redirect the user to a login page in order to authenticate (after which they'll
have access):
Figure 15
The [Authorize] attribute optionally allows you to grant
access only to specific users and/or roles. For example, if I wanted to
limit access to the "About" action to just myself and Bill Gates I
could write:
Figure 16
Typically for all but trivial applications you don't want to
hard-code user names within your code. Instead you usually want to use a
higher-level concept like "roles" to define permissions, and then map
users into roles separately (for example: using active directory or a database
to store the mappings). The [Authorize] attribute makes it easy to
control access to Controllers and Actions using a "Roles" property:
Figure 17
The [Authorize] attribute does not have a dependency on any
specific user identity or role management mechanism. Instead it works
against the ASP.NET "User" object - which is extensible and allows
any identity system to be used.
AccountController Class
I mentioned above that the [Authorize] attribute can be used
with any authentication or user identity management system. You can write
or use any custom login UI and/or username/password management system you want
with it.
To help you get started, though, the ASP.NET MVC Project
Template in Visual Studio now includes a pre-built
"AccountController" and associated login views that implement a forms-authentication
membership system with support for logging in, logging out, registering new
users, and changing passwords. All of the views templates and UI can be
easily customized independent of the AccountController class or implementation:
Figure 18
The Site.master template also now includes UI at the
top-right that provides login/logout functionality. When using
forms-based authentication it will prompt you to login if you are not currently
authenticated:
Figure 19
And it displays a welcome message along with a logout link
if you are authenticated on the site:
Figure 20
Clicking the Login link above takes users to a Login screen
like below that they can use to authenticate:
Figure 21
New users can click the register link to create new
accounts:
Figure 22
Error handing and error display is also built-in:
Figure 23
The AccountController class that is added to new projects
uses the built-in ASP.NET Membership API to store and manage user credentials
(the Membership system uses a provider API allowing any back-end storage to be
plugged-in, and ASP.NET includes built-in providers for Active Directory and
SQL Server). If you don't want to use the built-in Membership system you
can keep the same AccountController action method signatures, View templates,
and Forms Authentication ticket logic, and just replace the user account logic
within the AccountController class. For the next ASP.NET MVC preview release
we are planning to encapsulate the interaction logic between the
AccountController and the user identity system behind an interface - which will
make it easier to plug-in your own user storage system (without having to
implement a full membership provider) as well as to easily unit test both it
and the AccountController.
Our hope is that this provides a nice way for people to
quickly get started, and enable them to have a working end to end security
system as soon as they create a new project.