带有 DI 的 .Net Core 1.1 HttpClient [英] .Net Core 1.1 HttpClient with DI

查看:47
本文介绍了带有 DI 的 .Net Core 1.1 HttpClient的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下是我使用的代码:

  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 中,我注入它

In the Startup.cs, I inject it

services.AddTransient<IApiQuery, ApiQuery>();

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

I have recently made these changes as previously the code was actually instantiating the httpClient in each of the method, i.e., var client = new HttpClient();

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

At some places it was like: using(var client = new HttpClient()){}

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

I Think due to such code, the the appPool is showing error due to which my IIS hangs, and the problem only get solved by restarting the appPool. I conclude this to be problem as I went through a lot of other articles. What I was not able to conclude is on if it should be a good idea to inject the ApiQuery Service as singleton itself. Will it be better to inject it as ?

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

As I am injecting IApiQuery to every business service right now as a transient service, will it be good idea ? Any thoughts

推荐答案

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

HttpClient should be singleton-scoped. There's a finite number of connections available on your machine, and since HttpClient holds on to connections it creates, having multiple instances floating around can quickly exhaust your connection pool.

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

Beginning with ASP.NET Core 2.1, there exists IHttpClientFactory which provides a simple and reusable way of injecting properly scoped HttpClient instances. However, since you're using 1.1, that's not available to you. The recommended path would be to upgrade your project to 2.1. The 1.X line of ASP.NET Core is frankly trash. It wasn't ready for production use, despite being an official release.

如果您坚持使用 1.1,那么您需要实现自己的方法来重用 HttpClient 实例.最直接的方法是使用访问器"类,然后您可以利用这些类将不同的 HttpClient 注入到不同的对象中.例如:

If you insist on sticking with 1.1, then you'll need to implement your own method of reusing HttpClient instances. The most straightforward way is to use "accessor" classes, which you can then utilize to inject different HttpClients into different objects. For example:

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 也只会被创建一次).然后,将您的类设置为在其构造函数中接受此访问器:

Then, you can register this accessor class as a singleton, meaning it will only be created once (so the contained HttpClient will also only be created once). Then, set up your class to accept this accessor in its constructor:

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

这篇关于带有 DI 的 .Net Core 1.1 HttpClient的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆