如何在不“新建”类实例并自己提供参数的情况下进行依赖注入?

Ran*_*l W 5 c# dependency-injection asp.net-core-3.1

我有一个根据使用 NSwagStudio 生成的 OpenApi 定义创建的客户端。生成的代码具有接口和类。它在构造函数中需要一个 HttpClient。我为 DI 引擎进行了这样的设置。

services.AddScoped<IConsumerAdapterClient, ConsumerAdapterClient>(sp =>
{
    var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
    var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
    return new ConsumerAdapterClient(baseUrl,httpClientFactory.CreateClient());
});
Run Code Online (Sandbox Code Playgroud)

但这对我来说似乎不太正确。经过一番搜索,我找到了这篇文章。现在相同的代码看起来像这样。

services.AddHttpClient<IConsumerAdapterClient, ConsumerAdapterClient>(client =>
{
    var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
    client.BaseAddress = new Uri(baseUrl);
});
Run Code Online (Sandbox Code Playgroud)

好的,这样更好。但现在我必须增加安全性。为此,我有一个名为 ClientCredentialProvider 的类,它负责使用客户端凭据流获取访问令牌的工作。我不想修改生成的代码(这只是要求维护问题),因此,我可以利用 NSwagStudio 将类生成为部分类的事实。这意味着我可以创建一个新的构造函数并将安全元素注入到类中,然后我可以在准备方法挂钩中使用它。

public partial class ConsumerAdapterClient
{
    private readonly IClientCredentialProvider _clientCredentialProvider;

    public ConsumerAdapterClient(HttpClient httpClient, IClientCredentialProvider clientCredentialProvider)
    {
        _httpClient = httpClient;
        _clientCredentialProvider = clientCredentialProvider;
        _settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
    }

    protected async Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, StringBuilder url)
    {
        await PrepareRequestAsync(client, request, url.ToString());
    }

    protected async Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, string url)
    {
        if (_clientCredentialProvider != null)
        {
            var accessToken = await _clientCredentialProvider.GetAccessToken();
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        }
    }

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
    protected async Task ProcessResponseAsync(HttpClient client, HttpResponseMessage response, CancellationToken cancellationToken)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
    {
        // nothing to do here.
    }
}
Run Code Online (Sandbox Code Playgroud)

这就产生了一个新问题,因为它现在抱怨多个构造函数,并且不知道该选择哪一个。从生成的代码中删除构造函数后,它按预期工作。

所以这里存在三个问题。

  • 一是我必须从生成的代码中删除构造函数。这会产生维护问题,但这并不是一个可怕的问题,尝试运行测试很快就会发现它。

  • 其次,因为我必须从生成的代码中删除构造函数,所以我必须移动 _settings 的初始化并将其复制到分部类中的构造函数。这是一次性的事情,所以我可以忍受它。

  • 第三个是我使用部分类来完成此操作。虽然这本身并不是什么大问题,但我有很多这样的客户端类,并且需要为每个类一遍又一遍地创建相同的东西。

那么,如何在不一遍又一遍地创建样板代码的情况下实现此类呢?

我把部分课程变成了基础课程。然后我告诉 NSwagStudio 我的基类,这样当代码生成时它将使用基类。我现在遇到的唯一问题是我无法重载构造函数。现在,我没有注入 ClientCredentialProvider,而是回到了开始的方式,但为 ClientCredentialProvider 设置了一个属性。

public class ClientServicesBase
{
    private IClientCredentialProvider _clientCredentialProvider;

    public IClientCredentialProvider ClientCredentialProvider
    {
        set => _clientCredentialProvider = value;
    }
// The rest of the code is the same as above.
}
Run Code Online (Sandbox Code Playgroud)

因此,要设置它,它看起来像这样。

services.AddScoped<IConsumerAdapterClient, ConsumerAdapterClient>(sp =>
{
    var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
    var clientCredentialProvider = sp.GetRequiredService<IClientCredentialProvider>();
    var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
    return new ConsumerAdapterClient(baseUrl,httpClientFactory.CreateClient())
    {
        ClientCredentialProvider = clientCredentialProvider
    };
});
Run Code Online (Sandbox Code Playgroud)

所以这有效并且它做了我想要它做的事情,但它并没有以代表微软推荐的解决方案的方式完成。
如何:使用 DI 引擎添加安全元素,而无需从服务容器中提取参数并新建该类的新实例?

Ran*_*l W 3

根据杰里米对我的问题的评论,我创建了一个从生成的类派生的新类。它没有添加任何新功能,但添加了带有强类型参数的构造函数。我仍然使用为我处理安全部分的基类,但现在我以 Microsoft 推荐的方式使用 DI 引擎。

public class ConsumerAdapterClientExtension : ConsumerAdapterClient, IConsumerAdapterClient
{
    public ConsumerAdapterClientExtension(HttpClient httpClient, IClientCredentialProvider clientCredentialProvider) : base(httpClient)
    {
        ClientCredentialProvider = clientCredentialProvider;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后当我在启动类中连接它时,它看起来像这样。

services.AddHttpClient<IConsumerAdapterClient, ConsumerAdapterClientExtension>(client =>
{
    var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
    client.BaseAddress = new Uri(baseUrl);
});
Run Code Online (Sandbox Code Playgroud)