.Net Core 1.1 HttpClient 与 DI

.Net Core 1.1 HttpClient with DI

以下是我使用的代码:

  namespace MySite.Api
{
    using System.Collections.Specialized;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Configuration;
    using Newtonsoft.Json;
    using System;
    using System.Net.Http.Headers;
    using Microsoft.Extensions.Logging;

    /// <summary>
    /// API query execution helper
    /// </summary>
    public class ApiQuery : IApiQuery
    {
        /// <summary>
        /// configuration reference
        /// </summary>
        private IConfiguration config;

        private HmacAuthenticationUtils hmacUtils;

        private readonly ILogger logger;

        private static readonly HttpClient httpClient = new HttpClient();

        private static readonly HttpClient httpClientHMAC = new HttpClient();
        /// <summary>
        /// Initializes a new instance of the <see cref="ApiQuery"/> class.
        /// </summary>
        /// <param name="inConfig">injected configuration</param>
        public ApiQuery(IConfiguration inConfig, HmacAuthenticationUtils hmacUtils, ILoggerFactory loggerFactory)
        {
            this.config = inConfig;
            this.hmacUtils = hmacUtils;
            this.logger = loggerFactory.CreateLogger("perfLogger");
        }

        /// <summary>
        /// HTTP verb post
        /// </summary>
        /// <param name="requestUrl">API url</param>
        /// <param name="requestData">request data</param>
        /// <returns>HTTP response message</returns>
        public virtual async Task<string> Post(string requestUrl, object requestData, HttpClient client = null)
        {
            return await PostBypassCache(requestUrl, requestData, client);
        }

        /// <summary>
        /// HTTP verb post, specifically to bypass cache
        /// </summary>
        /// <param name="requestUrl">API url</param>
        /// <param name="requestData">request data</param>
        /// <returns>HTTP response message</returns>
        public async Task<string> PostBypassCache(string requestUrl, object requestData, HttpClient client = null)
        {
            DateTime perfStart = DateTime.Now;

            string customerJson = string.Empty;
            if (requestData is string)
            {
                customerJson = requestData.ToString();
            }
            else
            {
                customerJson = JsonConvert.SerializeObject(requestData);
            }

            ////just some template output to test which I'm getting back.
            string resultJson = "{ 'status':'No Content'}";
            if (client == null)
            {
                client = httpClient;
            }
            var response = await httpClient.PostAsync(requestUrl, new StringContent(customerJson, Encoding.UTF8, "application/json"));
            if (response.StatusCode == HttpStatusCode.OK)
            {
                resultJson = await response.Content.ReadAsStringAsync();
            }

            logger.LogInformation("response time: " + (DateTime.Now - perfStart).TotalMilliseconds + "ms. Resource:" + requestUrl);
            return resultJson;
        }

        /// <summary>
        /// HTTP verb post
        /// </summary>
        /// <param name="requestUrl">API url</param>
        /// <param name="requestData">request data</param>
        /// <param name="headerset">header data</param>
        /// <returns>string data</returns>
        public async Task<string> PostHmacAuth(string requestUrl, string requestData)
        {
            var httpRequest = new HttpRequestMessage(HttpMethod.Post, requestUrl);
            httpRequest.Content = new StringContent(requestData, Encoding.UTF8, "application/json");
            var signature = await Utils.GenerateAuthenticationString(httpRequest);
            httpClientHMAC.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(HmacAuthenticationUtils.HmacHeader, signature);
            return await PostBypassCache(requestUrl, requestData, httpClientHMAC);
        }

    }
}

在Startup.cs里,我注入

services.AddTransient<IApiQuery, ApiQuery>();

我最近做了这些更改,因为以前的代码实际上是在每个方法中实例化 httpClient,即 var client = new HttpClient();

有些地方是这样的: 使用(var client = new HttpClient()){}

我认为由于此类代码,appPool 显示错误导致我的 IIS 挂起,只有重新启动 appPool 才能解决问题。当我浏览了很多其他文章时,我认为这是个问题。我无法得出结论的是,将 ApiQuery 服务作为单例注入是否是个好主意。 注入它会更好吗?

由于我现在将 IApiQuery 作为临时服务注入每个业务服务,这是个好主意吗?有什么想法

HttpClient 应该是单例范围的。您的计算机上可用的连接数量有限,并且由于 HttpClient 保留它创建的连接,因此有多个实例浮动会很快耗尽您的连接池。

从 ASP.NET Core 2.1 开始,IHttpClientFactory 提供了一种简单且可重用的方式来注入适当范围的 HttpClient 实例。但是,由于您使用的是 1.1,因此您无法使用它。推荐的路径是将您的项目升级到 2.1。 ASP.NET 核心的 1.X 行坦率地说是垃圾。尽管是正式版本,但尚未准备好用于生产。

如果您坚持使用 1.1,那么您将需要实现自己的重用 HttpClient 实例的方法。最直接的方法是使用 "accessor" classes,然后您可以利用它们将不同的 HttpClients 注入到不同的对象中。例如:

public class ApiHttpClientAccessor : IDisposable
{
    public ApiHttpClientAccessor()
    {
        HttpClient = new HttpClient
        {
            BaseAddress = new Uri("https://foo.com")
        };
    }

    public HttpClient HttpClient { get; }

    private bool _disposed;

    public virtual void Dispose(bool disposing)
    {
        if (disposing && !_disposed)
        {
            HttpClient.Dispose();
        }
        _disposed = true;
     }

     public bool Dispose() =>
         Dispose(true);        
}

然后,您可以将此访问器 class 注册为单例,这意味着它只会创建一次(因此包含的 HttpClient 也只会创建一次)。然后,设置您的 class 以在其构造函数中接受此访问器:

public class ApiQuery : IApiQuery
{
    private readonly HttpClient _client;

    public ApiQuery(ApiHttpClientAccessor httpClientAccessor)
    {
        _client = (httpClientAccessor ?? throw new ArgumentNullException(nameof(httpClientAccessor))).HttpClient;
    }

    ...
}

Startup.cs中:

services.AddSingleton<ApiHttpClientAccessor>();
services.AddTransient<IApiQuery, ApiQuery>();