在ASP.NET Core中替换中间件的激活器(Replace activator for middleware in ASP.NET Core)

编程入门 行业动态 更新时间:2024-10-25 20:21:41
在ASP.NET Core中替换中间件的激活器(Replace activator for middleware in 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); } }

我发现我有两个问题:

我无法将标准的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组件应该在他们自己的管道中,我的应用程序组件应该在另一个组件中,但实际上我经常需要以交叉方式使用组件(如上面的示例中所示)。

I am trying to instruct my ASP.NET Core MVC application to use a 3rd party DI container. Rather than writing an adapter I am trying to just plug in the the library following the advice in this post

This works pretty well - I can replace the built in IControllerActivator with my own that uses the DI container. However, I am running into a roadblock when trying to instantiate custom middleware that also relies on injected dependencies. ASP.NET cannot resolve these dependencies because it is not using my 3rd party DI container - is there an equivalent of IControllerActivator for middleware, or am I stuck using the built-in DI or writing an adapter?

** EDIT **

Here's some more of my code - I am actually trying to use Ninject using the pattern above.

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); } }

I've discovered I have two problems:

I can't inject standard ASP.NET components into my controllers because Ninject is not aware of them My middleware that uses application services can't be instantiated because ASP.NET isn't aware of Ninject.

For an example of the first problem, here's a controller that fails to instantiate because I'm using IUrlHelper (also note the ILogger, which also fails to instantiate):

public class SystemController : Controller { public SystemController(ILogger logger, IUrlHelper urlHelper) { /*...*/ } }

Here's an example of the second problem with a custom middleware:

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) { /* ... */ } }

I realize that in theory ASP.NET components should be in their own pipeline and my application components should be in another, but in practice I often need to use components in a cross-cutting way (as in the examples above).

最满意答案

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>));
 

严格警告 :不要直接复制框架抽象。 这几乎不会导致好的结果。 您应该指定根据您的应用程序定义的抽象。 这甚至可能意味着适配器变得更加复杂,需要多个框架组件来履行其合约,但这会产生更清洁和更易维护的应用程序代码。

但是,如果对您应用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 ,因为那是你自己的应用程序容器的用途; 而不是内置的配置系统。

The SOLID principles dictate that:

the abstracts are owned by the upper/policy layers (DIP)

Which means that our application code should not depend directly on framework code, even if they are abstractions. Instead we should define role interfaces that are tailored for the use of our application.

So instead of depending on a Microsoft.Framework.Logging.ILogger abstraction, that might or might not fit our application specific needs, the SOLID principles guide us towards abstractions (ports) that are owned by the application, and use adapter implementations that hook into framework code. Here's an example of how your own ILogger abstraction might look like.

When application code depends on your own abstraction, you need an adapter implementation that will be able to forward the call to the implementation supplied by the framework:

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) { ... }
}
 

This adapter can be registered in your application container as follows:

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

BIG WARNING: Do not make direct copies of the framework abstractions. That will almost never lead to good results. you should specify abstractions that are defined in terms of your application. This could even mean that an adapter becomes more complex and needs multiple framework components to fulfill its contract, but this results in cleaner and more maintainable application code.

But if applying SOLID is too much a hassle for you, and you just want to depend directly on external components, you can always cross-wire the required dependencies in your application container as follows:

container.Register<Microsoft.Framework.Logging.ILogger>(
    app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>);
 

It is as easy as this, but do note that in order to keep your application clean and maintainable, it's much better to define application specific abstractions that adhere to the SOLID principles. Also note that, even if you do this, you only need a few of those cross-wired dependencies anyway. So it's best to still keep your application container as separated as possible from the vNext configuration system.

With the middleware, there is a completely different issue at play here. In your middleware you are injecting runtime data (the next delegate) into a component (the CustomMiddleware class). This is giving you double grief, because this complicates registration and resolving the component and prevents it to be verified and diagnosed by the container. Instead, you should move the next delegate out of the constructor and into the Invoke delegate as follows:

public class CustomMiddleware
{
    private IPersonService _personService;

    public CustomMiddleware(IPersonService personService) {
        _personService = personService;
    }

    public async Task Invoke(HttpContext context, RequestDelegate next) { /* ... */ }
}
 

Now you can hook your middleware into the pipeline as follows:

app.Use(async (context, next) =>
{
    await container.GetInstance<CustomMiddleware>().Invoke(context, next);
});
 

But don't forget that you can always create your middleware by hand as follows:

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);
});
 

It's really unfortunate that ASP.NET calls its own services ApplicationServices, because that's where your own application container is for; not the built-in configuration system.

更多推荐

本文发布于:2023-08-06 21:01:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1455419.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:中间件   Core   NET   ASP   activator

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!