c# - 替换 ASP.NET Core 中中间件的激活器

标签 c# dependency-injection ninject asp.net-core

我正在尝试指示我的 ASP.NET Core MVC 应用程序使用第 3 方 DI 容器。我没有编写适配器,而是尝试按照 this post 中的建议插入库。

这非常有效 - 我可以用我自己的使用 DI 容器的 IControllerActivator 替换内置的。但是,在尝试实例化也依赖于注入(inject)依赖项的自定义中间件时,我遇到了障碍。 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 组件注入(inject)我的 Controller ,因为 Ninject 不知道它们
  • 无法实例化我使用应用程序服务的中间件,因为 ASP.NET 不知道 Ninject。

对于第一个问题的示例,这里有一个无法实例化的 Controller ,因为我正在使用 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为我们的应用程序的使用量身定制。

因此,与其依赖于 Microsoft.Framework.Logging.ILogger 抽象,它可能适合也可能不适合我们的应用程序特定需求,SOLID 原则指导我们使用由以下人员拥有的抽象(端口)应用程序,并使用 Hook 到框架代码中的适配器实现。这是一个如何 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 委托(delegate))注入(inject)组件(CustomMiddleware 类)。这会给您带来双重痛苦,因为这会使注册和解析组件变得复杂,并阻止容器对其进行验证和诊断。相反,您应该将 next 委托(delegate)移出构造函数并移入 Invoke 委托(delegate),如下所示:

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 真的很不幸,因为那是你自己的应用程序容器所在的地方;不是内置的配置系统。

关于c# - 替换 ASP.NET Core 中中间件的激活器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33002340/

相关文章:

c# - 我可以使用 SerialPort.Write 发送字节数组吗

ninject - 为从 ASP.NET MVC 和工作应用程序使用 RabbitMQ 设计一个好的界面

c# - 如何避免与 IoC 容器耦合

c# - 在另一个项目 c# 中使用引用项目的 WCF 服务

c# - 如何修复测试名称的无效 ReSharper View ?

c# - 如何在 MVC3 应用程序中构建用于发送的 HTML 消息?

c# - 获取 Simple Injector 的容器实例

scala - Guice Multibinder 不适用于 Play 框架

c# - 我可以或应该在运行时有条件地使用 CaSTLe Windsor 绑定(bind)接口(interface)吗?

c# - 如何使用工厂模式实现每个请求/线程的数据上下文