You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过控制器约定/自定义提供程序启用限流(无需逐个添加EnableRateLimiting特性)

如何通过控制器约定/自定义提供程序启用限流(无需逐个添加EnableRateLimiting特性)

我太懂这种痛点了——要给几十上百个控制器挨个加[EnableRateLimiting]不仅繁琐,还容易漏加。而且你说的IControllerModelConvention里Attributes集合只读的问题,确实是个容易踩的坑。不过别慌,有几个实用的方案能帮你批量搞定这个需求,原理都是绕开只读的Attributes集合,直接从筛选器管道入手。

方案一:用IControllerModelConvention添加限流筛选器(最推荐)

虽然ControllerModel.Attributes是只读的,但限流特性本质上是一个IFilterMetadata实现类。我们完全可以跳过修改特性集合,直接把EnableRateLimitingAttribute的实例加到控制器的Filters集合里——效果和在类上标记特性一模一样。

假设你的控制器都带有自定义的标记特性(比如[MyApiControllerMarker]),可以这样写约定:

public class BatchRateLimitConvention : IControllerModelConvention
{
    private readonly string _rateLimitPolicyName;

    public BatchRateLimitConvention(string rateLimitPolicyName)
    {
        _rateLimitPolicyName = rateLimitPolicyName;
    }

    public void Apply(ControllerModel controller)
    {
        // 检查当前控制器是否带有你的自定义标记特性
        var hasMarker = controller.ControllerType
            .GetCustomAttributes(typeof(MyApiControllerMarkerAttribute), inherit: true)
            .Any();

        if (hasMarker)
        {
            // 直接添加限流筛选器,替代手动加特性
            controller.Filters.Add(new EnableRateLimitingAttribute(_rateLimitPolicyName));
            
            // 可选:如果控制器上有[DisableRateLimiting],可以在这里移除它
            var disableFilter = controller.Filters
                .OfType<DisableRateLimitingAttribute>()
                .FirstOrDefault();
            if (disableFilter != null)
            {
                controller.Filters.Remove(disableFilter);
            }
        }
    }
}

然后在Program.cs里注册这个约定:

builder.Services.AddControllers(options =>
{
    // 传入你定义好的限流策略名称
    options.Conventions.Add(new BatchRateLimitConvention("DefaultApiRateLimit"));
});

方案二:用IApplicationModelProvider做底层修改

如果你的需求更复杂(比如要同时修改控制器和操作的元数据),可以用更底层的IApplicationModelProvider扩展点,它能覆盖整个应用模型的构建过程:

public class RateLimitApplicationModelProvider : IApplicationModelProvider
{
    private readonly string _policyName;
    // 控制执行顺序,0表示在默认提供程序之前执行
    public int Order => 0;

    public RateLimitApplicationModelProvider(string policyName)
    {
        _policyName = policyName;
    }

    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {
        foreach (var controller in context.Result.Controllers)
        {
            // 这里可以替换成你的匹配逻辑:比如基类、命名空间、标记特性
            if (controller.ControllerType.Namespace.StartsWith("YourApp.Api.V1"))
            {
                controller.Filters.Add(new EnableRateLimitingAttribute(_policyName));
            }
        }
    }

    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
        // 后续处理逻辑(一般不需要)
    }
}

注册方式稍微不同,需要把它添加到服务集合中:

builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, RateLimitApplicationModelProvider>(sp =>
    new RateLimitApplicationModelProvider("DefaultApiRateLimit")));

方案三:按规则匹配控制器(无需标记特性)

如果你不需要标记特性,也可以通过控制器的基类、命名空间或名称规则来批量匹配:

比如给所有继承自BaseApiController的控制器加限流:

public void Apply(ControllerModel controller)
{
    if (typeof(BaseApiController).IsAssignableFrom(controller.ControllerType))
    {
        controller.Filters.Add(new EnableRateLimitingAttribute("DefaultApiRateLimit"));
    }
}

或者给某个命名空间下的所有控制器加限流:

public void Apply(ControllerModel controller)
{
    if (controller.ControllerType.Namespace?.Contains("YourApp.Api.Admin") ?? false)
    {
        controller.Filters.Add(new EnableRateLimitingAttribute("AdminRateLimit"));
    }
}

关键原理说明

你之前遇到的Attributes集合只读问题,是因为这个集合是控制器类型上原始反射得到的特性集合,ASP.NET Core不允许修改它。但实际上,特性的核心作用是向框架提供元数据,最终会被转换成筛选器加入管道。我们直接操作Filters集合,就绕开了只读限制,效果和标记特性完全一致。

注意事项

  • 确保你已经在Program.cs中正确配置了限流策略,比如:
    builder.Services.AddRateLimiter(options =>
    {
        options.AddPolicy("DefaultApiRateLimit", context =>
            RateLimitPartition.GetFixedWindowLimiter(
                partitionKey: context.User.Identity?.Name ?? context.Request.HttpContext.Connection.RemoteIpAddress?.ToString(),
                factory: _ => new FixedWindowRateLimiterOptions
                {
                    AutoReplenishment = true,
                    PermitLimit = 10,
                    QueueLimit = 0,
                    Window = TimeSpan.FromMinutes(1)
                }));
    });
    
  • 如果有个别控制器需要排除限流,可以在约定里添加排除逻辑(比如检查是否有[SkipRateLimit]自定义特性)。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 07:03:11