Filters provide a simple and elegant way to implement cross-cutting concerns in your ASP.NET MVC application. Filter achieve this by injecting code into the request processing. Examples of cross-cutting concerns are logging and authorization.
Five Types of Filter
ASP.NET MVC has five types of filter built-in which allow you to implement additional logic during the request processing.
Filter Type | Interface | Default Implementation | Description |
Action | IActionFilter | ActionFilterAttribute | Runs before and after the action method |
Authentication | IAuthenticationFilter | None | Runs first, before any other filters or the action method and can run again after the authorization filters |
Authorization | IAuthorizationFilter | AuthorizeAttribute | Runs second, after authentication, but before any other filters or the action method |
Exception | IExceptionFilter | HandleErrorAttribute | Runs only if another filter, the action method, or the action result throws an exception |
Result | IResultFilter | ActionFilterAttribute | Runs before and after the action method |
Before the framework invokes an action, it inspects the method definition to see if it has attributes. If the framework finds one, the methods of this attribute are invoked. As you can see, the ActionFilterAttribute is implemented by IActionFilter and IResultFilter. This class is abstract and enforces you to implement it. The AuthorizeAttribute and HandleErrorAttribute classes already contain useful features therefore you don’t have to derive from them.
Applying a Filter to an Action or Controller
It is possible to apply one or more filters to an action or controller. A filter which is often used is the Authorize attribute. If the Authorize attribute is applied to an action, only users which are logged in can invoke this action. If the attribute is applied to the controller, it applies to all actions.
Apply filter to action and controller
On the example above, I applied the Authorize attribute to the controller which means that every action of this controller can be invoked only by logged in users. Additionally, I applied the attribute to the DoAdminStuff and set the Roles to Admin which means that only users which have the admin role are allowed to invoke this action. This example also shows that some attributes can have a parameter to specify the action.
Applying Authorization Filters
In the last section, I already used one of the two attributes of the Authorize attribute. The available properties are:
Name | Description |
Users | Comma-separated usernames which are allowed |
Roles | Comma-separated role names |
If several roles are declared, then the user must have at least one of these roles.
Applying Authentication Filters
Authentication filters run before any other filter and can also run after an action has been executed but before the ActionResult is processed.
The IAuthenticationFilter Interface
The Authentication filter implements IAuthenticationFilter which implements two methods:
- void OnAuthentication(AuthenticationContext context);
- void OnAuthenticationChallenge(AuthenticationChallengeContext context);
The OnAuthenticationChallenge method is invoked whenever a request fails the authentication or authorization process. The parameter is an AuthenticationChallengeContext object which is derived from ControllerContext. The AuthenticationChallengeContext class has two useful properties:
Name | Description |
ActionDescriptor | Returns an ActionDescriptor that describes the action method to which the filter has been applied |
Result | Sets an ActionResult that expresses the result of the authentication challenge |
Applying Exception Filters
Exception filters are only executed if an unhandled exception has been thrown when invoking an action. An exception can be thrown by:
- Another filter
- The action itself
- During the action result execution
Microsoft provides the HandleErrorAttribute for exception handling. This class implements the IExceptionFilter interface. The HandleErrorAttribute class has three properties:
Name | Type | Description |
ExceptionType | Type | The exception type handled by this filter. It will also handle exception types that inherit from the specified value but will ignore all others. The default value is System.Exception, which means that, by default, it will handle all standard exceptions. |
View | string | The name of the view template that this filter renders. If you do not specify a value, it takes a default value of Error, so by default, it renders /Views// Error.cshtml or /Views/Shared/Error.cshtml. </td> </tr> Master | string | The name of the layout used when rendering this filter’s view. If you do not specify a value, the view uses its default layout page. </table> </div> As summary: When an unhandled exception of the type specified by ExceptionType is encountered, this filter will render the view specified by the View property If an exception occurs in your development environment, you will get the famous yellow screen of death. To prevent this from happening, you have to set the customsError attribute to the web.config file. CustommError in web.config The customError attribute redirects the request to the location specified at the defaultRedirect attribute. The default value for the mode attribute is RemoteOnly, which means that all local requests see the yellow screen of death. ### Applying the HandleError attribute Applying the HandleError attribute to an action enables you to prevent the application from crashing and also enables you to provide some useful error message and help to the user. You can specify an ExceptionType and then send the request to a specific view and provide some information about what went wrong. Apply HandleError on an action On the screenshot above, you can see two different versions of how to use the HandleError attribute. The first line with the attribute only would redirect to /Error/ErrorMessage because I defined this destination in the defaultRedirect attribute in the web.config. This page can only show a generic error message which might not help the user at all. Therefore you can also specify which exception might happen and then redirect the request to a specific view. In the example above, I redirect all ArgumentOutOfRangeException to the ErrorMessage view. This view can be either in the view folder of the controller or in the shared view folder. ### Displaying information with the HandleErrorInfo view model The HandleError attribute passes the HandleErrorInfo view model to your view. The properties of the HandleErrorInfo view model are: Name | Type | Description | ActionName | string | Returns the name of the action method that generated the exception | ControllerName | string | Returns the name of the controller that generated the exception | Exception | Exception | Returns the exception |
In the ErrorMessage view, I can use this view model to give the user a proper error description and provide some help to solve the problem. Helpful error message in the view I know that my error message is not the best and I bet that you can come up with a better one. I also provided a link, so the user can easily return to the page where he came from. ## Applying Action Filters Action filters can be used for anything. I use them for performance checks to find problems with the execution of actions. ### Using the IActionFilter interface The IActionFilter interface has two methods: * void OnActionExecuting(ActionExecutingContext filterContext) * void OnActionExecuted(ActionExecutedContext filterContext) The OnActionExecuting method is called before an action method is invoked. The parameter, ActionExecutingContext has two properties: Name | Type | Description | ActionDescriptor | ActionDescriptor | Provides details of the action method | Result | ActionResult | The result for the action method. A filter can cancel the request by setting this property to a non-null value |
The OnActionExecuted method is called after the action is completed and has 5 properties: Name | Type | Description | ActionDescriptor | ActionDescriptor | Provides details of the action method | Canceled | bool | Returns true if the action has been canceled by another filter | Exception | Exception | Returns an exception thrown by another filter or by the action method | ExceptionHandled | bool | Returns true if the exception has been handled | Result | ActionResult | The result for the action method; a filter can cancel the request by setting this property to a non-null value |
### Implementing an Action Filter I created a new class called MyActionAttribute which derives from FilterAttribute and IActionFilter. In the OnActionExecuting method, I check if the browser is Chrome. If the request comes from Chrome, I return an HttpUnauthorizedResult. Again, this is not the most useful implementation but I think you get what you can do with this method. For now, I don’t implement anything in the OnActionExecuted method. This is no problem. Just be careful because Visual Studio throws a NotImplementedException if you let Visual Studio create the method. The action filter class implementation To use my new filter, I apply it to the CheckFilter action. Note that you don’t have to write MyActionAttribute when applying it as attribute. MyAction is enough. Applying the action filter attribute to an action When you call this action from Chrome, you can see that the access is denied for the user. Result of the action filter Unauthorized result in Chrome ### Implementing the OnActionExecutedMethod As I already mentioned, I often use action filter to measure the execution time of an action. To do that, I start a Stopwatch in the OnActionExecuting method and stop it in the OnActionExecuted method. Afterwards, I print the execution time. Implementation of the PerformanceAction attribute To see different results, I added Thread.Sleep with a random number, so that the execution will take somewhere between some milliseconds and two seconds. Applying the PerformanceAction attribute The print of the execution time is printed before the content of the action because the action filter is executed before the result of the action is processed. Result of performance test with the action filter ## Applying Result Filters The IResultFilter which is implemented by result filters has two methods: * void OnResultExecuting(ResultExecutingContext filterContext) * void OnResultExecuted(ResultExecutedContext filterContext) The OnResultExecuting method is called after an action has returned an action result but before this result is executed. The OnResultExecuted method is called after the action result is executed. The ResultExecutingContext and ResultExecutedContext parameter has the same properties as the action filter parameter and also have the same effects. ### Implementing a Result Filter To demonstrate how the result filter works, I repeat the performance test but this time I measure the time between the start and the end of the execution of the ActionResult. To do that, I implement the IResultFilter and work with the OnResultExecuting and OnResultExecuted method. Implementation of the ResultAction attribute The next step is applying the filter to an action and call the action to see the result. Applying the result filter attribute to an action When you look at the output, you see a difference in the result of the action filter output. The print of the measured time is beneath the output of the action. This behavior is caused by the fact that the result filter is processed after the ActionResult. Therefore the result is printed and then afterward the measured time is added. Result of performance test with the action filter ## Filtering without Filter In the previous examples, I showed how to create implementations of filters by implementing the desired interface. The Controller class already implements the IAuthenticationFilter, IAuthorizationFilter, IActionFilter, IResultFilter, and IExceptionFilter interfaces. It also provides empty virtual implementations for each of the methods. Knowing this, you can override the desired methods directly in the controller and achieve the same outcome as the filter classes did. Applying filtering in a controller without filter attributes If you call the Index action of the FilteringWithoutFilter controller, you will see the same result as previously with the attributes. Result of performance test with the action and result filter I have to admit that I am not a big fan of this approach. The big advantage of ASP.NET MVC is Separation of Concerns. When using filtering within a controller, you lose this advantage. The only time when it might make sense to use filtering directly in a controller is when this class is a base class for other controllers. I recommend using the attribute approach. ### Simply combining Action and Result Filter If you look closely, you can see that I combine the action filter method OnActionExecuting and the result filter method OnResultExecuted. You can do this yourself by implementing the IActionFilter and IResultFilter interface or by deriving from ActionFilterAttribute which implements both interfaces. The advantage of deriving from ActionFilterAttribute is that you don’t have empty methods which are not implemented. ## Applying Global Filters If you want to apply filter to all your actions, you can use global filters. If you are using the MVC template, the framework already creates the FilterConfig.cs file under the App_Start folder. Since I didn’t use the template, I create the file myself. This class has one static method, RegisterGlobalFilters. The FilterConfig.cs is pretty similar to the RouteConfig.cs. To add a new filter, add it to the filters collection with add. Register a filter globally in FilterConfig.cs Note that the namespace is Filter, not Filter.App_Start. The HandleErrorAttribute will always be defined as global filter in ASP.NET MVC. It is not mandatory to define the HandleErrorAttribute as global filter but it is the default exception handling and will render the /Views/Shared/Error.cshtml view if an exception happens. To register a filter a, you have to pass an instance of the filter class. The name contains the Attribute prefix. If you use the class as attribute, you can omit the Attribute prefix. Additionally, you have to add the RegisterGlobalFilters call in the Global.asx file. Content of the Global.asx ## Conclusion In this post, I showed all five types of built-in filters in the ASP.NET MVC framework and discussed how to use them to address cross-cutting concerns effectively. These filters help you to extend the logic of your controller and actions while a request is processed. For more details about how built-in filters work, I highly recommend the books Pro ASP.NET MVC 5 and Pro ASP.NET MVC 5 Plattform. You can find the source code on GitHub. | |
Comments powered by Disqus.