HttpClientFactory:带有额外构造函数参数的类型化HttpClient

19

使用 HttpClientFactory,我们可以配置依赖注入以创建和管理HttpClients的生命周期:

public class GitHubService : IGitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");

        Client = client;
    }

    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        var response = await Client.GetAsync(
            "/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<GitHubIssue>>(responseStream);
    }
}

然后在 Startup.cs 中配置 DI:

services.AddHttpClient<GitHubService>();

然而,如果输入客户端具有额外的构造函数参数,应该如何提供这些参数? 例如,如果要传递存储库名称:

public class GitHubService : IGitHubService
{
    public HttpClient Client { get; }
    private readonly string _repositoryName;

    public GitHubService(HttpClient client, string repositoryName)
    {
        _repositoryName = repositoryName;

        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");

        Client = client;
    }

    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        var response = await Client.GetAsync(
            $"/repos/aspnet/{_repositoryName}/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<GitHubIssue>>(responseStream);
    }
}
也许这不是一个现实的例子,但依赖注入应该如何配置以提供仓库名称呢?
6个回答

19

我通过切换到命名的客户端,成功地使这个工作起来:

//To start with, create a named client:
services.AddHttpClient("GitHubClient", ctx => { ctx.BaseAddress = new Uri("https://api.github.com/"); });

//Then set up DI for the TypedClient
services.AddTransient<IGitHubService>(ctx =>
{
    var clientFactory = ctx.GetRequiredService<IHttpClientFactory>();
    var httpClient = clientFactory.CreateClient("GitHubClient");
 
    return new GitHubService(httpClient, repositoryName);
});
  

3
这应该是瞬态的还是单例的? - tinonetic
推荐使用HttpClient的方法。总结起来,在生命周期管理方面,您应该使用长期存在的客户端并设置PooledConnectionLifetime(适用于.NET Core和.NET 5+),或者使用由IHttpClientFactory创建的短期存在的客户端。在.NET Core和.NET 5+中:使用静态或单例的HttpClient实例,并将PooledConnectionLifetime设置为所需的时间间隔,例如2分钟,根据预期的DNS更改情况而定。 - Rmalmoe

14
如果您不想使用命名客户端的话,另一种选择是创建一个自定义类来处理额外参数。 问题在于它不知道如何解析字符串类型。您可以创建一个对象,它有一个字符串属性来包含您想要传递的值,将它注册为单例并让容器来解析它。 创建一个包含所有额外参数的类,在您的情况下是“repositoryName”。
public class RepositoryConfig
{
    public string RepositoryName {get; set;}
}

注册新类

services.AddSingleton(new RepositoryConfig { RepositoryName = "MyRepo"});

然后注册 HttpClient

services.AddHttpClient<IGitHubService, GitHubService>();
现在您的类将正确地被实例化。

8

实际上有一种简单的方法可以在一步中完成。

services.AddHttpClient<IGitHubService, GitHubService>((client, sp) =>
{
   client.BaseAddress = new Uri("https://api.github.com/");
   return new GitHubService(client, repositoryName);
});

3
也许您可以通过HttpClient的属性传递参数。仓库名称可以通过BaseAddress传递。
var repositoryName = // load from a config for example

services.AddHttpClient<GitHubService>(c =>
{
    c.BaseAddress = new Uri($"https://api.github.com/repos/aspnet/{repositoryName}");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

这是一个好主意,但不幸的是对于我的实际用例,参数在查询字符串中,我无法更新我调用的服务。最终我使用了命名客户端。感谢您的回复。 - philreed

1
如果您正在使用 Autofac作为依赖项解析器,则可以使用其 Delegate Factories来实现此目的。
它的实现大致如下:
public delegate GitHubService GitHubServiceFactory(string repositoryName);

public class AnotherService
{
    private GitHubService gitHubService;
    public AnotherService(GitHubServiceFactory gitHubServiceFactory)
    {
        this.gitHubService = gitHubServiceFactory("myRepositoryName");
    }
}

据我所知,使用内置依赖解析器无法实现此功能。

0
正确的方法是使用AddHttpClient<TClient,TImplementation>(Func<HttpClient, IServiceProvider, TImplementation>) 扩展方法
services.AddHttpClient<IGitHubService, GitHubService>((client, sp) =>
    // any other constructor dependencies in GitHubService will be filled in
    // by ActivatorUtilities from the provided IServiceProvider
    ActivatorUtilities.CreateInstance<GitHubService>(sp, client, repositoryName)
);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接