我有一个.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);
}
}
答案 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
IMyService
,MyCustomActionFilter
还是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>()
。