Matthew Jones

One of the ways MVC allows us to fine-tune what an action does is via the use of Action Filter attributes. Action Filters are attributes which inherit from the ActionFilterAttribute class, and can execute either before or after a decorated action (or before/after every action in a controller) and modify how the action is handled.

In this post we'll explore creating a custom Action Filter by inheriting from ActionFilterAttribute.

What is an ActionFilter?

In ASP.NET, an "Action Filter" in the loose sense applies to any action attribute. These attributes can implement one or more of the following interfaces:

Note that if a given class implements more than one of those interfaces, the corresponding methods from the interfaces will be executed in the order listed above.

In order to make creating these attributes easier, .NET exposes the ActionFilterAttribute class which implements IActionFilter and IResultFilter. This class exposes four methods that you can override:

OnActionExecuting: Fires before the action is executed.

OnActionExecuted: Fires after the action is executed.

OnResultExecuting: Fires before the action result is executed.

OnResultExecuted: Fires after the action result is executed.

Each of these methods have different use cases. For example, if you wanted to modify the result of the action based on some additional data, the best place to do that would be OnResultExecuting.

You've actually already seen ActionFilter attributes in action if you've ever seen HandleErrorAttribute or AuthorizeAttribute, as both of those classes derive from the base ActionFilterAttribute class.

Let's build a simple, custom Action Filter to show these ideas in action.

Setting Up the Demo App

We'll be using a database schema that looks like this:

Each user can create one or more Reports in this schema. What we are going to build is an Action Filter Attribute that restricts access to these reports to only the User that created them. In order to show how this works more transparently, we are also going to allow the current user of the app to "impersonate" the database-stored users by placing a User ID into Session, then using that ID to see whether or not that user can access a given Report.

Confused? It's not too bad. Let's get started!

Selecting the Current User

Let's start with the Home/Index view model, controller actions, and view; these allow us to select the current user and store it in Session.

Building the Action Filter

We want our filter to check the Session["CurrentUserID"] value against the Report ID, and see if the current user created the specified report. If s/he did, we proceed as normal, and if not, we redirect back to Home/Index.

Notice the ActionExecutingContext parameter. That parameter is extremely important as it contains the information (such as route values, query string parameters, and controller data) that we need to access.

Now, for the first step, let's get the report ID from the route parameter "id":

Notice that we can access the Session through the HttpContext property of ActionExecutingContext.

Now the question becomes this: what goes inside that if clause? If they don't have a valid ID, we want to redirect back to Home/Index with a flash message, but we cannot call RedirectToAction from an Action Filter. Instead, we set the Result of the ActionExecutingContext to a new RedirectToRouteResult, like so:

There's still one more scenario we need to handle: what if the user that's trying to access this report isn't the person that created it? In this scenario, just like the invalid-user-ID one, we want to redirect to Home/Index with a flash message.

Now, when we run the app, the system will look to see which user we currently are impersonating, and if we try to access a report that that user didn't create, we will get kicked back out to the Home/Index view.

As always, you can check out the sample project on GitHub, and please let me know what you think of this demo in the comments!