简体   繁体   English

在 ASP.NET Core 中间件构造函数中混合依赖注入和手动传递的参数

[英]Mixing dependency injection and manually-passed parameter in ASP.NET Core middleware constructor

I'm writing a custom middleware for ASP.NET Core 2.2.我正在为 ASP.NET Core 2.2 编写自定义中间件。 According to Microsoft Docs on writing custom middlewares:根据关于编写自定义中间件的Microsoft Docs

Middleware components can resolve their dependencies from dependency injection (DI) through constructor parameters.中间件组件可以通过构造函数参数从依赖注入(DI)中解析它们的依赖关系。 UseMiddleware<T> can also accept additional parameters directly. UseMiddleware<T>也可以直接接受额外的参数。

This seems all good, but it doesn't say what happens when I mix the two ways, eg use DI and pass parameters in UseMiddleware<T> .这看起来都很好,但它并没有说明当我混合这两种方式时会发生什么,例如使用 DI 并在UseMiddleware<T>传递参数。 For example, I have the following middleware:例如,我有以下中间件:

public class CustomMiddleware
{
    public CustomMiddleware(RequestDelegate next, ILogger<CustomMiddleware> logger, CustomMiddlewareOptions options)
    { 
        ...
    }

    public async Task InvokeAsync(HttpContext context)
    {
        ...
    }

where logger is provided by DI and options is provided like the following:其中logger由 DI 提供, options提供如下:

app.UseMiddleware<CustomMiddleware>(new CustomMiddlewareOptions());

My own testing with 2.2 seems to show that this works fine, and the order of the parameters in the constructor doesn't matter (I can place DI parameter before or after manually-passed parameter, or even in between two manually-passed parameters).我自己对 2.2 的测试似乎表明这可以正常工作,并且构造函数中参数的顺序无关紧要(我可以将 DI 参数放在手动传递的参数之前或之后,甚至在两个手动传递的参数之间) . But I'm looking for some assurances that what I'm doing is OK.但我正在寻找一些保证,即我所做的一切都没有问题。 It would be really great if anyone could point to some docs or source code that supports this sort of usage.如果有人能指出一些支持这种用法的文档或源代码,那就太好了。 Thanks!谢谢!

My own testing with 2.2 seems to show that this works fine, and the order of the parameters in the constructor doesn't matter (I can place DI parameter before or after manually-passed parameter, or even in between two manually-passed parameters).我自己对 2.2 的测试似乎表明这可以正常工作,并且构造函数中参数的顺序无关紧要(我可以将 DI 参数放在手动传递的参数之前或之后,甚至在两个手动传递的参数之间) . But I'm looking for some assurances但我正在寻找一些保证

Yes.是的。 After reading the source code, I would say it is fine.阅读源代码后,我会说这很好。

How it works这个怎么运作

Your CustomMiddleware is a by-convention Middleware (different from the Factory-based middleware), which is activated by ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs) :你的CustomMiddleware是一个约定俗成的中间件(不同于基于工厂的中间件),它由ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs) 激活

var ctorArgs = new object[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);     // 
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);

Here the the args (Given Arguments) is the argument array that you pass into the UseMiddleware<CustomMiddleware>(args) (without the next ).这里的args (Given Arguments) 是您传递给UseMiddleware<CustomMiddleware>(args) (没有next )的参数数组。

And there're two stages when preparing the constructor arguments:准备构造函数参数时有两个阶段:

  1. Match the given args against the construct parameter types.将给定的args与构造参数类型匹配。 And set values when the type matches.并在类型匹配时设置值。 See source code Here 在此处查看源代码
  2. Fill the null element using ServiceProvider.GetRequiredService<SomeService>() .See source code here .使用ServiceProvider.GetRequiredService<SomeService>()填充null元素。请参阅此处的源代码 If the service instance is still null , then use the default value.如果服务实例仍然为null ,则使用default值。

For example, let's say :例如,让我们说:

  1. You have a by-convention middleware whose constructor has the following signature:您有一个约定俗成的中间件,其构造函数具有以下签名:
     public CustomMiddleware(RequestDelegate next, A a, B b, C c, D d, E e){ ... }
  2. And then we pass in two arguments when registering the middleware :然后我们在注册中间件时传入两个参数:

     app.UseMiddleware(c, a)

    where c is an instance of C Type, and a is an instance of A Type.其中cC Type 的实例, aA Type 的实例。 So the givenParameters Array is [next,c, a]所以给定的givenParameters数组是[next,c, a]

To create an instance of CustomMiddleware , the compiler needs to know the complete constructor parameter values.要创建CustomMiddleware的实例,编译器需要知道完整的构造函数参数值。 DI extension gets this constructor parameter values array ( _parameterValues ) within two stages.See : DI 扩展在两个阶段内获取此构造函数参数值数组 ( _parameterValues )。参见:

在此处输入图片说明

The stage2 works in a way like below: stage2 的工作方式如下:

b'= sp.GetService(B); 
if b' == null :
    b' = default value of B

As you can see above, the ActivatorUtilities.CreateInstance(sp,mw,args) API deals with the order and missing arguments automatically.正如您在上面看到的, ActivatorUtilities.CreateInstance(sp,mw,args) API 自动处理顺序和丢失的参数。


As a side note, the by-convention middlewares are activated at startup-time and will always be a singleton.附带说明一下,约定俗成的中间件在启动时激活,并且始终是单例。 If you want to use scoped service, see this thread如果您想使用范围服务,请参阅此线程

But I'm looking for some assurances that what I'm doing is OK.但我正在寻找一些保证,即我所做的一切都没有问题。

Well, this is opinion-based.嗯,这是基于意见的。 While everything is working in your case it is okay, I believe.虽然在你的情况下一切正常,但我相信没关系。 But I would prefere using options pattern introduced in ASP.NET Core .但我更喜欢使用ASP.NET Core引入的选项模式

public class CustomMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<CustomMiddleware> _logger;
    private readonly CustomMiddlewareOptions _options;

    public CustomMiddleware(RequestDelegate next, ILogger<CustomMiddleware> logger, IOptions<CustomMiddlewareOptions> options)
    {
        _next = next;
        _logger = logger;
        _options = options.Value;
    }
    //...
}

For this one you will need to configure CustomMiddlewareOptions in Startup为此,您需要在Startup配置CustomMiddlewareOptions

services.Configure<CustomMiddlewareOptions>(options =>
{
    options.Id = 1;
    options.Name = "options";
    //set other properties
});

In this case you should add middleware without parameters在这种情况下,您应该添加不带参数的中间件

app.UseMiddleware<CustomMiddleware>();

Note笔记

It's also worth to take a look at RequestLocalizationMiddleware也值得一看RequestLocalizationMiddleware

public RequestLocalizationMiddleware(RequestDelegate next, IOptions<RequestLocalizationOptions> options)

and how framework offers you to use the middleware in ApplicationBuilderExtensions以及框架如何让您在ApplicationBuilderExtensions 中使用中间件

public static IApplicationBuilder UseRequestLocalization(this IApplicationBuilder app)
{
    //omitted
    return app.UseMiddleware<RequestLocalizationMiddleware>();
}

or或者

public static IApplicationBuilder UseRequestLocalization(this IApplicationBuilder app, RequestLocalizationOptions options)
{
    //omitted
    return app.UseMiddleware<RequestLocalizationMiddleware>(Options.Create(options));
}

As you can see ASP.NET Core developers also prefer to stick with IOptions in middleware constructor and while manually specifying additional parameter they just wrap them into Options正如您所看到的, ASP.NET Core开发人员也更喜欢在中间件构造函数中使用IOptions ,并且在手动指定附加参数时,他们只是将它们包装到Options

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM