替换 ASP.NET 核心中的中间件激活器
Replace activator for middleware in ASP.NET Core
我正在尝试指示我的 ASP.NET Core MVC 应用程序使用第 3 方 DI 容器。我没有编写适配器,而是尝试按照 this post
中的建议插入库
这工作得很好——我可以用我自己的使用 DI 容器替换内置的 IControllerActivator
。但是,在尝试实例化也依赖于注入的依赖项的自定义中间件时,我 运行 遇到了障碍。 ASP.NET 无法解决这些依赖关系,因为它没有使用我的第 3 方 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);
}
}
我发现我有两个问题:
- 我无法将标准 ASP.NET 组件注入我的控制器,因为 Ninject 不知道它们
- 无法实例化我使用应用程序服务的中间件,因为 ASP.NET 不知道 Ninject。
作为第一个问题的示例,这里有一个无法实例化的控制器,因为我正在使用 IUrlHelper
(另请注意 ILogger
,它也无法实例化):
public class SystemController : Controller
{
public SystemController(ILogger logger, IUrlHelper urlHelper)
{
/*...*/
}
}
这是自定义中间件的第二个问题的示例:
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)
{
/* ... */
}
}
我意识到理论上 ASP.NET 组件应该在它们自己的管道中,而我的应用程序组件应该在另一个管道中,但实际上我经常需要以横切方式使用组件(如在以上示例)。
SOLID 原则规定:
the abstracts are owned by the upper/policy layers (DIP)
这意味着我们的应用程序代码不应该直接依赖于框架代码,即使它们是抽象的。相反,我们应该定义适合我们应用程序使用的 role interfaces。
因此,SOLID 原则不依赖于 Microsoft.Framework.Logging.ILogger
抽象,它可能适合也可能不适合我们的应用程序特定需求,而是指导我们使用应用程序拥有的抽象(端口),并使用适配器实现挂钩到框架代码中。这是 your own ILogger abstraction 的示例。
当应用程序代码依赖于您自己的抽象时,您需要一个能够将调用转发给框架提供的实现的适配器实现:
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) { ... }
}
可以按如下方式在您的应用程序容器中注册此适配器:
container.RegisterSingleton<MyApp.ILogger>(new MsLoggerAdapter(
app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>));
重要警告:不要直接复制框架抽象。这几乎永远不会带来好的结果。您应该指定根据您的应用程序定义的抽象。这甚至可能意味着适配器变得更加复杂并且需要多个框架组件来履行其合同,但这会导致应用程序代码更清晰和更易于维护。
但是如果应用 SOLID 对你来说太麻烦,而你只想直接依赖外部组件,你总是可以在你的应用程序容器中交叉连接所需的依赖项,如下所示:
container.Register<Microsoft.Framework.Logging.ILogger>(
app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>);
就这么简单,但请注意,为了保持应用程序的清洁和可维护性,最好定义符合 SOLID 原则的特定于应用程序的抽象。另请注意,即使您这样做,您也只需要其中的一些交叉依赖项。因此,最好还是让您的应用程序容器与 vNext 配置系统尽可能分开。
对于中间件,这里有一个完全不同的问题。在您的中间件中,您将运行时数据(next
委托)注入组件(CustomMiddleware
class)。这会给您带来双重痛苦,因为这会使注册和解析组件变得复杂,并阻止容器对其进行验证和诊断。相反,您应该将 next
委托移出构造函数并移入 Invoke
委托,如下所示:
public class CustomMiddleware
{
private IPersonService _personService;
public CustomMiddleware(IPersonService personService) {
_personService = personService;
}
public async Task Invoke(HttpContext context, RequestDelegate next) { /* ... */ }
}
现在您可以按如下方式将中间件连接到管道中:
app.Use(async (context, next) =>
{
await container.GetInstance<CustomMiddleware>().Invoke(context, next);
});
但不要忘记,您始终可以手动创建中间件,如下所示:
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);
});
真的很不幸 ASP.NET 调用它自己的服务 ApplicationServices
,因为那是你自己的应用程序容器所在的地方;不是内置的配置系统。
我正在尝试指示我的 ASP.NET Core MVC 应用程序使用第 3 方 DI 容器。我没有编写适配器,而是尝试按照 this post
中的建议插入库这工作得很好——我可以用我自己的使用 DI 容器替换内置的 IControllerActivator
。但是,在尝试实例化也依赖于注入的依赖项的自定义中间件时,我 运行 遇到了障碍。 ASP.NET 无法解决这些依赖关系,因为它没有使用我的第 3 方 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);
}
}
我发现我有两个问题:
- 我无法将标准 ASP.NET 组件注入我的控制器,因为 Ninject 不知道它们
- 无法实例化我使用应用程序服务的中间件,因为 ASP.NET 不知道 Ninject。
作为第一个问题的示例,这里有一个无法实例化的控制器,因为我正在使用 IUrlHelper
(另请注意 ILogger
,它也无法实例化):
public class SystemController : Controller
{
public SystemController(ILogger logger, IUrlHelper urlHelper)
{
/*...*/
}
}
这是自定义中间件的第二个问题的示例:
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)
{
/* ... */
}
}
我意识到理论上 ASP.NET 组件应该在它们自己的管道中,而我的应用程序组件应该在另一个管道中,但实际上我经常需要以横切方式使用组件(如在以上示例)。
SOLID 原则规定:
the abstracts are owned by the upper/policy layers (DIP)
这意味着我们的应用程序代码不应该直接依赖于框架代码,即使它们是抽象的。相反,我们应该定义适合我们应用程序使用的 role interfaces。
因此,SOLID 原则不依赖于 Microsoft.Framework.Logging.ILogger
抽象,它可能适合也可能不适合我们的应用程序特定需求,而是指导我们使用应用程序拥有的抽象(端口),并使用适配器实现挂钩到框架代码中。这是 your own ILogger abstraction 的示例。
当应用程序代码依赖于您自己的抽象时,您需要一个能够将调用转发给框架提供的实现的适配器实现:
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) { ... }
}
可以按如下方式在您的应用程序容器中注册此适配器:
container.RegisterSingleton<MyApp.ILogger>(new MsLoggerAdapter(
app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>));
重要警告:不要直接复制框架抽象。这几乎永远不会带来好的结果。您应该指定根据您的应用程序定义的抽象。这甚至可能意味着适配器变得更加复杂并且需要多个框架组件来履行其合同,但这会导致应用程序代码更清晰和更易于维护。
但是如果应用 SOLID 对你来说太麻烦,而你只想直接依赖外部组件,你总是可以在你的应用程序容器中交叉连接所需的依赖项,如下所示:
container.Register<Microsoft.Framework.Logging.ILogger>(
app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>);
就这么简单,但请注意,为了保持应用程序的清洁和可维护性,最好定义符合 SOLID 原则的特定于应用程序的抽象。另请注意,即使您这样做,您也只需要其中的一些交叉依赖项。因此,最好还是让您的应用程序容器与 vNext 配置系统尽可能分开。
对于中间件,这里有一个完全不同的问题。在您的中间件中,您将运行时数据(next
委托)注入组件(CustomMiddleware
class)。这会给您带来双重痛苦,因为这会使注册和解析组件变得复杂,并阻止容器对其进行验证和诊断。相反,您应该将 next
委托移出构造函数并移入 Invoke
委托,如下所示:
public class CustomMiddleware
{
private IPersonService _personService;
public CustomMiddleware(IPersonService personService) {
_personService = personService;
}
public async Task Invoke(HttpContext context, RequestDelegate next) { /* ... */ }
}
现在您可以按如下方式将中间件连接到管道中:
app.Use(async (context, next) =>
{
await container.GetInstance<CustomMiddleware>().Invoke(context, next);
});
但不要忘记,您始终可以手动创建中间件,如下所示:
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);
});
真的很不幸 ASP.NET 调用它自己的服务 ApplicationServices
,因为那是你自己的应用程序容器所在的地方;不是内置的配置系统。