Resource Filters in ASP.NET Core Web API

Resource Filters in ASP.NET Core Web API

In this article, I will discuss Resource Filters in ASP.NET Core Web API Applications with Examples. Please read our previous article discussing Authorization Filters in ASP.NET Core Web API Applications. Resource Filters are a unique and powerful type of filter in ASP.NET Core Web API. While Authorization Filters focus on security and Action/Result Filters on execution flow, Resource Filters are all about early resource management, optimization, and pre-/post-processing of requests and responses.

What is a Resource Filter in ASP.NET Core Web API?

In ASP.NET Core Web API, a Resource Filter is a special type of filter that allows us to run custom logic very early (after the Authorization filter) and very late (after the Result filter) in the request processing pipeline. The Main purposes of Resource Filters:

  • Resource Management: They can prepare, manage, or clean up resources (like database connections, files, or cache) needed for the request.
  • Short-circuiting Requests: They can check if a response (like cached data) can be returned immediately, skipping the rest of the processing for efficiency.
  • Pre/Post-Processing: They can perform operations both at the start and end of a request, making them useful for things like caching, rate limiting, or initial validation.

A Resource Filter is like a mall gatekeeper who gets the first chance to look at an incoming HTTP request and the last chance before the response leaves.

Example: Receptionist at a Hotel

What is a Resource Filter in ASP.NET Core Web API?

Think of a receptionist at a hotel’s entrance. When a guest arrives, the receptionist (Resource Filter) checks whether the guest already has an online reservation.

  • If YES: The guest gets the room key right away and skips all further steps (no need to fill out forms, show ID, etc.).
  • If NO: The guest must complete paperwork, show ID, and follow the full check-in process (normal processing pipeline).

The receptionist, acting first, decides if things can be handled quickly or if the guest needs to go through all steps. Similarly, a Resource Filter can speed up or skip further processing if it finds a quicker solution, like returning cached results or blocking requests early.

Example: Toll Gate with FastTag

Resource Filters in ASP.NET Core Web API with Examples

A car is approaching a toll gate on a highway, in FastTag Lane (Resource Filter Analogy). As the car approaches, a scanner quickly checks if it has a valid FastTag.

  • If the FastTag is Valid: The gate opens automatically within seconds, and the car drives through, no manual checks, stopping, or waiting. This is like a Resource Filter serving a cached response: the request is handled instantly, skipping all further steps.
  • If the FastTag is Missing or Invalid: The car is diverted to the manual toll lane. Here, the driver has to stop, talk to the attendant, pay cash, and go through manual verification and billing. This is like the request moving to the next stages in the pipeline: authorization checks, validation, and finally, action execution.

The FastTag lane (Resource Filter) acts as an early checkpoint, quickly deciding if the process can be completed right at the start or if it needs to go through all the regular steps.

In ASP.NET Core, this is exactly what a Resource Filter does. It gets the first look at every request and can either provide an instant response (if the answer is ready, like cached data) or let the request continue for deeper checks and processing. This helps optimize performance and manage resources efficiently.

How Do Resource Filters Work in ASP.NET Core Web API?

Resource Filters are special components that allow us to execute custom code at two key moments during the handling of an HTTP request:

  • Before most of the processing starts (right after authorization, before model binding, action filters, and the action method).
  • After all the processing is complete, just before the response is sent back to the client.

They do this by implementing either the IResourceFilter (for synchronous code) or IAsyncResourceFilter (for asynchronous code) interfaces. IResourceFilter interfaces define two key methods, and IAsyncResourceFilter defines one method that ASP.NET Core calls during the request lifecycle:

OnResourceExecuting/OnResourceExecutionAsync (Before Action Execution)

This method is called right after the Authorization Filters have passed, but before the action method and other filters (like action filters or model binding) run. It gives you the first chance to inspect, modify, or short-circuit the request. You receive a ResourceExecutingContext object, which allows you to:

  • Access the HttpContext and request data.
  • Set context.Result to an IActionResult (like a JSON response or status code) to short-circuit the pipeline.
Short-circuiting means:

If you assign a value to context.Result in this method, the framework does not run the action method or any other filters that come after. Instead, it directly sends this response back to the client. This is useful when you want to:

  • Return a cached response immediately without running the action.
  • Deny a request early due to validation failure.
OnResourceExecuted (After Action Execution)

This method is called after the action method has run and produced a result before the response is sent to the client. It receives a ResourceExecutedContext object, which allows us to:

  • Inspect or modify the response.
  • Perform cleanup tasks like disposing of resources, closing database connections, or logging.
Request Processing Flow with Resource Filters

The Processing Flow with Resource Filters is as follows:

  1. Request arrives at the server.
  2. Authorization Filters run first to ensure the user/request is authorized.
  3. Resource Filter’s OnResourceExecuting runs next.
    • You can check conditions or serve cached data immediately here.
    • If you set the context.Result here, the pipeline short-circuits and skips all further filters and the action method.
  4. If not short-circuited:
    • Model Binding occurs (maps HTTP data to action parameters).
    • Action Filters run (before and after the action).
    • Action Method executes.
  5. Result Filters run (before the result is finalized/sent).
  6. Resource Filter’s OnResourceExecuted runs to finalize or clean up.
  7. Exception Filters run if any unhandled exception occurred during action, result, or resource filter execution.
  8. The response is sent to the client.
Real-Time Examples to Understand Resource Filters in ASP.NET Core Web API

Let us develop a new ASP.NET Core Web API application to demonstrate real-world scenarios using Resource Filters. The example will explain how to use IResourceFilter, IAsyncResourceFilter, and attribute-based Resource Filter, each with a different scenario.

Create a new ASP.NET Core Web API project:

First, create a new ASP.NET Core Web API Project named ResourceFilterDemo. For in-memory caching, the required packages are already included in ASP.NET Core by default, so no extra packages are needed here.

Define the Resource Filters

Now, we will create three Resource Filters using three different approaches. First, create a folder named Filters in the project root directory where we will create all our Resource Filters.

Scenario 1: API Response Caching with IResourceFilter

You have a weather forecast endpoint that receives thousands of requests every minute for popular cities. Instead of querying the database or an external weather API every time, a Resource Filter checks if a recent weather result for the requested city is already cached. If it is, the filter serves the cached data instantly and skips the rest of the processing pipeline. If not, the request proceeds normally, and the result is cached for future requests.

So, create a new class file named WeatherCacheResourceFilter.cs within the Filters folder and copy and paste the following code. The WeatherCacheResourceFilter class implements the IResourceFilter interface and provides implementations for both the OnResourceExecuting and OnResourceExecuted methods. 

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace ResourceFilterDemo.Filters
{
    // This class implements a resource filter to cache weather data responses per city
    public class WeatherCacheResourceFilter : IResourceFilter
    {
        // Injected memory cache instance to store and retrieve cached data
        private readonly IMemoryCache _memoryCache;

        // Defines how long cached data will be stored (60 seconds here)
        private static readonly TimeSpan CacheDuration = TimeSpan.FromSeconds(60);

        // Constructor accepts IMemoryCache via dependency injection
        public WeatherCacheResourceFilter(IMemoryCache memoryCache)
        {
            _memoryCache = memoryCache;
        }

        // This method runs before the action method executes
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            // Extract the 'city' route parameter from the request URL, convert to lowercase for consistency
            var city = context.RouteData.Values["city"]?.ToString()?.ToLower();

            // If city is missing or empty, do nothing and let the request proceed
            if (string.IsNullOrEmpty(city)) 
                return; // Exit if city is missing

            // If you are using query string for city
            // var city = context.HttpContext.Request.Query["city"].ToString()?.ToLower();

            // Attempt to retrieve cached data for this city from the memory cache
            // 'cachedData' will contain the cached weather data if it exists
            if (_memoryCache.TryGetValue(city, out object? cachedData) && cachedData != null)
            {
                // If cached data found, immediately short-circuit the request pipeline
                // by returning the cached data as JSON with HTTP status 200 OK
                context.Result = new JsonResult(cachedData)
                {
                    StatusCode = 200
                };
            }
            // If no cached data found, the request proceeds to the action method normally
        }

        // This method runs after the action method has executed
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            // Extract the 'city' route parameter again for caching the response
            var city = context.RouteData.Values["city"]?.ToString()?.ToLower();

            // If you are using query string for city
            // var city = context.HttpContext.Request.Query["city"].ToString()?.ToLower();

            // If city is missing or empty, do nothing (cannot cache without key)
            if (string.IsNullOrEmpty(city)) 
                return; // Exit if city is missing

            // Check if the action method returned a successful 200 OK result with non-null value
            if (context.Result is ObjectResult result && result.StatusCode == 200 && result.Value is not null)
            {
                // Store the result value in the memory cache with the city as the key
                // Cache duration is set to CacheDuration (60 seconds)
                _memoryCache.Set(city, result.Value, CacheDuration);
            }
            // If the result is not successful or value is null, do not cache anything
        }
    }
}
When Should We Use IResourceFilter in ASP.NET Core Web API?

We need to use the IResourceFilter interface when our custom resource filter logic is synchronous and does not require any asynchronous operations. The IResourceFilter is ideal for scenarios where the actions need to perform, such as short-circuiting requests, caching responses, validating request headers, or logging resource access, can be completed quickly and do not involve any I/O-bound or time-consuming tasks like database calls, remote API requests, or file system operations. Using IResourceFilter in these cases keeps our filter implementation efficient and straightforward, as the framework does not have to deal with task management or asynchronous context switching.

Scenario 2: API Rate Limiting with IAsyncResourceFilter

To protect your API from abuse and ensure fair usage, you implement rate limiting per user or per IP address. A Resource Filter checks, at the very start of each request, how many requests this client has made in the last minute. If the client exceeds the allowed limit, the filter immediately returns a “Too Many Requests” (HTTP 429) response, stopping further processing. Otherwise, the request continues as usual.

So, create a new class file named RateLimitResourceFilter.cs within the Filters folder and copy and paste the following code. The RateLimitResourceFilter class implements the IAsyncResourceFilter interface and provides implementations for the OnResourceExecutionAsync method. 

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;

namespace ResourceFilterDemo.Filters
{
    // This class implements an asynchronous resource filter for rate limiting API requests by client IP address
    public class RateLimitResourceFilter : IAsyncResourceFilter
    {
        // Injected memory cache instance used to track request counts per client IP
        private readonly IMemoryCache _cache;

        // Maximum allowed requests per minute per client IP
        private readonly int _maxRequestsPerMinute = 5;

        // Constructor receives IMemoryCache via dependency injection
        public RateLimitResourceFilter(IMemoryCache cache)
        {
            _cache = cache;
        }

        // This async method runs before the action method executes
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
        {
            // Get the client's IP address as a string; fallback to "unknown" if not found
            var ip = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";

            // Compose a unique cache key per IP to track request count
            var cacheKey = $"RateLimit_{ip}";

            // Try to get current request count for this IP from cache; default to 0 if not present
            var requestCount = _cache.Get<int?>(cacheKey) ?? 0;

            // Check if the client has already reached or exceeded the request limit
            if (requestCount >= _maxRequestsPerMinute)
            {
                // Build a JSON error response indicating too many requests
                var errorResponse = new
                {
                    Status = 429,
                    Message = "Too Many Requests. Please try again later."
                };

                // Set the HTTP response immediately with 429 status and the error message
                context.Result = new JsonResult(errorResponse)
                {
                    StatusCode = 429
                };

                // Short-circuit the request pipeline; do not execute action or other filters
                return;
            }
            else
            {
                // Increment the request count and store it back in cache with a 1-minute expiration
                _cache.Set(cacheKey, requestCount + 1, TimeSpan.FromMinutes(1));
            }

            // If rate limit not exceeded, proceed with the request execution pipeline
            await next();
        }
    }
}
When Should We Use IAsyncResourceFilter in ASP.NET Core Web API?

We need to use the IAsyncResourceFilter interface when our custom resource filter logic requires asynchronous operations, such as making database calls, calling external web services, or performing any other I/O-bound work that benefits from non-blocking execution. By implementing IAsyncResourceFilter, we enable ASP.NET Core to execute our resource filter without blocking threads, which improves the scalability and responsiveness of our API, especially under high load or when dealing with operations that may take a significant amount of time to complete.

API Key Validation and Early Request Rejection (Custom Attribute + IResourceFilter)

Each request must include a valid API key. A Resource Filter can validate the presence and validity of the API key at the start of the request pipeline. If the key is invalid or missing, it immediately returns an unauthorized response, preventing further processing. This early rejection saves processing resources and enhances security by stopping unauthorized requests as soon as possible.

So, create a new class file named ApiKeyValidationResourceFilterAttribute.cs within the Filters folder and then copy and paste the following code. The ApiKeyValidationResourceFilterAttribute class inherits from the Attribute class, implements the IResourceFilter interface, and provides implementations for both the OnResourceExecuting and OnResourceExecuted methods. 

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace ResourceFilterDemo.Filters
{
    // Custom attribute that implements a synchronous resource filter to validate API key presence
    // Can be applied to controllers or actions as an attribute
    public class ApiKeyValidationResourceFilterAttribute : Attribute, IResourceFilter
    {
        // Name of the HTTP header expected to contain the API key
        private const string ApiKeyHeaderName = "X-API-KEY";

        // This method runs before the action method executes
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            // Access the collection of HTTP request headers
            var headers = context.HttpContext.Request.Headers;

            // Try to get the value of the API key header
            // If the header is missing or empty, extractedApiKey will be null or empty
            if (!headers.TryGetValue(ApiKeyHeaderName, out var extractedApiKey))
            {
                // Prepare a JSON payload indicating unauthorized access due to missing API key
                var payload = new
                {
                    Status = 401,
                    Message = "Unauthorized: API Key is missing."
                };

                // Set the HTTP response immediately to 401 Unauthorized with the payload
                // This short-circuits the request pipeline and prevents the action from executing
                context.Result = new JsonResult(payload)
                {
                    StatusCode = 401
                };

                // Return to stop further processing of the request
                return;
            }

            // If API key header is present, no action is taken here and the request proceeds
        }

        // This method runs after the action method executes
        // No post-processing is needed in this filter for after the action execution
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            // No implementation needed here
        }
    }
}
Register Filters and MemoryCache in the Program.cs:

Please modify the Program class as follows:

using ResourceFilterDemo.Filters;
namespace ResourceFilterDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllers()
            .AddJsonOptions(options =>
            {
                // Disable camelCase in JSON output, preserve property names as defined in C# classes
                options.JsonSerializerOptions.PropertyNamingPolicy = null;
            });

            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            // Register MemoryCache
            builder.Services.AddMemoryCache();

            //Register the Resource Filter
            //No Need to Register the ResourceFilter Attribute
            builder.Services.AddScoped<WeatherCacheResourceFilter>();
            builder.Services.AddScoped<RateLimitResourceFilter>();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}
API Controller Demonstrating Resource Filters

Now, create an API Empty Controller named WeatherController within the Controllers folder and then copy and paste the following code. The following controller code is self-explained, so please read the comment lines for a better understanding.

using Microsoft.AspNetCore.Mvc;
using ResourceFilterDemo.Filters;

namespace ResourceFilterDemo.Controllers
{
    [ApiController]
    [Route("api/[controller]")]

    // Apply the API Key Validation resource filter to all actions in this controller
    // This ensures every request to any endpoint here requires a valid API key
    [ApiKeyValidationResourceFilter]
    public class WeatherController : ControllerBase
    {
        // GET api/weather/forecast/{city}
        // Apply caching resource filter to this action to cache responses per city
        [HttpGet("forecast/{city}")]
        [ServiceFilter(typeof(WeatherCacheResourceFilter))]
        public IActionResult GetWeatherForecast(string city)
        {
            // Simulate fetching weather data with randomized temperature and current timestamp
            var weatherData = new
            {
                TemperatureC = new Random().Next(-10, 40),
                Condition = "Sunny",
                Timestamp = DateTime.UtcNow
            };

            // Wrap the weather data with the city key, converting city name to lowercase for consistency
            var response = new
            {
                City = city.ToLower(),
                Data = weatherData
            };

            // Return HTTP 200 OK with the response JSON object
            return Ok(response);
        }

        // GET api/weather/limited-forecast/{city}
        // Apply rate limiting filter and caching filter to this action, in that order
        // Rate limiting will reject excessive requests before caching logic is invoked
        [HttpGet("limited-forecast/{city}")]
        [ServiceFilter(typeof(RateLimitResourceFilter))]
        [ServiceFilter(typeof(WeatherCacheResourceFilter))]
        public IActionResult GetLimitedWeatherForecast(string city)
        {
            // Simulate fetching weather data with randomized temperature and current timestamp
            var weatherData = new
            {
                TemperatureC = new Random().Next(-10, 40),
                Condition = "Cloudy",
                Timestamp = DateTime.UtcNow
            };

            // Wrap the weather data with the city key, converting city name to lowercase
            var response = new
            {
                City = city.ToLower(),
                Data = weatherData
            };

            // Return HTTP 200 OK with the response JSON object
            return Ok(response);
        }

        // GET api/weather/open/{city}
        // This action only has API key validation from the controller level, no caching or rate limiting
        [HttpGet("open/{city}")]
        public IActionResult GetOpenWeather(string city)
        {
            // Simulate fetching weather data with randomized temperature and current timestamp
            var weatherData = new
            {
                TemperatureC = new Random().Next(-10, 40),
                Condition = "Partly Cloudy",
                Timestamp = DateTime.UtcNow
            };

            // Wrap the weather data with the city key, converting city name to lowercase
            var response = new
            {
                City = city.ToLower(),
                Data = weatherData
            };

            // Return HTTP 200 OK with the response JSON object
            return Ok(response);
        }
    }
}
Why Do We Need Resource Filters in ASP.NET Core?

Resource Filters give you a powerful opportunity to intercept, modify, or short-circuit HTTP requests early, even before model binding and action execution. They are ideal when you want to improve performance, enforce policies, or manage resources globally.

Caching Responses – Serve Data Fast Without Extra Processing

Imagine an online news API that serves the latest headlines. Generating these headlines requires querying multiple databases and running complex algorithms. If the headlines for a particular region are already cached, the resource filter can immediately return the cached headlines without calling the controller action or executing other filters. This avoids unnecessary database hits, reduces server load, and significantly improves response time.

  • Benefit: By short-circuiting the request and serving cached data early, resource filters optimize performance and scalability.
Resource Management – Efficient Use and Cleanup of Shared Resources

Consider a financial API that opens a database transaction before processing a user’s request to ensure consistency. A resource filter can open a database transaction at the start of request processing. After the action executes, the resource filter commits or rolls back the transaction depending on success or failure. This ensures the entire request runs within a single transaction scope, providing atomicity and consistent resource management.

  • Benefit: Resource filters help allocate resources once per request and clean them up reliably afterward, avoiding leaks or inconsistent states.
Short-Circuiting Requests – Handle Simple Cases Immediately

An API that requires API key validation before processing requests. The resource filter checks the API key at the start. If the key is invalid or missing, the filter immediately returns an error response, preventing the request from proceeding further. This saves CPU time and bandwidth by avoiding unnecessary work downstream.

  • Benefit: This early rejection of invalid requests enhances security and resource efficiency.
Rate Limiting – Prevent Overload

An API exposed publicly applies a rate limit to avoid abuse. The resource filter checks if the client has exceeded their allowed request quota. If exceeded, it returns a 429 Too Many Requests response immediately. Otherwise, it lets the request proceed.

  • Benefit: Protects your API backend from overload, ensuring fair usage.
Preprocessing Headers or Authentication Tokens – Early Rejection

Your API validates or decrypts custom headers before model binding or action execution. A resource filter inspects and processes these headers at the start. It can reject requests with malformed headers early or modify them for downstream processing.

  • Benefit: Keeps authentication and preprocessing clean and centralized.

Resource Filters in ASP.NET Core Web API are ideal for managing cross-cutting resource concerns at the very beginning and end of the request processing pipeline. They enable us to implement efficient caching, preprocessing, and resource management patterns, optimizing our Web API for performance and scalability. They can dramatically reduce database calls, speed up responses, and centralize logic that otherwise would be scattered across your application.

In the next article, I will discuss Action Filters in ASP.NET Core Web API Applications. In this article, I explain Resource Filters in ASP.NET Core Web API Applications. I hope you enjoy this Resource Filters in ASP.NET Core Web API article.

1 thought on “Resource Filters in ASP.NET Core Web API”

Leave a Reply

Your email address will not be published. Required fields are marked *