【问题标题】:Replace activator for middleware in ASP.NET Core在 ASP.NET Core 中替换中间件的激活器
【发布时间】:2016-01-05 06:45:42
【问题描述】:

我正在尝试指示我的 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 组件应该在它们自己的管道中,而我的应用程序组件应该在另一个管道中,但实际上我经常需要以横切方式使用组件(如上面的示例中所示)。

【问题讨论】:

  • 你想使用什么 DI 容器?
  • @opants 看起来像 Simple Injector。
  • 您能否分享有关您正在编写的中间件和您尝试注入的依赖项的更多信息。请您尝试过。
  • "或者我是卡在使用内置 DI 还是编写适配器"。您永远不需要构建适配器。根本不需要适配器,正如我在here 解释的那样,适配器只会妨碍您。
  • @opants:您不应该将其视为“2 个 DI 容器”。您的应用程序只有一个 DI 容器,并且您拥有“ASP.NET 的配置系统”,它在内部恰好看起来像一个 DI 容器。从某种意义上说,事情和以前没有什么不同。 MVC 和 Web API 已经有了自己的配置系统;我们从不想更换他们完整的内部配置系统。这真的没有意义。

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


【解决方案1】:

SOLID 原则规定:

摘要归上层/政策层所有 (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 类)中。这让您倍感悲痛,因为这会使注册和解析组件变得复杂,并阻止它被容器验证和诊断。相反,您应该将 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,因为那是您自己的应用程序容器的用途;不是内置的配置系统。

【讨论】:

  • 我理解快乐之路所违反的原则,但替代方案看起来很糟糕。信守原则意味着将所有抽象包装在您的控制之外,并将外部依赖项交叉连接到应用程序容器中。你真的觉得这样更好吗?
  • @davidfowl 我不建议在应用程序容器中交叉连接外部依赖项,因为这意味着应用程序组件仍然可以依赖外部依赖项,而那些应该由适配器包装。并且不要忘记您只需要包装您的应用程序实际使用的抽象,通常只是几个。是否“更好”取决于手头应用程序的大小和生命周期,但总的来说,我会说这实际上要好得多。
  • 更糟糕的是,这意味着您需要将它们保留在应用程序容器之外,以某种方式获取它们并手动将它们传递给适配器。老实说,IMO 真是太可怕了。好消息是,这种模式是可行的,但从审美 POV 来看不是很好。
  • @davidfowl 我真的不知道你在说什么。将东西传递给适配器很简单;这从来没有给我带来任何麻烦。你不需要为此滥用你的容器。
  • 当然,我们会让用户决定。
猜你喜欢
  • 2016-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-04
相关资源
最近更新 更多