Skip to content

Microsoft.AspNetCore.RateLimiting RateLimiterPolicy GetPartitionAsync  #45603

@cristipufu

Description

@cristipufu

Background and Motivation

I want to save API rate limits in the database and have the possibility of changing them at runtime (eg: based on the customer's subscription/licensing/etc).

The problem is that IRateLimiterPolicy doesn't have an async method

public class ClientIdRateLimiterPolicy : IRateLimiterPolicy<string>
{
    private readonly IServiceProvider _serviceProvider;

    public ClientIdRateLimiterPolicy(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public RateLimitPartition<string> GetPartition(HttpContext httpContext)
    {
        var clientId = httpContext.Request.Headers["X-ClientId"].ToString();

        using var scope = _serviceProvider.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<SampleDbContext>();

        var rateLimit = dbContext.Clients.Where(x => x.Identifier == clientId).Select(x => x.RateLimit).FirstOrDefault();

        return RateLimitPartition.GetConcurrencyRateLimiter(clientId, key => new ConcurrencyRateLimiterOptions
        {
            PermitLimit = rateLimit?.PermitLimit ?? 1
        });
    }
}

Proposed API

public interface IRateLimiterPolicy<TPartitionKey>
{
    Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected { get; }

-   RateLimitPartition<TPartitionKey> GetPartition(HttpContext httpContext);
    
+   ValueTask<RateLimitPartition<TPartitionKey>> GetPartitionAsync(HttpContext httpContext);
}

Usage Examples

public class ClientIdRateLimiterPolicy : IRateLimiterPolicy<string>
{
    private readonly IServiceProvider _serviceProvider;

    public ClientIdRateLimiterPolicy(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async ValueTask<RateLimitPartition<string>> GetPartitionAsync(HttpContext httpContext)
    {
        var clientId = httpContext.Request.Headers["X-ClientId"].ToString();

        using var scope = _serviceProvider.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<SampleDbContext>();

        var rateLimit = await redisCache.GetAsync(clientId);
        var rateLimit = await dbContext.Clients.Where(x => x.Identifier == clientId).Select(x => x.RateLimit).FirstOrDefaultAsync();

        return RateLimitPartition.GetConcurrencyRateLimiter(clientId, key => new ConcurrencyRateLimiterOptions
        {
            PermitLimit = rateLimit?.PermitLimit ?? 1
        });
    }
}

Risks

Find a way w/o breaking changes

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-middlewareIncludes: URL rewrite, redirect, response cache/compression, session, and other general middlewaresfeature-rate-limitWork related to use of rate limit primitives

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions