Jos*_*ron 8 c# dependency-injection ninject asp.net-core
我试图指示我的ASP.NET Core MVC应用程序使用第三方DI容器.而不是写一个适配器,我试图按照这篇文章中的建议插入库
这很好用 - 我可以IControllerActivator用我自己的内置替换使用DI容器的内置.但是,在尝试实例化依赖注入依赖项的自定义中间件时,我遇到了障碍.ASP.NET无法解决这些依赖关系,因为它没有使用我的第三方DI容器 - 是否有相当于IControllerActivator中间件,或者我是否坚持使用内置DI或编写适配器?
**编辑**
这是我的一些代码 - 我实际上是尝试使用上面的模式使用Ninject.
internal sealed class NinjectControllerActivator : IControllerActivator
{
private readonly IKernel _kernel;
public NinjectControllerActivator(IKernel kernel)
{
_kernel = kernel;
}
[DebuggerStepThrough]
public object Create(ActionContext context, Type controllerType)
{
return _kernel.Get(controllerType);
}
}
Run Code Online (Sandbox Code Playgroud)
我发现我有两个问题:
有关第一个问题的示例,这里是一个无法实例化的控制器,因为我正在使用IUrlHelper(还要注意ILogger,它也无法实例化):
public class SystemController : Controller
{
public SystemController(ILogger logger, IUrlHelper urlHelper)
{
/*...*/
}
}
Run Code Online (Sandbox Code Playgroud)
以下是自定义中间件的第二个问题示例:
public class CustomMiddleware
{
private RequestDelegate _next;
// this is an application specific service registered via my Ninject kernel
private IPersonService _personService;
public CustomMiddleware(RequestDelegate next, IPersonService personService)
{
_next = next;
_personService = personService;
}
public async Task Invoke(HttpContext context)
{
/* ... */
}
}
Run Code Online (Sandbox Code Playgroud)
我意识到理论上ASP.NET组件应该在他们自己的管道中,而我的应用程序组件应该在另一个组件中,但实际上我经常需要以交叉方式使用组件(如上例所示).
Ste*_*ven 12
SOLID原则规定:
摘要由上层/政策层(DIP)拥有
这意味着我们的应用程序代码不应直接依赖于框架代码,即使它们是抽象的.相反,我们应该定义为我们的应用程序使用而定制的角色接口.
因此Microsoft.Framework.Logging.ILogger,SOLID原则不是依赖于抽象,而是可能适合或不适合我们的应用程序特定需求,而是指导我们实现应用程序所拥有的抽象(端口),并使用挂钩到框架代码中的适配器实现.以下是您自己的ILogger抽象可能如何的示例.
当应用程序代码依赖于您自己的抽象时,您需要一个能够将调用转发给框架提供的实现的适配器实现:
public sealed class MsLoggerAdapter : MyApp.ILogger
{
private readonly Func<Microsoft.Framework.Logging.ILogger> factory;
public MsLoggerAdapter(Func<Microsoft.Framework.Logging.ILogger> factory) {
this.factory = factory;
}
public void Log(LogEntry entry) {
var logger = this.factory();
LogLevel level = ToLogLevel(entry.Severity);
logger.Log(level, 0, entry.Message, entry.Exception,
(msg, ex) => ex != null ? ex.Message : msg.ToString());
}
private static LogLevel ToLogLevel(LoggingEventType severity) { ... }
}
Run Code Online (Sandbox Code Playgroud)
此适配器可以在应用程序容器中注册,如下所示:
container.RegisterSingleton<MyApp.ILogger>(new MsLoggerAdapter(
app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>));
Run Code Online (Sandbox Code Playgroud)
BIG警告:不要不使框架抽象的直接拷贝.这几乎不会带来好结果.您应该指定根据您的应用程序定义的抽象.这甚至可能意味着适配器变得更加复杂并且需要多个框架组件来履行其合同,但这会产生更清晰且更易于维护的应用程序代码.
但是,如果应用SOLID对您来说太麻烦,并且您只想直接依赖外部组件,则可以始终在应用程序容器中交叉连接所需的依赖项,如下所示:
container.Register<Microsoft.Framework.Logging.ILogger>(
app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>);
Run Code Online (Sandbox Code Playgroud)
它就像这样简单,但请注意,为了保持应用程序的清洁和可维护性,定义符合SOLID原则的特定于应用程序的抽象要好得多.另请注意,即使您这样做,您也只需要一些交叉连接的依赖项.因此,最好仍然将应用程序容器与vNext配置系统尽可能分开.
使用中间件,这里有一个完全不同的问题.在中间件中,您将运行时数据(next委托)注入到组件(CustomMiddleware类)中.这让您倍感悲痛,因为这会使注册和解析组件变得复杂,并阻止容器对其进行验证和诊断.相反,您应该将next委托移出构造函数并进入Invoke委托,如下所示:
public class CustomMiddleware
{
private IPersonService _personService;
public CustomMiddleware(IPersonService personService) {
_personService = personService;
}
public async Task Invoke(HttpContext context, RequestDelegate next) { /* ... */ }
}
Run Code Online (Sandbox Code Playgroud)
现在您可以将中间件挂钩到管道中,如下所示:
app.Use(async (context, next) =>
{
await container.GetInstance<CustomMiddleware>().Invoke(context, next);
});
Run Code Online (Sandbox Code Playgroud)
但不要忘记,您可以随时手动创建中间件,如下所示:
var frameworkServices = app.ApplicationServices;
app.Use(async (context, next) =>
{
var mw = new CustomMiddleware(
container.GetInstance<IPersonService>(),
container.GetInstance<IApplicationSomething>(),
frameworkServices.GetRequiredService<ILogger>(),
frameworkServices.GetRequiredService<AspNetSomething>());
await mw.Invoke(context, next);
});
Run Code Online (Sandbox Code Playgroud)
非常不幸的是,ASP.NET调用自己的服务ApplicationServices,因为这是您自己的应用程序容器所在的位置; 不是内置的配置系统.