将服务注入到操作过滤器中

72

我正在尝试将服务注入到我的操作筛选器中,但是在构造函数中没有得到所需的服务注入。这是我的代码:

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
{
}

启动.cs

services.AddScoped<ISessionService, SessionService>();

属性装饰器只允许常量值。我猜你应该在默认构造函数中解决你的服务。 - T-moty
我不确定在这种情况下如何实现。 - hyde
1
看这里,我猜你只需要一个装饰器 - T-moty
我已经有一个可行的解决方案 - 我正在更新它以适用于ASP.NET Core。希望几分钟内能有结果。 - Scott Hannen
3
不要试图将服务注入属性中,而是使用被动属性的编写方式。 - Mark Seemann
3
不应该为授权/策略实现自己的过滤器或授权属性。这就是AuthorizeAttribute的策略构建器和策略属性的作用。请参考这位负责ASP.NET Core安全部分的ASP.NET开发人员的答案:https://dev59.com/CVwZ5IYBdhLWcg3wbv7r#31465227 - Tseng
6个回答

81

参考这些文章:

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(){...}
}

还有其他例子:

  • 将过滤器用作全局过滤器

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

  • 使用带有顺序的过滤器

请尝试这些方法,看是否解决了您的问题。

希望能帮到您。


1
是的,这个方法可行。我之前不熟悉ServiceFilter属性,这就是我代码中缺失的部分。谢谢你。 - hyde
1
你不应该这样进行授权策略检查,这不是它的预期方式。你应该使用例如Cookie授权或其他授权类型(例如jwt),然后在应用程序启动时使用AuthorizeAttribute与你设置的策略进行检查。请查看我上面的评论。 - Tseng
有趣的是,全局过滤器似乎不允许 DI,请参见:https://github.com/damienbod/AspNet5Filters/blob/e2949743baa529a2e645abe66116c82543d10e03/src/AspNet5Filters/Startup.cs#L37 - 博客作者实际上在那里自己硬编码依赖项。 - Stefan Hendriks
发现了一篇真正涵盖全局过滤器和依赖注入的博客:http://weblogs.asp.net/ricardoperes/asp-net-core-inversion-of-control-and-dependency-injection - Stefan Hendriks
4
如果您需要在属性上指定属性值,那么这个机制是如何工作的? - Jeremy Holovacs

36

解决这个问题的另一种方法是,可以通过以下代码中的上下文(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<IMyService>。它提示我使用 Microsoft.Extensions.DependencyInjection.ServerProviderServiceExtensions.GetService,但似乎我找不到可用的版本。 - SventoryMang
2
这看起来是最好的解决方案,只需进行小调整:var service = context.HttpContext.RequestServices.GetService(typeof(IMyService)) as IMyService; - BrokeMyLegBiking
3
这个方法或许能够起作用,但它使用了有时被认为是反模式的服务定位器模式。 - Anjani

35

全局过滤器

你需要实现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());
});

1
谢谢!这个解决方案对我来说并不明显,我自己很难找到。 - Stephen M. Redd
@StephenM.Redd 请确保这不是可重用的。否则会使用已释放的上下文。 - Andrei

12

例子

private ILoginService _loginService;

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

希望这有所帮助。


2
简单的样例,但是很直接。这就是我一直在寻找的正确方法。通过使用(T)context.HttpContext.RequestServices.GetService(typeof(T)),它可以解决依赖关系。干得好! - Jerameel Resco

11

阅读完这篇文章之后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")]这样传递参数:在asp.net中如何添加操作筛选器的参数?(rboe的代码也展示了如何注入东西(同样的方式))


1

虽然问题隐含地涉及“通过属性过滤器”,但仍值得强调的是,“按类型全局添加过滤器”支持依赖注入:

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

关于基于属性的过滤器:

过滤器以属性的形式实现并直接添加到控制器类或操作方法中,不能通过依赖注入(DI)提供构造函数依赖项。这是因为属性必须在应用它们的地方提供其构造函数参数。这是属性工作方式的限制。 https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection 然而,正如之前回答OP的答案所提到的,有办法使用间接方式来实现DI。为了完整起见,这里是官方文档的链接:

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接