Creating and Testing ASP.NET Core Filter Attributes
Introduction
Filters are a great way to inject code in the filter-pipeline (aka invocation pipeline), in which the flow is based on the execution order of the different filter-types. Also, we can extract repetitive code out of the action methods. By this way, we can follow the Don’t Repeat Yourself (DRY) principle by creating a common abstraction.
In our previous article .NET Nakama (2021, February), we have investigated the filter pipeline, the different filter types and the cases in which can be used, and how the different filter-types can be applied in controllers and action methods. These concepts are used in the current article. Therefore, you can begin from the previous article (if you haven’t read it yet 😉).
In this article, we will start by investigating the difference between the implementation of synchronous
and asynchronous
filters. Then, we will see the questions that can guide us to make the appropriate decisions when implementing filter attributes. Finally, we will create two filter attributes (as examples) and unit tests for some of their scenarios-behaviour.
Synchronous and Asynchronous Filters
ASP.NET Core supports both synchronous
and asynchronous
filters. The main difference when implementing such filters is the selection of the appropriate interface.
Synchronous filter interfaces define two methods that are executed before
and after
a specific stage in the filter pipeline. The method that is executed before
has a name that ends with Executing
and the method that is executed after
has a name that ends with Executed
.
Asynchronous filters define one method with a name that ends with ExecutionAsync
. The implementation is similar to a middleware’s implementation, in the sense that a next()
method is used to execute any subsequent filters or action methods.
Decisions when Implementing Filters
We have already seen the different filter types and how to apply them in .NET Nakama (2021, February). Also, we have seen that filters support both synchronous and asynchronous implementations through different interface definitions, which are described in each filter type section.
Before starting implementing a filter, we can answer the following questions, which will guide us to make the appropriate decisions depending on the current goal.
- The filter should be synchronous or asynchronous?
- Consider using asynchronous filters if there is a need for data access, I/O, and long-running operations.
- Implement either the synchronous or the asynchronous version of a filter interface, but not both.
- Which filter type(s) are necessary for the current goal?
- Depending on the implementation goal and by considering Figure 1, decide on which stage in the filer pipeline your code should be injected.
- As a result, you should have decided the filter type(s) and the before-after methods that should be implemented.
- Does the filter depend on other services via Dependency Injection (DI)?
- No! Continue to the next question.
- Yes! To use DI in a filter, select one of the following: ServiceFilterAttribute, TypeFilterAttribute, or IFilterFactory. For more information regarding the
IFilterFactory
read .NET Nakama (2021, February). We have collected the necessary information, do not continue to the next question!
- How many filter types have been selected (one or many)?
- Note: This question is applicable only when DI is not required.
- If only one filter type is selected, consider using a built-in filter attribute depending on the selected filter type.
- In case of multiple selected filter types, consider using the IFilterFactory, in which multiple filter types can be created in the same class.
After answering the previous questions, we should have decided the necessities to create a scaffold filter, which are:
- The filter type(s) that should be used.
- The before or/and after methods.
- The
Interfaces
orbase class
that should be implemented.
Creating Filters
We will implement two filter attributes (as an example) based on some needs (our goals) which will be used in a Web API project. You can download the source code of these examples from GitHub.
Create Filter by Using a Built-In Filter Attribute
Let’s assume that we need to return different HTTP-headers depending on the controller and the action method. We can start by answering the preparation questions:
Question | Answer |
---|---|
The filter should be synchronous or asynchronous? | Synchronous. Because it’s a simple task, without the need for data access, I/O, or long-running operations. |
Which filter type(s) are necessary for the current goal? | The Result filter type can be used, as we need to modify the result that is produced from the action method, before its execution (i.e. using the OnResultExecuting method). |
Does the filter depend on other services via Dependency Injection (DI)? | No, we do not need DI. |
How many filter types have been (one or many)? | We have selected one filter type (Result Filter). So, the ResultFilterAttribute built-in filter attribute can be used. |
Based on our answers, we can create a synchronous ResultFilter
that would be based on the ResultFilterAttribute
class, which will be used to add the HTTP-headers in our response (AddHeaderAttribute.cs
).
Create Filter by Using the IFilterFactory and IExceptionFilter
In this example, we will use the IFilterFactory
to demonstrate the creation of a class of multiple filter types and to investigate the filter pipeline flow in practice. In your case, you can select the number of filter types based on your needs.
Our filter attribute will be used to add a log each time a different filter type event is called. Additionally, we will implement an Exception filter
, which will either redirect to a web-page or it will return a JSON response, depending on the Accept
header.
The IFilterFactory
interface and the Attribute
base class is used to create the filter attribute (MyMultipleActionsAttribute.cs
). From the following code we can see how to determine the accepted targets of an attribute (AttributeTargets
), if multiple instances can be used (AllowMultiple
), and if it can be inherited (Inherited
).
The MyMultipleActionsFilter
implements the following interfaces IResourceFilter
, IActionFilter
, IExceptionFilter
, and IResultFilter
. The following code shows a part of the complete implementation to present the main logic.
Apply Filters and Examine Results
The filter attributes that we have created can be added to the filter pipeline by instance
. This means that we can use the created attributes with their names (without the Attribute
suffix), e.g. the AddHeaderAttribute
can be used as [AddHeader]
and MyMultipleActionsAttribute
as [MyMultipleActions]
.
As we can see in the following code example, we have applied the [AddHeader]
attribute in two cases:
- At the
WeatherForecastController
with theheadergeneral
andvalue1
parameters. This attribute will be applied to all action methods of the controller and will return an HTTP-header withname = headergeneral
andvalue = value1
, as we can see in Figure 2. - At the
GetById
action method with theheaderspecific
andnew string[] { "value2", "value3" }
parameters. This attribute will be applied only to the current action method, and will return an HTTP-header withname = headerspecific
with two values “value2”and “value3”, as we can see in Figure 3.
The [MyMultipleActions]
attribute has been applied separately to the actions methods with different input parameters. Based on the Enabled
parameter’s value, the attribute will be active (by adding logs and catching exceptions) or not.
- At the
GetAll
action method with theEnabled = false
parameter. As we can see in Figure 2, there are no logs regarding theMyMultipleActions
filter. - At the
GetById
action method with theEnabled = true
parameter.- From Figure 3, we can see that the
MyMultipleActions
filter has added a log for each stage of the filter pipeline, as expected based on Figure 1. - Figure 4 shows how to call the
GetById
action by using a browser (the Accept HTTP-header is set automatically totext/html
). As we can see, the page redirects to an error page. - Figure 5 shows how to call the
GetById
action by using the Postman application by setting the Accept HTTP-header to return a JSON response. Also, we can see the actual JSON response.
- From Figure 3, we can see that the
Testing a Filter
When applying unit testing, we are breaking down the code functionalities into testable scenarios-behaviours to test them as individual units. Unit testing can greatly affect the quality of the code if it is used as part of the development flow (e.g. defined in the definition of done). It is recommended to create unit tests that verify the behaviour of the code as soon as it’s written. But, this is an investment that has to be agreed with the team and company.
The following scenarios-behaviours of our filter attributes will be verified by creating unit tests. But, these scenarios are not complete. In a real-life application, many more unit tests would be created to cover more cases.
- The
AddHeaderAttribute
is expected to add a header (with predefined name and value(s)) to the HTTP response. - The
MyMultipleActionsFilter
is expected to add an information log each time a different filter type event is called.
As we can see in the following unit tests, we are using the AAA (Arrange, Act, and Assert) pattern to separate the relative sections of code. For the Arrange section, we are initializing the following objects depending on our case-scenario to imitate the actual conditions in which the filter will be executed.
- The custom input parameters depending on the logic of the current filter.
- An ActionContext object, depending on our case-scenario to imitate the actual conditions in which the filter will be executed.
- The context for the specific filter type and method, depending on our case-scenario.
Summary
A filter in ASP.NET Core can be used to inject code in the filter-pipeline. Also, filters can be used to extract repetitive code out of the action methods. In this article, we focused on creating and testing filter attributes. Before the implementation of a filter, we can start by making some decisions based on the current goal. This will help us select the proper filter type (or types), methods, and Interfaces
or base class
that should be implemented.
We have created two filter attributes based on our needs-goals. Our created filters (ResultFilterAttribute, IFilterFactory, IExceptionFilter, etc.) were applied in a Web API project in which we verified their results for different input parameters. For example, our implementation of the IExceptionFilter
differentiates the exception handling depending on the value of the Accept
header. Finally, we saw how to create unit tests for specific scenarios-behaviours of our created filters.
Filters are a great way to inject code in the filter-pipeline and to extract repetitive code out of the action methods. I hope that you are already thinking about the cases in which filters can be used in your projects 🙂.
If you liked this article (or not), do not hesitate to leave comments, questions, suggestions, complaints, or just say Hi in the section below. Don't be a stranger 😉!
Dont't forget to follow my feed and be a .NET Nakama. Have a nice day 😁.