如何在自定义AuthorizeAttribute构造函数中传入ServiceProvider获取DbContext?
问题解答:自定义特性构造函数中获取DbContext的可行方案
首先得明确一个核心限制:你没法在自定义Attribute的构造函数里获取ServiceProvider或DbContext。因为Attribute是作为元数据在程序启动早期(甚至编译阶段)就被实例化的,这时候依赖注入容器还没完成初始化,根本拿不到任何注入的服务。你之前硬编码连接字符串的方式确实不适合生产环境,因为没法利用DI的配置和生命周期管理。
下面给你两种符合需求的类型安全方案,分别对应「独立于授权请求时机」和「请求上下文内处理但解耦授权逻辑」的场景:
方案一:程序启动时批量注册模块(推荐,完全独立于授权请求)
如果你的目标是把所有标记了OmsAuthorizeAttribute的模块信息提前写入数据库,不需要等到第一次授权请求,那可以在程序启动时扫描所有控制器/Action,提取Attribute参数后批量处理。
步骤1:修改OmsAuthorizeAttribute,开放模块信息的访问权限
把私有字段改成带公共getter的属性:
public class OmsAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter { public enum AccessEnablers { PostLogin, Anytime, Default } public string ModuleName { get; } public string ModuleDescription { get; } public AccessEnablers AllowAccess { get; set; } = AccessEnablers.Default; public OmsAuthorizeAttribute(string moduleName, string moduleDescription, AccessEnablers overrideAccess = AccessEnablers.Default) { ModuleName = moduleName; ModuleDescription = moduleDescription; AllowAccess = overrideAccess; } // 原有的OnAuthorization逻辑保持不变... }
步骤2:在Program.cs中启动时扫描并注册
构建完WebApplication后,创建作用域获取DbContext,然后扫描所有带该Attribute的Action:
var app = builder.Build(); // 启动时批量注册所有标记的模块 using (var scope = app.Services.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService<OmsDbContext>(); // 扫描当前程序集下的所有控制器 var controllerTypes = typeof(Program).Assembly.GetTypes() .Where(t => t.IsAssignableTo(typeof(ControllerBase))); foreach (var controllerType in controllerTypes) { // 提取控制器下所有带OmsAuthorizeAttribute的Action var actions = controllerType.GetMethods() .Where(m => m.GetCustomAttributes<OmsAuthorizeAttribute>(inherit: true).Any()); foreach (var action in actions) { var attribute = action.GetCustomAttribute<OmsAuthorizeAttribute>(); if (attribute != null && OmsEnvironment.settings.WizardCompleted) { OmsDbContext.registerModule(attribute.ModuleName, attribute.ModuleDescription, dbContext); } } } } // 后续中间件配置... app.Run();
方案二:用IFilterFactory实现带DI的类型安全特性
如果需要在请求上下文内处理,但不想和授权逻辑强绑定,你可以让OmsAuthorizeAttribute实现IFilterFactory,通过工厂模式创建带DI依赖的Filter实例,既保留类型安全的参数传递,又能获取DbContext。
完整实现代码
public class OmsAuthorizeAttribute : AuthorizeAttribute, IFilterFactory { public enum AccessEnablers { PostLogin, Anytime, Default } public string ModuleName { get; } public string ModuleDescription { get; } public AccessEnablers AllowAccess { get; set; } = AccessEnablers.Default; public OmsAuthorizeAttribute(string moduleName, string moduleDescription, AccessEnablers overrideAccess = AccessEnablers.Default) { ModuleName = moduleName; ModuleDescription = moduleDescription; AllowAccess = overrideAccess; } // 实现IFilterFactory,创建带DI的Filter实例 public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { var dbContext = serviceProvider.GetRequiredService<OmsDbContext>(); var memoryCache = serviceProvider.GetRequiredService<IMemoryCache>(); return new OmsAuthorizeFilter(dbContext, memoryCache, ModuleName, ModuleDescription, AllowAccess); } public bool IsReusable => false; // 实际的授权和模块注册逻辑放在内部Filter类中 private class OmsAuthorizeFilter : IAuthorizationFilter { private readonly OmsDbContext _dbContext; private readonly IMemoryCache _memoryCache; private readonly string _moduleName; private readonly string _moduleDescription; private readonly AccessEnablers _allowAccess; public OmsAuthorizeFilter(OmsDbContext dbContext, IMemoryCache memoryCache, string moduleName, string moduleDescription, AccessEnablers allowAccess) { _dbContext = dbContext; _memoryCache = memoryCache; _moduleName = moduleName; _moduleDescription = moduleDescription; _allowAccess = allowAccess; } public void OnAuthorization(AuthorizationFilterContext context) { // 模块注册逻辑:用缓存确保只执行一次 if (OmsEnvironment.settings.WizardCompleted) { var cacheKey = $"ModuleRegistered_{_moduleName}"; if (!_memoryCache.TryGetValue(cacheKey, out _)) { OmsDbContext.registerModule(_moduleName, _moduleDescription, _dbContext); _memoryCache.Set(cacheKey, true, TimeSpan.FromDays(30)); } } // 原有的授权逻辑 Printer.print("Authorization requested for module: " + _moduleName); if (!context.HttpContext.User.Identity.IsAuthenticated && _allowAccess != AccessEnablers.Anytime) context.Result = new RedirectToActionResult("Login", "Account", context); else { // 自定义授权逻辑 } } } }
这种方式下,你依然可以像原来一样使用[OmsAuthorize("模块名", "描述")]标记Action,完全保留类型安全,同时还能通过DI获取DbContext和其他服务。
内容的提问来源于stack exchange,提问作者ROMSCore
相关产品推荐
相关产品推荐

