c# - 在 ASP.Net Core 中使用 HTTPClient 作为 DI Singleton 的最佳方式

标签 c# asp.net-core dotnet-httpclient

我正在尝试找出如何最好地使用 ASP.Net Core 中的 HttpClient 类。

根据文档和几篇文章,该类最好在应用程序的生命周期内实例化一次,并为多个请求共享。不幸的是,我找不到如何在 Core 中正确执行此操作的示例,因此我提出了以下解决方案。

我的特殊需求需要使用 2 个不同的端点(我有一个用于业务逻辑的 APIServer 和一个 API 驱动的 ImageServer),所以我的想法是有 2 个我可以在应用程序中使用的 HttpClient 单例。

我在 appsettings.json 中配置了我的服务点,如下所示:

"ServicePoints": {
"APIServer": "http://localhost:5001",
"ImageServer": "http://localhost:5002",
}

接下来,我创建了一个 HttpClientsFactory,它将实例化我的 2 个 httpclients 并将它们保存在一个静态字典中。

public class HttpClientsFactory : IHttpClientsFactory
{
    public static Dictionary<string, HttpClient> HttpClients { get; set; }
    private readonly ILogger _logger;
    private readonly IOptions<ServerOptions> _serverOptionsAccessor;

    public HttpClientsFactory(ILoggerFactory loggerFactory, IOptions<ServerOptions> serverOptionsAccessor) {
        _logger = loggerFactory.CreateLogger<HttpClientsFactory>();
        _serverOptionsAccessor = serverOptionsAccessor;
        HttpClients = new Dictionary<string, HttpClient>();
        Initialize();
    }

    private void Initialize()
    {
        HttpClient client = new HttpClient();
        // ADD imageServer
        var imageServer = _serverOptionsAccessor.Value.ImageServer;
        client.BaseAddress = new Uri(imageServer);
        HttpClients.Add("imageServer", client);

        // ADD apiServer
        var apiServer = _serverOptionsAccessor.Value.APIServer;
        client.BaseAddress = new Uri(apiServer);
        HttpClients.Add("apiServer", client);
    }

    public Dictionary<string, HttpClient> Clients()
    {
        return HttpClients;
    }

    public HttpClient Client(string key)
    {
        return Clients()[key];
    }
  } 

然后,我创建了以后定义我的 DI 时可以使用的接口(interface)。请注意,HttpClientsFactory 类继承自此接口(interface)。

public interface IHttpClientsFactory
{
    Dictionary<string, HttpClient> Clients();
    HttpClient Client(string key);
}

现在我准备将它注入(inject)到我的依赖容器中,如下所示在 ConfigureServices 方法下的 Startup 类中。

// Add httpClient service
        services.AddSingleton<IHttpClientsFactory, HttpClientsFactory>();

现在一切都已准备就绪,可以开始在我的 Controller 中使用它了。
首先,我接受了依赖。为此,我创建了一个私有(private)类属性来保存它,然后将其添加到构造函数签名并通过将传入对象分配给本地类属性来完成。

private IHttpClientsFactory _httpClientsFactory;
public AppUsersAdminController(IHttpClientsFactory httpClientsFactory)
{
   _httpClientsFactory = httpClientsFactory;
}

最后,我们现在可以使用工厂来请求 htppclient 并执行调用。下面是一个示例,我使用 httpclientsfactory 从图像服务器请求图像:

[HttpGet]
    public async Task<ActionResult> GetUserPicture(string imgName)
    {
        // get imageserver uri
        var imageServer = _optionsAccessor.Value.ImageServer;

        // create path to requested image
        var path = imageServer + "/imageuploads/" + imgName;

        var client = _httpClientsFactory.Client("imageServer");
        byte[] image = await client.GetByteArrayAsync(path);

        return base.File(image, "image/jpeg");
    }

完成!

我已经对此进行了测试,它在我的开发环境中运行良好。但是,我不确定这是否是实现它的最佳方式。我仍然有以下问题:

  1. 这个解决方案线程安全吗? (根据 MS 文档:“此类型的任何公共(public)静态(在 Visual Basic 中共享)成员都是线程安全的。”)
  2. 这种设置是否能够在不打开许多单独连接的情况下处理重负载?
  3. 在 ASP.Net Core 中如何处理“Singleton HttpClient?”中描述的 DNS 问题。当心这种严重的行为以及如何解决。”位于http://byterot.blogspot.be/2016/07/singleton-httpclient-dns.html
  4. 还有其他改进或建议吗?

最佳答案

如果使用 .net core 2.1 或更高版本,最好的方法是使用新的 HttpClientFactory。我想微软意识到人们遇到的所有问题,所以他们为我们做了艰苦的工作。请参阅下文了解如何设置。

注意:添加对 Microsoft.Extensions.Http 的引用。

1 - 添加一个使用 HttpClient 的类

public interface ISomeApiClient
{
    Task<HttpResponseMessage> GetSomethingAsync(string query);
}

public class SomeApiClient : ISomeApiClient
{
    private readonly HttpClient _client;

    public SomeApiClient (HttpClient client)
    {
        _client = client;
    }

    public async Task<SomeModel> GetSomethingAsync(string query)
    {
        var response = await _client.GetAsync($"?querystring={query}");
        if (response.IsSuccessStatusCode)
        {
            var model = await response.Content.ReadAsJsonAsync<SomeModel>();
            return model;
        }
        // Handle Error
    }
}

2 - 在 Startup.cs 的 ConfigureServices(IServiceCollection services) 中注册您的客户端

var someApiSettings = Configuration.GetSection("SomeApiSettings").Get<SomeApiSettings>(); //Settings stored in app.config (base url, api key to add to header for all requests)
services.AddHttpClient<ISomeApiClient, SomeApiClient>("SomeApi",
                client =>
                {
                    client.BaseAddress = new Uri(someApiSettings.BaseAddress);
                    client.DefaultRequestHeaders.Add("api-key", someApiSettings.ApiKey);
                });

3 - 在代码中使用客户端

public class MyController
{
    private readonly ISomeApiClient _client;

    public MyController(ISomeApiClient client)
    {
        _client = client;
    }

    [HttpGet]
    public async Task<IActionResult> GetAsync(string query)
    {
        var response = await _client.GetSomethingAsync(query);

        // Do something with response

        return Ok();
    }
}

您可以使用 services.AddHttpClient 在启动时根据需要添加任意数量的客户端并注册

感谢 Steve Gordon 和 his post here帮助我在我的代码中使用它!

关于c# - 在 ASP.Net Core 中使用 HTTPClient 作为 DI Singleton 的最佳方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42065678/

相关文章:

c# - C# Windows 服务是否需要大约 60-65 秒才能启动?

c# - 我可以根据条件禁用 ViewCell.ContextActions

c# - 如何在 docker 容器中运行 selenium chrome 驱动程序?

asp.net-core - 如何在主/从 redis 设置上负载平衡读取?

c# - Silverlight,PostAsync 没有返回响应

c# - HttpClient 302 重定向

c# - 如何为按键拍摄添加延迟

c# - 按下左/右键时防止焦点改变

Docker 中的 ASP.NET 5.0 beta 8 无法启动

c# - Httpclient 该实例已启动一个或多个请求。属性只能在发送第一个请求之前修改