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

如何为ASP.NET控制器方法创建组合多特性的自定义特性?

为ASP.NET控制器方法创建组合式自定义特性

编写ASP.NET控制器时,常需为Action方法重复添加多个特性。若能创建可自动组合其他特性的自定义特性,能大幅保持代码整洁。已有方案通过System.ComponentModel.PropertyDescriptor为属性实现类似功能,但因不存在System.ComponentModel.MethodDescriptor,无法直接移植到方法场景。下面说明如何为方法实现该功能:


实现思路

ASP.NET Core的特性处理依赖元数据提供程序(MetadataProvider),我们可以通过自定义IActionModelConvention扫描控制器方法上的自定义组合特性,将其拆解为内部包含的多个特性并添加到方法元数据中。

具体步骤

1. 定义组合特性基类

先创建一个基类,让所有组合特性继承它,方便后续识别:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public abstract class CompositeAttribute : Attribute
{
    // 由子类返回需要组合的特性集合
    public abstract IEnumerable<Attribute> GetAttributes();
}

2. 创建具体的组合特性

针对示例中的重复特性,创建针对性的组合特性:

// 组合HttpPost+200OK+500错误响应的特性
[<AttributeUsage(AttributeTargets.Method, AllowMultiple = false)>]
type ApiCommandAttribute(route: string) =
    inherit CompositeAttribute()
    override _.GetAttributes() =
        seq {
            yield HttpPostAttribute(route)
            yield ProducesResponseTypeAttribute(StatusCodes.Status200OK)
            yield ProducesResponseTypeAttribute(typeof<Dto.Exception>, StatusCodes.Status500InternalServerError)
        }

// 组合HttpGet+指定响应类型+500错误的特性
[<AttributeUsage(AttributeTargets.Method, AllowMultiple = false)>]
type ApiQueryAttribute(route: string, responseType: Type) =
    inherit CompositeAttribute()
    override _.GetAttributes() =
        seq {
            yield HttpGetAttribute(route)
            yield ProducesResponseTypeAttribute(responseType, StatusCodes.Status200OK)
            yield ProducesResponseTypeAttribute(typeof<Dto.Exception>, StatusCodes.Status500InternalServerError)
        }

3. 实现约定类处理组合特性

创建IActionModelConvention实现,扫描Action方法的组合特性并替换为内部特性:

public class CompositeAttributeConvention : IActionModelConvention
{
    public void Apply(ActionModel action)
    {
        var compositeAttributes = action.Attributes.OfType<CompositeAttribute>().ToList();
        if (!compositeAttributes.Any()) return;

        foreach (var compositeAttr in compositeAttributes)
        {
            // 添加组合特性包含的所有子特性
            foreach (var attr in compositeAttr.GetAttributes())
            {
                action.Attributes.Add(attr);
                action.Selectors.ForEach(s => s.EndpointMetadata.Add(attr));
            }
            // 移除原组合特性(可选)
            action.Attributes.Remove(compositeAttr);
            action.Selectors.ForEach(s => s.EndpointMetadata.Remove(compositeAttr));
        }
    }
}

4. 注册约定到ASP.NET Core

在Program.cs中注册自定义约定:

builder.Services.AddControllers(options =>
{
    options.Conventions.Add(new CompositeAttributeConvention());
});

5. 在控制器中使用组合特性

简化后的控制器代码:

[<ApiQuery("list-products", typeof<Dto.Product list>)>]
member this.ListProducts() = this.Http(Market.Product.all() |> AsyncSeq.map Dto.Of)

[<ApiQuery("find-product", typeof<Dto.Product>)>]
[<ProducesResponseType(StatusCodes.Status404NotFound)>]
member this.FindProduct([<Required>] product: string) = this.Http(Market.Product.withName_ product |> Async.map (Option.map Dto.Of >> Option.toObj))

[<ApiCommand("start-feed")>]
member this.StartFeed([<Required>] contract: string) = this.ForwardToService(MineService, StartFeed contract)

[<ApiCommand("stop-feed")>]
member this.StopFeed([<Required>] contract: string) = this.ForwardToService(MineService, StopFeed contract)

[<ApiCommand("recalc-bars")>]
member this.RecalcBars([<Required>] product: string, fromUtc_: Nullable<DateTime>) = this.ForwardToService(MineService, RecalcBars (product, Option.ofNullable fromUtc_))

[<ApiCommand("reset-chart")>]
member this.ResetChart([<Required>] product: string) = this.ForwardToService(ChartService, ResetChart product)

关键说明

  • 该方式依赖ASP.NET Core的模型约定,在应用启动时处理所有控制器元数据,无额外运行时开销。
  • 可根据需求扩展组合特性,比如添加权限验证、日志等特性的组合。
  • 组合特性支持参数传递(如路由、响应类型),只需在构造函数中接收参数并传递给内部特性即可。

内容的提问来源于stack exchange,提问作者Franco Tiveron

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 13:05:30