Click here to Skip to main content
15,867,911 members
Articles / Web Development / ASP.NET / ASP.NET Core
Tip/Trick

Built-in Rate Limiting in ASP.NET Core vs AspNetCoreRateLimit

Rate me:
Please Sign up or sign in to vote.
4.50/5 (2 votes)
28 Nov 2022CPOL5 min read 8.2K   5   2
Learn about the built-in rate-limiting middleware in ASP.NET Core.
An introduction to the new rate-limiting middleware available in ASP.NET Core and how it compares to the AspNetCoreRateLimit package that solves the same task.

Introduction

Since I learned that ASP.NET Core 7 and newer come with built-in rate limiting I have wanted to try it out. I finally had some time to check it out and here's what I've found so far. This introduces rate limiting in ASP.NET Core using both the built-in option and AspNetCoreRateLimit.

Background

A quick comment before we begin. I've blogged about this subject in the past, so I would recommend you to read through this post: Rate limiting API requests with ASP.NET Core and AspNetCoreRateLimit. In this post, I'll be comparing the built-in rate limiting feature in ASP.NET Core with AspNetCoreRateLimit. I'll try to summarize the previous post here but for the full experience, consider reading both posts.

Rate-limiting in ASP.NET Core

Rate limiting in a web application is typically about ... well limiting the number of requests processed by the web application. For public APIs, you quickly need some way of limiting how many requests your users can make. There's a range of possibilities as to how to implement this, whether this is based on the client's IP address, an API key/token, or something third. To compare the examples in this post with the previous post about AspNetCoreRateLimit, I'll implement rate limiting based on an API key.

To test out rate limiting, you will need .NET 7 or newer as well as the newest version of Visual Studio 2022 (currently the preview version is required to run .NET 7 apps).

Create a new application by running the following command:

dotnet new mvc

This creates a new MVC app. Rate limiting is not specific to ASP.NET Core MVC and can be used with minimal API and other types as well. In the new web application, install the Microsoft.AspNetCore.RateLimiting NuGet package:

dotnet add package Microsoft.AspNetCore.RateLimiting

In the Program.cs file or (Startup.cs if don't like top-level statements) include rate limiting configuration like this:

C#
builder.Services.AddRateLimiter(options =>
{
});

This will tell ASP.NET Core that you want to configure the rate-limiting middleware with default settings. One default parameter that I would like to change up front is the status code that should be returned once the limit is reached. As a default Microsoft.AspNetCore.RateLimiting will return a status code of 503. To change this to the (more correct IMO) status code 429, provide it as part of the options:

C#
builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});

Next, we will need to tell the rate limit middleware when a limit is reached. This is done with a concept called policies. There's a range of different policy types available in the rate-limiting middleware. In this example, I want to limit the number of requests made with the same API key available in the URL like this:

https://localhost:7126/?api_key=mykey

To configure this, include the following policy:

C#
builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    options.AddPolicy("apikey", httpContext =>
    {
        if (httpContext.Request.Query.Keys.Contains("api_key"))
        {
            return RateLimitPartition.GetFixedWindowLimiter(
                httpContext.Request.Query["api_key"].ToString(),
                fac =>
                {
                    return new FixedWindowRateLimiterOptions
                    {
                        Window = TimeSpan.FromHours(1),
                        PermitLimit = 10,
                    };
                });
        }
        else
        {
            return RateLimitPartition.GetNoLimiter("");
        }
    });
});

Let's go through the policy line-by-line. Policies are added by calling the AddPolicy method. The first parameter is a policy name that we will need to reference this policy. This name will be used in the following step. Next, we provide a Func that contains the actual code to run for this policy. In the callback, I check if the current request contains a query parameter named api_key. In this case, we want rate limiting to apply. If not, rate limiting should not be run which is done by returning the result of GetNoLimiter.

This rate limit policy is based on the fixed window limit. As already mentioned, there is a range of different policy types available, so make sure to check out the documentation before picking. The fixed window limiter will rate limit requests within a specified time window. In this example, I permit 10 requests per hour using the same API key. In the real world, your API should probably be able to handle more than 10 requests but for this, I have chosen a low number to easily test this in the browser.

The final step missing is to tell ASP.NET Core to use the middleware and where to apply the policy. There are attributes available to apply this to specific actions and/or controllers, but for this example, I'll apply it to everything:

C#
app.UseRateLimiter();

app
    .MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}")
    .RequireRateLimiting("apikey");

By appending RequestRateLimiting to the controller set up and including the policy name that we set in the previous step, ASP.NET Core will automatically run the policy against all controllers. Remember that only endpoints with an api_key query parameter will apply here.

AspNetCoreRateLimit

That's a very basic example of how rate limiting can be implemented with the Microsoft.AspNetCore.RateLimiting package. Let's quickly revisit the AspNetCoreRateLimit package to see how a similar policy can be implemented with that package.

AspNetCoreRateLimit is another rate-limiting library that has existed for years. It is maintained by Stefan Prodan and offers a very flexible model for implementing rate limiting in ASP.NET Core.

Start by installing the AspNetCoreRateLimit NuGet package. If you are coding along, you can install it in the same project as before but having two rate-limiting packages installed isn't recommended for real code:

dotnet add package AspNetCoreRateLimit

A policy in AspNetCoreRateLimit is implemented as configuration and a client resolver:

C#
public class ElmahIoRateLimitConfiguration : RateLimitConfiguration
{
    public ElmahIoRateLimitConfiguration(
        IOptions<IpRateLimitOptions> ipOptions,
        IOptions<ClientRateLimitOptions> clientOptions)
            : base(ipOptions, clientOptions)
    {
    }

    public override void RegisterResolvers()
    {
        base.RegisterResolvers();
        ClientResolvers.Add(new ClientQueryStringResolveContributor());
    }
}

public class ClientQueryStringResolveContributor : IClientResolveContributor
{
    public Task<string> ResolveClientAsync(HttpContext httpContext)
    {
        var queryDictionary =
            Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(
                httpContext.Request.QueryString.ToString());
        if (queryDictionary.ContainsKey("api_key")
            && !string.IsNullOrWhiteSpace(queryDictionary["api_key"]))
        {
            return Task.FromResult(queryDictionary["api_key"].ToString());
        }

        return Task.FromResult(Guid.NewGuid().ToString());
    }
}

The code in this post has been updated to match the newest version of AspNetCoreRateLimit. For more details on how AspNetCoreRateLimit works, check out the previous post. The code above corresponds to the policy that we specified with the other package based on a query parameter named api_key.

These classes can be configured in the Program.cs file:

C#
builder.Services.AddSingleton<IClientPolicyStore, MemoryCacheClientPolicyStore>();
builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
builder.Services.AddSingleton<IProcessingStrategy, AsyncKeyLockProcessingStrategy>();
builder.Services.AddSingleton<IRateLimitConfiguration, ElmahIoRateLimitConfiguration>();

The additional singletons tell AspNetCoreRateLimit how to store the current state. The limit and timespan from the previous example are provided through options:

C#
builder.Services.Configure<ClientRateLimitOptions>(options =>
{
    options.GeneralRules = new List<RateLimitRule>
    {
        new RateLimitRule
        {
            Endpoint = "*",
            Period = "1h",
            Limit = 10,
        }
    };
});

Again, we limit the requests to 10 per hour. The * value for the Endpoint property matches how we included the rate-limiting middleware to all controllers in the previous example.

The only missing part is calling the UseClientRateLimiting method:

C#
app.UseClientRateLimiting();

That's it! A similar rate limit has now been implemented using AspNetCoreRateLimit. Whether you prefer one package over the other, I will leave it up to you. These examples only show a limited set of capabilities from each package so I would recommend you to try out both and pick the one you prefer.

History

  • 28th November, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
elmah.io
Denmark Denmark
This member doesn't quite have enough reputation to be able to display their biography and homepage.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 1461848814-Oct-23 18:17
Member 1461848814-Oct-23 18:17 
PraiseI would favor .NET 7 Pin
Dirk_Strauss5-Dec-22 11:25
professionalDirk_Strauss5-Dec-22 11:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.