August 2016

Volume 31 Number 8

[ASP.NET Core]

Real-World ASP.NET Core MVC Filters

By Steve Smith

Filters are a great, often underutilized feature of ASP.NET MVC and ASP.NET Core MVC. They provide a way to hook into the MVC action invocation pipeline, which makes them great for pulling common repetitive tasks out of your actions. Often, an app will have a standard policy that it applies to how it handles certain conditions, especially those that might generate particular HTTP status codes. Or it might perform error handling or application-level logging in a specific fashion, in every action. These kinds of policies represent cross-cutting concerns, and if possible, you want to follow the Don’t Repeat Yourself (DRY) principle and pull them out into a common abstraction. Then, you can apply this abstraction globally or wherever appropriate within your application. Filters provide a great way to achieve this.

What About Middleware?

In the June 2016 issue, I described how ASP.NET Core middle­ware allows you to control the request pipeline in your apps (msdn.magazine.com/magazine/mt707525). That sounds suspiciously like what filters can do in your ASP.NET Core MVC app. The difference between the two is context. ASP.NET Core MVC is implemented via middleware. (MVC itself isn’t middleware, but it configures itself to be the default destination for the routing middleware). ASP.NET Core MVC includes many features like Model Binding, Content Negotiation, and Response Formatting. Filters exist within the context of MVC, so they have access to these MVC-level features and abstractions. Middleware, in contrast, exists at a lower level and doesn’t have any direct knowledge of MVC or its features.

If you have functionality you want to run at a lower level, and it doesn’t depend on MVC-level context, consider using middleware. If you tend to have a lot of common logic in your controller actions, filters might provide a way for you to DRY them up to make them easier to maintain and test.

Kinds of Filters

Once the MVC middleware takes over, it calls into a variety of filters at different points within its action invocation pipeline.

The first filters that execute are authorization filters. If the request isn’t authorized, the filter short-circuits the rest of the pipeline immediately.

Next in line are resource filters, which (after authorization) are both the first and last filter to handle a request. Resource filters can run code at the very beginning of a request, as well as at the very end, just before it leaves the MVC pipeline. One good use case for a resource filter is output caching. The filter can check the cache and return the cached result at the beginning of the pipeline. If the cache isn’t yet populated, the filter can add the response from the action to the cache at the end of the pipeline.

Action filters run just before and after actions are executed. They run after model binding takes place, so they have access to the model-bound parameters that will be sent to the action, as well as the model validation status.

Actions return results. Result filters run just before and after results are executed. They can add behavior to view or formatter execution.

Finally, exception filters are used to handle uncaught exceptions and apply global policies to these exceptions within the app.

In this article, I’ll focus on action filters.

Filter Scoping

Filters can be applied globally, or at the individual controller or action level. Filters that are implemented as attributes can typically be added at any level, with global filters affecting all actions, controller attribute filters affecting all actions within that controller, and action attribute filters applying to just that action. When multiple filters apply to an action, their order is determined first by an Order property and second by how they’re scoped to the action in question. Filters with the same Order run outside-in, meaning first global, then controller and then action-level filters are run. After the action runs, the order is reversed, so the action-level filter runs, then the controller-level filter, then the global filter.

Filters that aren’t implemented as attributes can still be applied to controllers or actions by using the TypeFilterAttribute type. This attribute accepts the type of the filter to run as a constructor parameter. For example, to apply the CustomActionFilter to a single action method, you’d write:

[TypeFilter(typeof(CustomActionFilter))]
public IActionResult SomeAction()
{
  return View();
}

The TypeFilterAttribute works with the app’s built-in services container to ensure any dependencies exposed by the Custom­ActionFilter are populated at run time.

A DRY API

To demonstrate a few examples where filters can improve the design of an ASP.NET MVC Core app, I’ve built a simple API that provides basic create, read, update, delete (CRUD) functionality and follows a few standard rules for handling invalid requests. Because securing APIs is its own topic, I’m intentionally leaving that outside the scope of this sample.

My sample app exposes an API for managing authors, which are simple types with just a couple of properties. The API uses the standard HTTP verb-based conventions to get all authors, get one author by ID, create a new author, edit an author and delete an author. It accepts an IAuthorRepository through dependency injection (DI) to abstract the data access. (See my May article at msdn.com/magazine/mt703433 for more on DI.) Both the controller implementation and the repository are implemented asynchronously.

The API follows two policies:

  1. API requests that specify a particular author ID will get a 404 response if that ID doesn’t exist.
  2. API requests that provide an invalid Author model instance (ModelState.IsValid == false) will return a BadRequest with the model errors listed.

Figure 1 shows the implementation of this API with these rules in place.

Figure 1 AuthorsController

[Route("api/[controller]")]
public class AuthorsController : Controller
{
  private readonly IAuthorRepository _authorRepository;
  public AuthorsController(IAuthorRepository authorRepository)
  {
    _authorRepository = authorRepository;
  }
  // GET: api/authors
  [HttpGet]
  public async Task<List<Author>> Get()
  {
    return await _authorRepository.ListAsync();
  }
  // GET api/authors/5
  [HttpGet("{id}")]
  public async Task<IActionResult> Get(int id)
  {
    if ((await _authorRepository.ListAsync()).All(a => a.Id != id))
    {
      return NotFound(id);
    }
    return Ok(await _authorRepository.GetByIdAsync(id));
  }
  // POST api/authors
  [HttpPost]
  public async Task<IActionResult> Post([FromBody]Author author)
  {
    if (!ModelState.IsValid)
    {
      return BadRequest(ModelState);
    }
    await _authorRepository.AddAsync(author);
    return Ok(author);
  }
  // PUT api/authors/5
  [HttpPut("{id}")]
  public async Task<IActionResult> Put(int id, [FromBody]Author author)
  {
    if ((await _authorRepository.ListAsync()).All(a => a.Id != id))
    {
      return NotFound(id);
    }
    if (!ModelState.IsValid)
    {
       return BadRequest(ModelState);
    }
    author.Id = id;
    await _authorRepository.UpdateAsync(author);
    return Ok();
  }
  // DELETE api/values/5
  [HttpDelete("{id}")]
  public async Task<IActionResult> Delete(int id)
  {
    if ((await _authorRepository.ListAsync()).All(a => a.Id != id))
    {
      return NotFound(id);
    }
    await _authorRepository.DeleteAsync(id);
    return Ok();
  }
  // GET: api/authors/populate
  [HttpGet("Populate")]
  public async Task<IActionResult> Populate()
  {
    if (!(await _authorRepository.ListAsync()).Any())
    {
      await _authorRepository.AddAsync(new Author()
      {
        Id = 1,
        FullName = "Steve Smith",
        TwitterAlias = "ardalis"
      });
      await _authorRepository.AddAsync(new Author()
      {
        Id = 2,
        FullName = "Neil Gaiman",
        TwitterAlias = "neilhimself"
      });
    }
    return Ok();
  }
}

As you can see, there’s a fair bit of duplicate logic in this code, especially in the way NotFound and BadRequest results are returned. I can quickly replace the model validation/BadRequest checks with a simple action filter:

public class ValidateModelAttribute : ActionFilterAttribute
{
  public override void OnActionExecuting(ActionExecutingContext context)
    {
    if (!context.ModelState.IsValid)
    {
      context.Result = new BadRequestObjectResult(context.ModelState);
    }
  }
}

This attribute can then be applied to those actions that need to perform model validation by adding [ValidateModel] to the action method. Note that setting the Result property on the Action­ExecutingContext will short-circuit the request. In this case, there’s no reason not to apply the attribute to every action, so I’ll add it to the controller rather than to every action.

Checking to see if the author exists is a bit trickier, because this relies on the IAuthorRepository that’s passed into the controller through DI. It’s simple enough to create an action filter attribute that takes a constructor parameter, but, unfortunately, attributes expect these parameters to be supplied where they’re declared. I can’t provide the repository instance where the attribute is applied; I want it to be injected at run time by the services container.

Fortunately, the TypeFilter attribute will provide the DI support this filter requires. I can simply apply the TypeFilter attribute to the actions, and specify the ValidateAuthorExistsFilter type:

[TypeFilter(typeof(ValidateAuthorExistsFilter))]

While this works, it’s not my preferred approach, because it’s less readable and developers looking to apply one of several common attribute filters will not find the ValidateAuthorExists­Attribute through IntelliSense. An approach I favor is to subclass the TypeFilterAttribute, give it an appropriate name, and put the filter implementation in a private class inside of this attribute. Figure 2 demonstrates this approach. The actual work is performed by the private ValidateAuthorExistsFilterImpl class, whose type is passed into the TypeFilterAttribute’s constructor.

Figure 2 ValidateAuthorExistsAttribute

public class ValidateAuthorExistsAttribute : TypeFilterAttribute
{
  public ValidateAuthorExistsAttribute():base(typeof
    (ValidateAuthorExistsFilterImpl))
  {
  }
  private class ValidateAuthorExistsFilterImpl : IAsyncActionFilter
  {
    private readonly IAuthorRepository _authorRepository;
    public ValidateAuthorExistsFilterImpl(IAuthorRepository authorRepository)
    {
      _authorRepository = authorRepository;
    }
    public async Task OnActionExecutionAsync(ActionExecutingContext context,
      ActionExecutionDelegate next)
    {
      if (context.ActionArguments.ContainsKey("id"))
      {
        var id = context.ActionArguments["id"] as int?;
        if (id.HasValue)
        {
          if ((await _authorRepository.ListAsync()).All(a => a.Id != id.Value))
          {
            context.Result = new NotFoundObjectResult(id.Value);
            return;
          }
        }
      }
      await next();
    }
  }
}

Note that the attribute has access to the arguments being passed to the action, as part of the ActionExecutingContext parameter. This allows the filter to check whether an id parameter is present and get its value before checking to see if an Author exists with that Id. You should also notice that the private ValidateAuthorExistsFilterImpl is an async filter. With this pattern, there’s just one method to implement, and work can be done before or after the action is executed by running it before or after the call to next. However, if you’re short-circuiting the filter by setting a context.Result, you need to return without calling next (otherwise you’ll get an exception).

Another thing to remember about filters is that they shouldn’t include any object-level state, such as a field on an IActionFilter (in particular one implemented as an attribute) that’s set during OnActionExecuting and then read or modified in OnActionExecuted. If you find the need to do this sort of logic, you can avoid that kind of state by switching to an IAsyncActionFilter, which can simply use local variables within the OnActionExecutionAsync method.

After shifting model validation and checking for the existence of records from within the controller actions to common filters, what has been the effect on my controller? For comparison, Figure 3 shows Authors2Controller, which performs the same logic as AuthorsController, but leverages these two filters for its common policy behavior.

Figure 3 Authors2Controller

[Route("api/[controller]")]
[ValidateModel]
public class Authors2Controller : Controller
{
  private readonly IAuthorRepository _authorRepository;
  public Authors2Controller(IAuthorRepository authorRepository)
  {
    _authorRepository = authorRepository;
  }
  // GET: api/authors2
  [HttpGet]
  public async Task<List<Author>> Get()
  {
    return await _authorRepository.ListAsync();
  }
  // GET api/authors2/5
  [HttpGet("{id}")]
  [ValidateAuthorExists]
  public async Task<IActionResult> Get(int id)
  {
    return Ok(await _authorRepository.GetByIdAsync(id));
  }
  // POST api/authors2
  [HttpPost]
  public async Task<IActionResult> Post([FromBody]Author author)
  {
    await _authorRepository.AddAsync(author);
    return Ok(author);
  }
  // PUT api/authors2/5
  [HttpPut("{id}")]
  [ValidateAuthorExists]
  public async Task<IActionResult> Put(int id, [FromBody]Author author)
  {
    await _authorRepository.UpdateAsync(author);
    return Ok();
  }
  // DELETE api/authors2/5
  [HttpDelete("{id}")]
  [ValidateAuthorExists]
  public async Task<IActionResult> Delete(int id)
  {
    await _authorRepository.DeleteAsync(id);
    return Ok();
  }
}

Notice two things about this refactored controller. First, it’s shorter and clearer. Second, there are no conditionals in any of the methods. The common logic of the API has been completely pulled into filters, which are applied where appropriate, so that the work of the controller is as straightforward as possible.

But Can You Test It?

Moving logic from your controller into attributes is great for reducing code complexity and enforcing consistent runtime behavior. Unfortunately, if you run unit tests directly against your action methods, your tests aren’t going to have the attribute or filter behavior applied to them. This is by design, and of course you can unit test your filters independent of individual action methods to ensure they work as designed. But what if you need to ensure not only that your filters work, but that they’re properly set up and applied to individual action methods? What if you want to refactor some API code you already have to take advantage of the filters I just showed, and you want to be sure the API still behaves correctly when you’re finished? That calls for integration testing. Fortunately, ASP.NET Core includes some great support for fast, easy integration testing.

My sample application is configured to use an in-memory Entity Framework Core DbContext, but even if it were using SQL Server, I could easily switch to using an in-memory store for my integration tests. This is important, because it dramatically improves the speed of such tests, and makes it much easier to set them up, because no infrastructure is required.

The class that does most of the heavy lifting for integration testing in ASP.NET Core is the TestServer class, available in the Microsoft.AspNetCore.TestHost package. You configure the TestServer identically to how you configure your Web app in the Program.cs entry point, using a WebHostBuilder. In my tests, I’m choosing to use the same Startup class as in my sample Web app, and I’m specifying that it runs in the Testing environment. This will trigger some sample data when the site starts up:

var builder = new WebHostBuilder()
  .UseStartup<Startup>()
  .UseEnvironment("Testing");
var server = new TestServer(builder);
var client = server.CreateClient();

The client in this case is a standard System.Net.Http.HttpClient, which you use to make requests to the server just as if it were over the network. But because all of the requests are made in memory, the tests are extremely fast and robust.

For my tests, I’m using xUnit, which includes the ability to run multiple tests with different data sets for a given test method. To confirm that my AuthorsController and Authors2Controller classes both behave identically, I’m using this feature to specify both controllers to each test. Figure 4 shows several tests of the Put method.

Figure 4 Authors Put Tests

[Theory]
[InlineData("authors")]
[InlineData("authors2")]
public async Task ReturnsNotFoundForId0(string controllerName)
{
  var authorToPost = new Author() { Id = 0, FullName = "test",
    TwitterAlias = "test" };
  var jsonContent = new StringContent(JsonConvert.SerializeObject(authorToPost),
     Encoding.UTF8, "application/json");
  var response = await _client.PutAsync($"/api/{controllerName}/0", jsonContent);
  Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
  var stringResponse = await response.Content.ReadAsStringAsync();
  Assert.Equal("0", stringResponse);
}
[Theory]
[InlineData("authors")]
[InlineData("authors2")]
public async Task ReturnsBadRequestGivenNoAuthorName(string controllerName)
{
  var authorToPost = new Author() {Id=1, FullName = "", TwitterAlias = "test"};
  var jsonContent = new StringContent(
    JsonConvert.SerializeObject(authorToPost),
     Encoding.UTF8, "application/json");
  var response = await _client.PutAsync($"/api/{controllerName}/1", jsonContent);
  Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
  var stringResponse = await response.Content.ReadAsStringAsync();
  Assert.Contains("FullName", stringResponse);
  Assert.Contains("The FullName field is required.", stringResponse);
}
[Theory]
[InlineData("authors")]
[InlineData("authors2")]
public async Task ReturnsOkGivenValidAuthorData(string controllerName)
{
  var authorToPost = new Author() {
    Id=1,FullName = "John Doe",
    TwitterAlias = "johndoe" };
  var jsonContent = new StringContent(JsonConvert.SerializeObject(authorToPost),
     Encoding.UTF8, "application/json");
  var response = await _client.PutAsync($"/api/{controllerName}/1", jsonContent);
  response.EnsureSuccessStatusCode();
}

Notice that these integration tests don’t require a database or an Internet connection or a running Web server. They’re almost as fast and simple as unit tests, but, most important, they allow you to test your ASP.NET apps through the entire request pipeline, not just as an isolated method within a controller class. I still recommend writing unit tests where you can, and falling back to integration tests for behavior you can’t unit test, but it’s great to have such a high-performance way to run integration tests in ASP.NET Core.

Next Steps

Filters are a big topic—I only had room for a couple of examples in this article. You can check out the official documentation on docs.asp.net to learn more about filters and testing ASP.NET Core apps.

The source code for this sample is available at bit.ly/1sJruw6.


Steve Smith is an independent trainer, mentor and consultant, as well as an ASP.NET MVP. He has contributed dozens of articles to the official ASP.NET Core documentation (docs.asp.net), and helps teams quickly get up to speed with ASP.NET Core. Contact him at ardalis.com and follow him on Twitter: aka @ardalis.


Thanks to the following Microsoft technical expert for reviewing this article: Doug Bunting
Doug Bunting is a developer working on the ASP.Net team at Microsoft.



Discuss this article in the MSDN Magazine forum