【问题标题】:.Net Core 1.1 HttpClient with DI.Net Core 1.1 HttpClient 与 DI
【发布时间】:2023-03-08 01:06:01
【问题描述】:

以下是我正在使用的代码:

  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 作为临时服务注入每个业务服务,这是个好主意吗?有什么想法

【问题讨论】:

    标签: rest singleton asp.net-core-1.0


    【解决方案1】:

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

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

    如果您坚持使用 1.1,那么您需要实现自己的方法来重用 HttpClient 实例。最直接的方法是使用“访问器”类,然后您可以利用它们将不同的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);        
    }
    

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

    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>();
    

    【讨论】:

    • 感谢您的回答。迁移正在进行中,但至少需要 2 个月才能投入生产。我计划将其持续到 2.0,但似乎我必须思考方向2.1。如果我实际上将 ApiQuery 设为单例并在那里放置我的 HttpClient 对象,这有什么区别吗?
    • 这也应该可以。目标是在应用程序的整个生命周期内,您只有一个用于此服务的 HttpClient 实例。
    猜你喜欢
    • 1970-01-01
    • 2017-09-29
    • 1970-01-01
    • 1970-01-01
    • 2023-02-08
    • 1970-01-01
    • 2021-03-04
    • 2018-08-03
    • 2021-04-14
    相关资源
    最近更新 更多