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

问题描述 投票:0回答:1

我试图指示我的 ASP.NET Core MVC 应用程序使用第 3 方 DI 容器。 我没有编写适配器,而是尝试按照这篇文章

中的建议插入库

这工作得很好 - 我可以用我自己的使用 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 组件应该位于自己的管道中,而我的应用程序组件应该位于另一个管道中,但实际上我经常需要以横切的方式使用组件(如上面的示例所示)。

c# dependency-injection ninject asp.net-core
1个回答
13
投票

SOLID 原则规定:

摘要归上层/政策层所有(DIP

这意味着我们的应用程序代码不应该直接依赖于框架代码,即使它们是抽象的。相反,我们应该定义为我们的应用程序的使用量身定制的角色接口

因此,SOLID 原则不是依赖于可能适合也可能不适合我们的应用程序特定需求的

Microsoft.Framework.Logging.ILogger
抽象,而是引导我们走向应用程序拥有的抽象(端口),并使用挂钩到框架代码的适配器实现。以下是您自己的 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) { ... }
}

此适配器可以在您的应用程序容器中注册,如下所示:

container.RegisterSingleton<MyApp.ILogger>(new MsLoggerAdapter(
    app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>));

更新:在 ASP.NET Core 的 Simple Injector 集成包的更高版本中,Simple Injector 能够自动从内置 ASP.NET 配置系统中提取这些依赖项。也就是说,不再需要之前的手工注册了。

大警告:不要直接复制框架抽象。这几乎永远不会带来好的结果。您应该指定根据您的应用程序定义的抽象。这甚至可能意味着适配器变得更加复杂,并且需要多个框架组件来履行其合同,但这会导致应用程序代码更干净、更易于维护。

但是,如果应用 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

,因为那是你自己的应用程序容器的用途;不是内置的配置系统。

© www.soinside.com 2019 - 2024. All rights reserved.