When building web applications, you might often want to control the frequency of user requests to prevent malicious attacks. In other words, you might want to limit the number of requests coming from an IP address during a short timespan to mitigate denial-of-service attacks. This process is known as rate limiting.
There are many Nuget packages that use Middleware for handling user requests but there is a problem with middleware because they affect all incoming requests! So, what is the solution if you want to control just some critical endpoints? Yes, that is ActionFilters (or EndPointFilters)!
Let's go to find out how to use action filters as a rate limit.
Create Asp.net core API project:
1- Click on "Create new project" 2- Select Asp.net core web API
3- Enter the project name
Ok you created the project, now you should install this Nuget package:
Install-Package DotNetRateLimiter
And add this line to your Program.cs
builder.Services.AddRateLimitService(builder. Configuration);
Now you use the rate limit on your Action methods:
[HttpGet]
[RateLimit(PeriodInSec = 60, Limit = 3)]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
For MinimalAPI .NET 7+
app.MapGet("/weatherforecast", () =>
{
return Results.Ok();
})
.WithRateLimiter(options =>
{
options.PeriodInSec = 60;
options. Limit = 3;
});
In this way the action only allows 3 requests per minute let's test it in swagger. if you try to call API more than 3 times it gets 429 (Too Many requests
):
Nice! it works. So, if you want to restrict the action method with parameters even in route or query string or request body (Really!), it could be possible like:
[HttpGet("forecast/{id1}/{id2}")]
[RateLimit(PeriodInSec = 60, Limit = 3, RouteParams = "id1,id2", QueryParams = "name1,name2")]
public IEnumerable<WeatherForecast> Get(int id1, string id2, string name1, string name2)
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
For MinimalAPI .NET 7+
app.MapGet("/weatherforecast/{id}", (int id) =>
{
return Results.Ok();
})
.WithRateLimiter(options =>
{
options.PeriodInSec = 60;
options.Limit = 3;
options.RouteParams = "id";
});
You can customize the rate limit response if needed, for the sake of this you need to add config into appsettings.json file:
"RateLimitOption": {
"EnableRateLimit": true, //Optional: if set false rate limit will be disabled, default is true
"HttpStatusCode": 429, //Optional: default is 429
"ErrorMessage": "Rate limit Exceeded", //Optional: default is Rate limit Exceeded
"IpHeaderName": "X-Forwarded-For" //Optional: header name for get Ip address, default is X-Forwarded-For
"RedisConnection": "127.0.0.1:6379", //Optional
"IpWhiteList": ["::1"], //Optional
"ClientIdentifier": "X-Client-Id" ////Optional: for getting client id from request header if this present the rate limit will not use IP for limit requests
"ClientIdentifierWhiteList": ["test-client"] ////Optional
}
As you noticed there are some options that can be useful, the RateLimit uses InMemory cache by default, but if you set up a Redis connection it will use Redis, it is recommended that we use Redis to check the rate limit in distributed applications. By default, it limits the IP address for control requests but you can set ClientIdentifier in the request headers and the header name is configurable.
Here is the Github repo:
Cheers!