将服务注入动作过滤器

Posted

技术标签:

【中文标题】将服务注入动作过滤器【英文标题】:Inject service into Action Filter 【发布时间】:2016-07-06 15:56:13 【问题描述】:

我正在尝试将服务注入到我的操作过滤器中,但我没有在构造函数中注入所需的服务。这是我所拥有的:

public class EnsureUserLoggedIn : ActionFilterAttribute

    private readonly ISessionService _sessionService;

    public EnsureUserLoggedIn()
    
        // I was unable able to remove the default ctor 
        // because of compilation error while using the 
        // attribute in my controller
    

    public EnsureUserLoggedIn(ISessionService sessionService)
    
        _sessionService = sessionService;
    

    public override void OnActionExecuting(ActionExecutingContext context)
    
        // Problem: _sessionService is null here
        if (_sessionService.LoggedInUser == null)
        
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            context.Result = new JsonResult("Unauthorized");
        
    

我正在像这样装饰我的控制器:

[Route("api/issues"), EnsureUserLoggedIn]
public class IssueController : Controller


Startup.cs

services.AddScoped<ISessionService, SessionService>();

【问题讨论】:

属性装饰只允许常量值。我想你应该在默认构造函数中解析你的服务。 我不确定在这种情况下怎么可能。 看这里,我猜你只需要一个decorator 不要尝试将服务注入属性,而是写passive attributes。 您不应该为授权/策略实现自己的过滤器或授权属性。这就是AuthoirzeAttribute 的策略生成器和策略属性。请参阅负责 ASP.NET Core 安全部分的 ASP.NET 开发人员的回答 ***.com/a/31465227/455493 【参考方案1】:

使用这些文章作为参考:

ASP.NET Core Action Filters

Action filters, service filters and type filters in ASP.NET 5 and MVC 6

将过滤器用作 ServiceFilter

由于过滤器将用作ServiceType,因此需要向框架IoC注册。如果直接使用操作过滤器,则不需要这样做。

Startup.cs

public void ConfigureServices(IServiceCollection services) 
    services.AddMvc();

    services.AddScoped<ISessionService, SessionService>();
    services.AddScoped<EnsureUserLoggedIn>();

    ...

使用 ServiceFilter 属性将自定义过滤器添加到 MVC 控制器方法和控制器类,如下所示:

[ServiceFilter(typeof(EnsureUserLoggedIn))]
[Route("api/issues")]
public class IssueController : Controller 
    // GET: api/issues
    [HttpGet]
    [ServiceFilter(typeof(EnsureUserLoggedIn))]
    public IEnumerable<string> Get()...

还有其他的例子

将过滤器用作全局过滤器

将过滤器与基本控制器一起使用

按顺序使用过滤器

看看,试一试,看看是否能解决您的问题。

希望这会有所帮助。

【讨论】:

是的,这行得通。之前对 ServiceFilter 属性不熟悉。那是我的代码中缺少的部分。谢谢。 您不应该以这种方式进行授权策略检查,这不是预期的方式。您应该使用即 Cookie 授权或其他一些授权类型(例如 jwt),然后将 AuthorizeAttribute 与您在应用程序启动时设置的策略一起使用。检查我上面的评论 有趣的是,全局过滤器似乎不允许 DI,请参阅:github.com/damienbod/AspNet5Filters/blob/… - 博客的作者实际上将依赖关系固定在那里。 找到了一篇真正涵盖全局过滤器+依赖注入的博客:weblogs.asp.net/ricardoperes/… 如果您需要在属性上指定属性,这将如何工作?【参考方案2】:

全局过滤器

你需要实现IFilterFactory:

public class AuthorizationFilterFactory : IFilterFactory

    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    
        // manually find and inject necessary dependencies.
        var context = (IMyContext)serviceProvider.GetService(typeof(IMyContext));
        return new AuthorizationFilter(context);
    

Startup 类中注册过滤器工厂而不是注册实际过滤器:

services.AddMvc(options =>

    options.Filters.Add(new AuthorizationFilterFactory());
);

【讨论】:

谢谢!这不是我自己很容易找到的明显解决方案。 @StephenM.Redd 确保它不可重用。否则它将使用已释放的上下文。【参考方案3】:

解决此问题的另一种方法。您可以通过 Context 获取服务,如下代码所示:

public override void OnActionExecuting(ActionExecutingContext context)

    _sessionService = context.HttpContext.RequestServices.GetService<ISessionService>();
    if (_sessionService.LoggedInUser == null)
    
        context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        context.Result = new JsonResult("Unauthorized");
    

请注意,您必须在 Startup.cs 中注册此服务

services.AddTransient<ISessionService, SessionService>();

【讨论】:

我不能使用 GetService 。它告诉我使用 Microsoft.Extensions.DependencyInjection.ServerProviderServiceExtensions.GetService,但我似乎找不到可用的。 这对我来说似乎是这个小调整的最佳解决方案: var service = context.HttpContext.RequestServices.GetService(typeof(IMyService)) as IMyService; 这可能有效,但它使用的是有时被认为是反模式的服务定位器模式【参考方案4】:

例子

private ILoginService _loginService;

public override void OnActionExecuting(ActionExecutingContext context)
        
            _loginService = (ILoginService)context.HttpContext.RequestServices.GetService(typeof(ILoginService));
        

希望对你有帮助。

【讨论】:

简单示例,但直截了当。我一直在寻找的那个。通过使用 (T)context.HttpContext.RequestServices.GetService(typeof(T)) 它解决了依赖关系。干得好。【参考方案5】:

看了这篇文章ASP.NET Core - Real-World ASP.NET Core MVC Filters (Aug 2016)我是这样实现的:

在 Starup.cs / ConfigureServices 中:

services.AddScoped<MyService>();

在 MyFilterAttribute.cs 中:

public class MyFilterAttribute : TypeFilterAttribute
        
    public MyFilterAttribute() : base(typeof (MyFilterAttributeImpl))
    

    

    private class MyFilterAttributeImpl : IActionFilter
    
        private readonly MyService _sv;

        public MyFilterAttributeImpl(MyService sv)
        
            _sv = sv;
        

        public void OnActionExecuting(ActionExecutingContext context)
                        
            _sv.MyServiceMethod1();
        

        public void OnActionExecuted(ActionExecutedContext context)
        
            _sv.MyServiceMethod2();
        
    

在 MyFooController.cs 中:

[MyFilter]
public IActionResult MyAction()


编辑:可以使用 TypeFilterAttribute 类的 Arguments 属性来传递 [MyFilter("Something")] 之类的参数:How do I add a parameter to an action filter in asp.net?(rboe 的代码还显示了如何注入东西(同样的方式))

【讨论】:

【参考方案6】:

虽然问题隐含地提到“通过属性过滤”,但仍然值得强调的是,“按类型全局”添加过滤器支持开箱即用的 DI:

[对于按类型添加的全局过滤器] 任何构造函数依赖项都将通过依赖注入 (DI) 填充。按类型添加过滤器等价于 filters.Add(new TypeFilterAttribute(typeof(MyFilter)))。 https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection

关于基于属性的过滤器:

作为属性实现并直接添加到控制器类或操作方法的过滤器不能具有依赖注入 (DI) 提供的构造函数依赖。这是因为属性必须在应用它们的地方提供其构造函数参数。这是属性如何工作的限制。 https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection

但是,正如前面对 OP 的回答中提到的,有一些间接方法可用于实现 DI。为了完整起见,这里是官方文档的链接:

ServiceFilterAttribute TypeFilterAttribute IFilterFactory implemented on your attribute

【讨论】:

以上是关于将服务注入动作过滤器的主要内容,如果未能解决你的问题,请参考以下文章

Autofac - ASP.NET Core 中的动作过滤器中的属性注入

将对象注入过滤器

006_过滤器

动作过滤器:如何调用服务层和异步方法

JSP使用过滤器防止SQL注入

自定义页面过滤器中的 ASP .NET Core 注入服务