抽象类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
相关产品推荐
相关产品推荐

