Проблема с методом File - он возвращает ленивый ответ. Т.е. он не читает весь поток сразу, а ждет пока будет вызван метод от IActionResult, поэтому семафор берется и сразу же отпускается.
Чтобы эту проблему решить надо знать, когда файл точно отправлен.
Предлагаю сделать декоратор специальный. Например:
[ApiController]
[Route("[controller]")]
public class SampleController : ControllerBase
{
private readonly IRateLimiter _rateLimiter;
public SampleController(IRateLimiter rateLimiter)
{
_rateLimiter = rateLimiter;
}
[HttpGet("connection")]
public async Task<IActionResult> DownLoadFile(Guid file)
{
var stream = await GetFileStream(file);
return new RateLimiterFileActionResult(File(stream, "content/type", "sample.txt"), _rateLimiter);
}
}
class RateLimiterFileActionResult : IActionResult
{
private readonly IActionResult _actionResultImplementation;
private readonly IRateLimiter _rateLimiter;
public RateLimiterFileActionResult(IActionResult actionResultImplementation, IRateLimiter rateLimiter)
{
_actionResultImplementation = actionResultImplementation;
_rateLimiter = rateLimiter;
}
public async Task ExecuteResultAsync(ActionContext context)
{
try
{
await _rateLimiter.ObtainAsync(context.HttpContext.RequestAborted);
await _actionResultImplementation.ExecuteResultAsync(context);
}
finally
{
await _rateLimiter.ReleaseAsync(context.HttpContext.RequestAborted);
}
}
}
public interface IRateLimiter
{
public Task ObtainAsync(CancellationToken token);
public Task ReleaseAsync(CancellationToken token);
}
Здесь используется не семафор, а специальный интерфейс, хотя разницы особо нет.
Также, стоит заметить, что блокировка берется внутри метода декоратора, а не в методе контроллера - например, пользователь отменит запрос и тогда надо будет думать как семафор отпустить - в этой реализации думать не надо.