ASP.NET Core 2.1服务定位器,其中简单注入器返回null

时间:2018-12-05 17:21:52

标签: c# .net asp.net-core .net-core simple-injector

我有一个.NET MVC 5 .NET Framework应用程序,正在将其转换为.NET Core 2.1

我有一个自定义操作过滤器,该过滤器在.NET Framework版本中已注册为Filterconfig类中的全局过滤器,如下所示:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new MyCustomActionFilter());
    }
}

在.NET版本的自定义操作过滤器中,我正在使用Service Locator模式(我知道它可以被视为反模式),如下所示:

var myService = DependencyResolver.Current.GetService<IMyService>();

我正在使用Simple Injector进行DI,并且在.NET版本中一切正常。在.NET Core版本中,我试图使相同的功能正常工作,但myService始终为空

我仍在使用Simple Injector(因为该解决方案中的所有其他项目都在使用它,并且它们并没有转移到.NET Core项目中(仅Web项目在其中)。

我的Startup.cs类具有以下代码:

services.Configure<MvcOptions>(options =>
{
    options.Filters.Add(new MyCustomActionFilter());
});

SimpleInjectorConfig.IntegrateSimpleInjector(services, container);

在服务层,我有一个SimpleInjector Registartion类,该类从Web层调用-然后调用DAL层进行注册

public class SimpleInjectorRegistration
{
    public static void RegisterServices(Container container)
    {
        container.Register<IMyService, MyService>();
        //further code removed for brevity

当我运行带有自定义过滤器中的断点和该RegisterServices方法中的断点的应用程序时,我可以看到首先命中了RegisterServices方法中的断点,然后是自定义过滤器中的断点-这使我认为一切都已连接正确地放在容器中。

但是,我尝试使用.NET Core Service Locator模式在自定义过滤器中再次执行以下操作

var myService = filterContext.HttpContext.RequestServices.GetService<IMyService>();

但是结果总是为空?

在此设置中我有想念的东西吗?

------------更新-------------------

根据史蒂文斯的评论,我向动作过滤器添加了一个构造函数,并传入了Simple Injector容器。

所以我的启动类现在是:

public class Startup
{
    //Simple Injector container
    private Container container = new Container(); 

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();


        services.Configure<MvcOptions>(options =>
        {
           options.Filters.Add(new MyCustomActionFilter(container)); 

我的自定义过滤器现在如下所示,其中添加了构造函数:

public class MyCustomActionFilter : ActionFilterAttribute
{
    private readonly IMyService _myService;

    public MyCustomActionFilter(Container container)
    {
        _myService = container.GetService<IMyService>();
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
       //actual code of custom filter removed - use of MyService 

我在MyCustomActionFilter的构造方法上设置了一个断点,可以看到它被击中,但是抛出了错误:

  

SimpleInjector.ActivationException:'IDbContext被注册为'Async Scoped'生活方式,但是在活动(Async Scoped)作用域的上下文之外请求实例。'

MyService依赖于注入到其中的DbContext(它正在做保存和从数据库检索数据的工作。

对于数据库上下文,我将其注册如下:

public class SimpleInjectorRegistration
{
    public static void RegisterServices(Container container, string connectionString)
    {
        container.Register<IDbContext>(() => new MyDbContext(connectionString),
            Lifestyle.Scoped);
    }

}

3 个答案:

答案 0 :(得分:3)

在旧的ASP.NET MVC和新的ASP.NET Core中如何集成Simple Injector之间有一些重大变化。在旧系统中,您将可以替换IDependencyResolver。但是,ASP.NET Core包含一个完全不同的模型,具有自己的内部DI容器。由于不可能用Simple Injector替换该内置容器,因此您将使两个容器并排运行。在这种情况下,内置容器将解析框架和第三方组件,其中Simple Injector将为您组成应用程序组件。

调用HttpContext.RequestServices.GetService时,您将请求服务的内置容器 not Simple Injector。正如TanvirArjel的答案所暗示的那样,将IMyService注册添加到内置容器中似乎一开始似乎可以,但是这完全跳过了等式中的Simple Injector,这显然不是一个选择,因为您希望使用Simple。注射器作为您的应用程序容器。

要模仿以前的类似Service Locator的行为,必须将SimpleInjector.Container注入到过滤器中,如下所示:

options.Filters.Add(new MyCustomActionFilter(container));

但是,如您在问题中所显示的那样,从构造函数中调用容器将是错误的:

public class MyCustomActionFilter : ActionFilterAttribute
{
    private readonly IMyService _myService;

    public MyCustomActionFilter(Container container)
    {
        _myService = container.GetService<IMyService>(); // NEVER DO THIS!!!
    }

    ...
}
  

警告:您应该从不从构造函数的容器中解析。或更笼统地说:您应该从不使用构造函数内部的任何注入依赖项。构造函数应该只存储依赖项。

正如Mark Seemann所解释的,injection constructors should be simple。在这种情况下,甚至会变得更糟,因为:

  • 在调用MyCustomActionFilter的构造函数期间,没有活动的作用域,并且无法解析IMyService
  • 即使可以解决IMyServiceMyCustomActionFilter还是Singleton,并且将IMyService存储在私有字段中将导致隐藏的Captive Dependency。这可能会导致各种各样的麻烦。

应该存储IMyService依赖性,而不是存储已解决的Container依赖性:

public class MyCustomActionFilter : ActionFilterAttribute
{
    private readonly Container _container;

    public MyCustomActionFilter(Container container)
    {
        _container = container;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        myService = container.GetService<IMyService>();
        //actual code of custom filter removed - use of MyService 
    }
}

在调用OnActionExecuting的过程中,将有一个活动的简单注入器Scope,它将使IMyService得以解决。最重要的是,由于IMyService未存储在私有字段中,因此不会被缓存,也不会引起强制依赖。

在您的问题中,您提到了Service Locator anti-pattern。实际上,是否将Container注入过滤器取决于服务定位器反模式的实现,具体取决于过滤器位于何处。正如Mark Seemann puts所言:

  

封装在“合成根”中的DI容器不是服务定位器,而是基础结构组件。

换句话说,只要过滤器类位于Composition Root的内部 ,就不会应用Service Locator反模式。但是,这确实意味着您必须确保过滤器本身包含尽可能少的有趣行为。该行为应全部移至过滤器解析的服务。

答案 1 :(得分:0)

在ASP.NET Core中,您必须在- var headerClass = 'index-header'; block head 类的- var headerClass = 'dashboard-header'; block head 方法中注册服务,如下所示:

ConfigureServices()

尝试一下,希望您的问题会消失。

答案 2 :(得分:0)

正如@Steven指出的那样,内置容器将解析框架和第三方组件,其中Simple Injector将为您组成应用程序组件。对于内置容器,它无法通过简单的注射器解决服务。对于简单的注入器,您可以尝试EnableSimpleInjectorCrossWiring来解析内置容器中的服务。

对于options.Filters.Add,它也接受MyCustomActionFilter instance,而无需将Container依赖于MyCustomActionFilter,请尝试在样品进样器中注册MyCustomActionFilter,然后将此实例传递给options.Filters.Add

  • 注册服务

        private void InitializeContainer(IApplicationBuilder app)
    {
        // Add application presentation components:
        container.RegisterMvcControllers(app);
        container.RegisterMvcViewComponents(app);
    
        // Add application services. For instance:
        container.Register<IMyService, MyService>(Lifestyle.Scoped);
        container.Register<MyCustomActionFilter>(Lifestyle.Scoped);
        // Allow Simple Injector to resolve services from ASP.NET Core.
        container.AutoCrossWireAspNetComponents(app);
    }
    
  • 添加MyCustomActionFilter

            services.Configure<MvcOptions>(options =>
        {
            using (AsyncScopedLifestyle.BeginScope(container))
            {
                options.Filters.Add(container.GetRequiredService<MyCustomActionFilter>());
            }
        });
        #region SampleInjector
        IntegrateSimpleInjector(services);
        #endregion
    

    注意:如果您指定container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();,则在致电using (AsyncScopedLifestyle.BeginScope(container))时需要container.GetRequiredService<MyCustomActionFilter>()