如何向自定义MyRouteAttribute中注入IOptions依赖?
如何向自定义路由特性
MyRouteAttribute注入IOptions? 嘿,这个问题挺典型的——因为特性是编译时实例化的,没法直接通过构造函数注入IOptions这种运行时依赖。不过我们有几种靠谱的解决方案,我给你拆解一下:
方案一:使用IApplicationModelProvider(推荐,符合DI最佳实践)
这种方式通过ASP.NET Core的应用模型扩展点,在运行时动态修改控制器的路由模板,同时可以轻松注入IOptions获取配置。
步骤1:定义配置类
先创建对应配置的实体类,用来绑定你的路由配置:
public class ApiRouteSettings { // 默认值和你原来的硬编码保持一致 public string BaseRoute { get; set; } = "api/default"; }
步骤2:注册配置和IOptions
在Program.cs里把配置绑定到IOptions:
// 假设你的配置在appsettings.json的"ApiRouteSettings"节点下 builder.Services.Configure<ApiRouteSettings>(builder.Configuration.GetSection("ApiRouteSettings"));
步骤3:改造MyRouteAttribute
把原来继承RouteAttribute的逻辑去掉,让它只作为一个标记特性,用来识别需要自定义路由的控制器:
public class MyRouteAttribute : Attribute { // 保留原来的路由参数逻辑 public string Route { get; } public MyRouteAttribute() : this(string.Empty) { } public MyRouteAttribute(string route) { Route = route; } }
步骤4:实现自定义IApplicationModelProvider
这个类会在运行时扫描控制器上的MyRouteAttribute,并利用IOptions的配置动态生成路由模板:
public class CustomRouteModelProvider : IApplicationModelProvider { private readonly IOptions<ApiRouteSettings> _routeSettings; // 直接通过构造函数注入IOptions,完美符合DI规范 public CustomRouteModelProvider(IOptions<ApiRouteSettings> routeSettings) { _routeSettings = routeSettings; } // 设置优先级,确保在默认路由提供者之前执行 public int Order => -1000; public void OnProvidersExecuting(ApplicationModelProviderContext context) { foreach (var controller in context.Result.Controllers) { var myRouteAttr = controller.Attributes.OfType<MyRouteAttribute>().FirstOrDefault(); if (myRouteAttr == null) continue; // 移除控制器上可能存在的默认RouteAttribute var existingRoute = controller.Attributes.OfType<RouteAttribute>().FirstOrDefault(); if (existingRoute != null) { controller.Attributes.Remove(existingRoute); } // 从IOptions获取配置,生成最终路由模板 var baseRoute = _routeSettings.Value.BaseRoute; var finalRoute = string.IsNullOrEmpty(myRouteAttr.Route) ? baseRoute : $"{baseRoute}/{myRouteAttr.Route}"; // 给控制器添加动态生成的RouteAttribute controller.Attributes.Add(new RouteAttribute(finalRoute)); } } public void OnProvidersExecuted(ApplicationModelProviderContext context) { // 无需额外处理 } }
步骤5:注册自定义提供者
在Program.cs里把这个提供者注册到DI容器:
builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, CustomRouteModelProvider>());
控制器使用方式不变
你的控制器代码不需要改动,和原来一样使用[MyRoute]或[MyRoute("xxx")]即可:
[MyRoute] public class MyController : Controller { // 控制器逻辑... } // 带自定义路由片段的用法 [MyRoute("users")] public class UserController : Controller { // 控制器逻辑... }
方案二:使用IHttpContextAccessor(不推荐,服务定位器模式)
如果因为某些原因必须在MyRouteAttribute内部直接获取IOptions,可以通过IHttpContextAccessor间接获取,但这种方式属于服务定位器模式,不符合DI的最佳实践,仅作为备选:
步骤1:注册IHttpContextAccessor
builder.Services.AddHttpContextAccessor();
步骤2:改造MyRouteAttribute
通过静态方法初始化依赖,然后在构造时获取IOptions:
public class MyRouteAttribute : RouteAttribute { private static IOptions<ApiRouteSettings> _routeSettings; // 静态初始化方法,在程序启动时调用 public static void Initialize(IServiceProvider serviceProvider) { _routeSettings = serviceProvider.GetRequiredService<IOptions<ApiRouteSettings>>(); } public MyRouteAttribute() : base(GetBaseRoute()) { } public MyRouteAttribute(string route) : base(GetFullRoute(route)) { } private static string GetBaseRoute() { // 配置不存在时回退到默认值 return _routeSettings?.Value.BaseRoute ?? "api/default"; } private static string GetFullRoute(string route) { var baseRoute = GetBaseRoute(); return string.IsNullOrEmpty(route) ? baseRoute : $"{baseRoute}/{route}"; } }
步骤3:在程序启动时初始化特性
var app = builder.Build(); // 初始化特性的依赖 MyRouteAttribute.Initialize(app.Services); // 后续中间件配置...
内容的提问来源于stack exchange,提问作者Esen
相关产品推荐
相关产品推荐

