ASP.NET 带有简单注入器的核心 2.1 服务定位器返回 null
ASP.NET Core 2.1 Service Locator with Simple Injector returning null
我有一个 .NET MVC 5 .NET Framework 应用程序,我正在将其转换为 .NET Core 2.1
我有一个自定义操作过滤器,它在 .NET Framework 版本中被注册为 Filterconfig class 中的全局过滤器,如下所示:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyCustomActionFilter());
}
}
在 .NET 版本的自定义操作过滤器中,我使用的是服务定位器模式(我知道它可以被认为是反模式),如下所示:
var myService = DependencyResolver.Current.GetService<IMyService>();
我正在为 DI 使用 Simple Injector,在 .NET 版本中一切正常。对于 .NET Core 版本,我试图获得相同的功能,但 myService 始终为 null
我仍在使用 Simple Injector(因为解决方案中的所有其他项目都在使用它,并且它们没有转移到 .NET Core 项目(只有 Web 项目是)。
我的 Startup.cs class 有这个代码:
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new MyCustomActionFilter());
});
SimpleInjectorConfig.IntegrateSimpleInjector(services, container);
在我的服务层,我有一个从 Web 层调用的 SimpleInjector Registartion class - 然后向下调用 DAL 层进行注册
public class SimpleInjectorRegistration
{
public static void RegisterServices(Container container)
{
container.Register<IMyService, MyService>();
//further code removed for brevity
当我 运行 应用程序在自定义过滤器中有一个断点并且在这个 RegisterServices 方法中有一个断点时,我可以看到 RegisterServices 方法中的断点首先被命中,然后是自定义过滤器中的断点 - 这个让我觉得容器中的一切都已正确连接。
但是我正尝试在自定义过滤器中使用 .NET Core 服务定位器模式再次执行以下操作
var myService = filterContext.HttpContext.RequestServices.GetService<IMyService>();
但结果始终为空?
我在这个设置中遗漏了什么吗?
------------更新-----------------
根据史蒂文斯的评论,我向我的操作过滤器添加了一个构造函数并传入了简单注入器容器。
所以我的启动 class 现在是:
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: 'The IDbContext is registered as 'Async Scoped' lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope.'
MyService 对注入其中的 DbContext 有依赖性(它正在执行从数据库保存和检索数据的工作。
对于数据库上下文,我注册如下:
public class SimpleInjectorRegistration
{
public static void RegisterServices(Container container, string connectionString)
{
container.Register<IDbContext>(() => new MyDbContext(connectionString),
Lifestyle.Scoped);
}
}
在旧的 ASP.NET MVC 和新的 ASP.NET Core 中集成 Simple Injector 的方式有一些重大变化。在旧系统中,您可以替换 IDependencyResolver
。 ASP.NET 然而,Core 包含一个完全不同的模型,它有自己的内部 DI 容器。由于用 Simple Injector 替换内置容器是 impossible,因此您将并排放置两个容器 运行。在这种情况下,内置容器将解析框架和第三方组件,其中 Simple Injector 将为您编写应用程序组件。
当您调用 HttpContext.RequestServices.GetService
时,您将请求内置容器的服务,不是 简单注入器。将 IMyService
注册添加到内置容器中,正如 TanvirArjel 的回答所暗示的那样,起初似乎可行,但这完全跳过了等式中的 Simple Injector,这显然不是一个选项,因为您希望使用 Simple注入器作为您的应用程序容器。
要模仿您之前的类似服务定位器的行为,您必须将 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!!!
}
...
}
WARNING: You should never resolve from the container from the constructor. Or in more general: you should never use any injected dependency from inside the constructor. The constructor should only store the dependency.
正如 Mark Seemann 所解释的,injection constructors should be simple。在这种情况下,情况甚至会变得更糟,因为:
- 调用
MyCustomActionFilter
的构造函数期间,没有活动作用域,无法解析IMyService
- 即使
IMyService
可以解析,MyCustomActionFilter
也是一个 Singleton
,将 IMyService
存储在私有字段中将导致隐藏 Captive Dependency。这可能会导致各种麻烦。
您应该存储 Container
依赖项,而不是存储已解析的 IMyService
依赖项:
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
没有存储在私有字段中,因此不会被缓存,也不会导致 Captive Dependency。
在你的问题中你提到了 Service Locator anti-pattern. Whether or not the injection of the Container
into your filter is in fact an implementation of the Service Locator anti-pattern depends on where the filter is located. As Mark Seemann puts 它:
A DI container encapsulated in a Composition Root is not a Service Locator - it's an infrastructure component.
换句话说,只要过滤器 class 位于 内部 您的 Composition Root,您就没有应用服务定位器反模式。然而,这确实意味着您必须确保过滤器本身包含尽可能少的有趣行为。该行为应全部移至过滤器解析的服务。
正如@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();
,调用container.GetRequiredService<MyCustomActionFilter>()
时需要using (AsyncScopedLifestyle.BeginScope(container))
。
我有一个 .NET MVC 5 .NET Framework 应用程序,我正在将其转换为 .NET Core 2.1
我有一个自定义操作过滤器,它在 .NET Framework 版本中被注册为 Filterconfig class 中的全局过滤器,如下所示:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyCustomActionFilter());
}
}
在 .NET 版本的自定义操作过滤器中,我使用的是服务定位器模式(我知道它可以被认为是反模式),如下所示:
var myService = DependencyResolver.Current.GetService<IMyService>();
我正在为 DI 使用 Simple Injector,在 .NET 版本中一切正常。对于 .NET Core 版本,我试图获得相同的功能,但 myService 始终为 null
我仍在使用 Simple Injector(因为解决方案中的所有其他项目都在使用它,并且它们没有转移到 .NET Core 项目(只有 Web 项目是)。
我的 Startup.cs class 有这个代码:
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new MyCustomActionFilter());
});
SimpleInjectorConfig.IntegrateSimpleInjector(services, container);
在我的服务层,我有一个从 Web 层调用的 SimpleInjector Registartion class - 然后向下调用 DAL 层进行注册
public class SimpleInjectorRegistration
{
public static void RegisterServices(Container container)
{
container.Register<IMyService, MyService>();
//further code removed for brevity
当我 运行 应用程序在自定义过滤器中有一个断点并且在这个 RegisterServices 方法中有一个断点时,我可以看到 RegisterServices 方法中的断点首先被命中,然后是自定义过滤器中的断点 - 这个让我觉得容器中的一切都已正确连接。
但是我正尝试在自定义过滤器中使用 .NET Core 服务定位器模式再次执行以下操作
var myService = filterContext.HttpContext.RequestServices.GetService<IMyService>();
但结果始终为空?
我在这个设置中遗漏了什么吗?
------------更新-----------------
根据史蒂文斯的评论,我向我的操作过滤器添加了一个构造函数并传入了简单注入器容器。
所以我的启动 class 现在是:
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: 'The IDbContext is registered as 'Async Scoped' lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope.'
MyService 对注入其中的 DbContext 有依赖性(它正在执行从数据库保存和检索数据的工作。
对于数据库上下文,我注册如下:
public class SimpleInjectorRegistration
{
public static void RegisterServices(Container container, string connectionString)
{
container.Register<IDbContext>(() => new MyDbContext(connectionString),
Lifestyle.Scoped);
}
}
在旧的 ASP.NET MVC 和新的 ASP.NET Core 中集成 Simple Injector 的方式有一些重大变化。在旧系统中,您可以替换 IDependencyResolver
。 ASP.NET 然而,Core 包含一个完全不同的模型,它有自己的内部 DI 容器。由于用 Simple Injector 替换内置容器是 impossible,因此您将并排放置两个容器 运行。在这种情况下,内置容器将解析框架和第三方组件,其中 Simple Injector 将为您编写应用程序组件。
当您调用 HttpContext.RequestServices.GetService
时,您将请求内置容器的服务,不是 简单注入器。将 IMyService
注册添加到内置容器中,正如 TanvirArjel 的回答所暗示的那样,起初似乎可行,但这完全跳过了等式中的 Simple Injector,这显然不是一个选项,因为您希望使用 Simple注入器作为您的应用程序容器。
要模仿您之前的类似服务定位器的行为,您必须将 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!!!
}
...
}
WARNING: You should never resolve from the container from the constructor. Or in more general: you should never use any injected dependency from inside the constructor. The constructor should only store the dependency.
正如 Mark Seemann 所解释的,injection constructors should be simple。在这种情况下,情况甚至会变得更糟,因为:
- 调用
MyCustomActionFilter
的构造函数期间,没有活动作用域,无法解析IMyService
- 即使
IMyService
可以解析,MyCustomActionFilter
也是一个Singleton
,将IMyService
存储在私有字段中将导致隐藏 Captive Dependency。这可能会导致各种麻烦。
您应该存储 Container
依赖项,而不是存储已解析的 IMyService
依赖项:
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
没有存储在私有字段中,因此不会被缓存,也不会导致 Captive Dependency。
在你的问题中你提到了 Service Locator anti-pattern. Whether or not the injection of the Container
into your filter is in fact an implementation of the Service Locator anti-pattern depends on where the filter is located. As Mark Seemann puts 它:
A DI container encapsulated in a Composition Root is not a Service Locator - it's an infrastructure component.
换句话说,只要过滤器 class 位于 内部 您的 Composition Root,您就没有应用服务定位器反模式。然而,这确实意味着您必须确保过滤器本身包含尽可能少的有趣行为。该行为应全部移至过滤器解析的服务。
正如@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();
,调用container.GetRequiredService<MyCustomActionFilter>()
时需要using (AsyncScopedLifestyle.BeginScope(container))
。