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

抽象类HttpDelete特性中nameof(泛型参数)失效,如何生成正确CRUD路由?

问题场景与代码

我尝试编写通用的基础CRUD端点代码,以下是删除端点的简化抽象类实现:

public abstract class DeleteEndpoint<TDomain, TId> : ...
{
    [HttpDelete(nameof(TDomain) + "/{id}")]
    public async Task<IActionResult> DeleteAsync(TId id)
    {
        // 业务逻辑实现
    }
}

具体子类继承实现如下:

public class ConcreteTypeDeleteEndpoint : Base.Crud.DeleteEndpoint<ConcreteType, int>
{
}

期望结果:HttpDelete的路由路径为ConcreteType/{id}
实际结果:路由路径为TDomain/{id}

请问nameof是否支持泛型参数?如果不支持,该如何实现预期效果?


解答

关于nameof对泛型参数的支持

nameof是编译时求值的运算符,它只能获取符号本身的名称字符串。对于泛型参数TDomain,编译时它只是一个类型占位符的名称,因此nameof(TDomain)会直接返回字符串"TDomain",无法识别运行时传入的具体类型,所以不支持这种场景。

实现预期路由的两种方案

方案1:自定义控制器路由约定(推荐)

利用ASP.NET Core的控制器模型约定(ControllerModelConvention),在应用启动时通过反射扫描所有继承自DeleteEndpoint<,>的控制器,动态修改路由模板。

步骤1:实现路由约定类

public class GenericCrudRouteConvention : IControllerModelConvention
{
    public void Apply(ControllerModel controller)
    {
        // 获取控制器的泛型基类
        var baseGenericType = controller.ControllerType.BaseType;
        if (baseGenericType == null || !baseGenericType.IsGenericType)
            return;

        // 检查是否继承自目标抽象类
        var genericDefinition = baseGenericType.GetGenericTypeDefinition();
        if (genericDefinition != typeof(DeleteEndpoint<,>))
            return;

        // 获取实际传入的TDomain类型
        var domainType = baseGenericType.GetGenericArguments()[0];
        var targetRoute = $"{domainType.Name}/{{id}}";

        // 找到DeleteAsync方法并修改HttpDelete属性的路由模板
        var deleteAction = controller.Actions.FirstOrDefault(a => a.ActionName == nameof(DeleteEndpoint<object, object>.DeleteAsync));
        if (deleteAction == null)
            return;

        var httpDeleteAttr = deleteAction.Attributes.OfType<HttpDeleteAttribute>().FirstOrDefault();
        if (httpDeleteAttr != null)
        {
            httpDeleteAttr.Template = targetRoute;
        }
    }
}

步骤2:注册路由约定

在Program.cs中添加约定到控制器配置:

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

这种方式完全保留了抽象类的通用性,无需在子类中重复编写路由配置。

方案2:子类显式指定路由(简单直接)

如果不想引入复杂的约定逻辑,可以在每个具体子类中重写DeleteAsync方法并显式指定路由:

public class ConcreteTypeDeleteEndpoint : Base.Crud.DeleteEndpoint<ConcreteType, int>
{
    [HttpDelete("ConcreteType/{id}")]
    public new async Task<IActionResult> DeleteAsync(int id)
    {
        return await base.DeleteAsync(id);
    }
}

缺点是需要在每个子类中重复代码,失去了部分抽象类的复用价值,适合小型项目或子类数量较少的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 10:21:38