ASP.NET Core Rate Limiting with gRPC

ASP.NET Core with .NET 7 adds an interesting new feature: rate limiting. In this post, I will look into how to apply rate limiting for gRPC services with ASP.NET Core.

At the time of writing this post, the code samples are based on .NET 7 RC2.

Rate Limiting with ASP.NET Core WebApi

We can find multiple demos on applying Rate Limiting on minimal APIs. However, it is less frequently demo-ed on how to apply rate limiting on controllers. Fortunately, the documentation details this scenario. Let me quickly recap.

Add a rate limiter to the services. Here I add a sliding window rate limiter.

builder.Services.AddRateLimiter(options =>
{
    options.AddSlidingWindowLimiter("myLimiter", options =>
    {
        options.Window = TimeSpan.FromSeconds(10);
        options.PermitLimit = 3;
        options.SegmentsPerWindow = 1;
    });
});

Add the rate limiter middleware to the HTTP pipeline:

app.UseRateLimiter();

Apply [EnableRateLimiting("myLimiter")] or [DisableRateLimiting] at Controller level or on the action methods:

[HttpGet]
[EnableRateLimiting("myLimiter")]
public int Get()
{
    return 1;
}

While [EnableRateLimiting] adds a policy to a certain action or controller, we can use the [DisableRateLimiting] attribute to disable the rate limiter: for example [EnableRateLimiting] may be added at the controller level, but a certain action should exempt of rate limiting; in this case add the [DisableRateLimiting] to the action that should not have the rate limiter.

Rate Limiting with gRPC

As gRPC in .NET7 services also builds on top of ASP.NET Core, let us review how we can setup rate limiting for such services. The same way as for webapi project, I register the rate limiting dependencies:

builder.Services.AddRateLimiter(options =>
{
    options.AddSlidingWindowLimiter("myLimiter", options =>
    {
        options.Window = TimeSpan.FromSeconds(10);
        options.PermitLimit = 3;
        options.SegmentsPerWindow = 1;
    });
});

In this example, a simple sliding window rate limiter is added. This rate limiter allows three requests through in a 10-second-long window. Above the limit (when there is no queue limit set), the default behavior returns 503 Service Unavailable response.

Next add the rate limiter to the HTTP pipeline:

app.UseRateLimiter();

To enable Rate Limiting for a gRPC service, we have multiple options. A similar approach to minimal API, we can add RequireRateLimiting on the IEndpointConventionBuilder in this case this is GrpcServiceEndpointConventionBuilder:

app.MapGrpcService<SuperServiceImpl>().RequireRateLimiting("myLimiter");

Note, that the name of the rate limiting policy must be provided for the RequireRateLimiting method. However, we can also use attributes, similar as in controllers: add [EnableRateLimiting("myLimiter")] or [DisableRateLimiting] on the actual implementation of the generated gRPC base class.

[EnableRateLimiting("myLimiter")]
public class SuperServiceImpl : SuperService.SuperServiceBase
{
    public override async Task<ResponseData> DoWork(RequestData request, ServerCallContext context)
    {
        return new ResponseData { Message = $"Hello {request.Message}" };
    }
}

We can add the above attributes onto the actual method implementation as well:

[EnableRateLimiting("myLimiter")]
public override async Task<ResponseData> DoWork(RequestData request, ServerCallContext context)
{
    return new ResponseData { Message = $"Hello {request.Message}" };
}

When the rate limiter rejects a request, that manifests as an RpcException on a C# client side, with 503 Unavailable status code:

Grpc.Core.RpcException: 'Status(StatusCode="Unavailable", Detail="Bad gRPC response. HTTP status code: 503")'

Conclusion

Users can apply Rate Limiting on ASP.NET Core gRPC services on a similar approach as for webapis.