繁体   English   中英

ASP.NET MVC 中的 HttpClient 单例实现

[英]HttpClient Singleton Implementation in ASP.NET MVC

阅读这篇 博文www.asp.net上的官方说明

HttpClient 旨在实例化一次并在应用程序的整个生命周期中重复使用。 特别是在服务器应用程序中,为每个请求创建一个新的 HttpClient 实例会耗尽重负载下可用的套接字数量。 这将导致 SocketException 错误。

我发现我们的代码在每次调用时都会处理 HttpClient。 我正在更新我们的代码,以便我们重用 HttClient,但我担心我们的实现但不是线程安全的。

这是新代码的当前草稿:

对于单元测试,我们为 HttpClient 实现了一个包装器,消费者调用包装器:

 public class HttpClientWrapper : IHttpClient
    {
        private readonly HttpClient _client;

        public Uri BaseAddress
        {
            get
            {
                return _client.BaseAddress;
            }

            set
            {
                _client.BaseAddress = value;
            }
        }

        public HttpRequestHeaders DefaultRequestHeaders
        {
            get
            {
                return _client.DefaultRequestHeaders;
            }
        }

        public HttpClientWrapper()
        {
            _client = new HttpClient();
        }

        public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, String userOrProcessName)
        {
            IUnityContainer container = UnityCommon.GetContainer();
            ILogService logService = container.Resolve<ILogService>();

            logService.Log(ApplicationLogTypes.Debug, JsonConvert.SerializeObject(request), userOrProcessName);

            return _client.SendAsync(request);
        }

        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing && _client != null)
                {
                    _client.Dispose();
                }

                disposedValue = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }
        #endregion
    } 

这是一个调用的服务:

public class EnterpriseApiService : IEnterpriseApiService
    {
        private static IHttpClient _client;

        static EnterpriseApiService()
        {
            IUnityContainer container = UnityCommon.GetContainer();
            IApplicationSettingService appSettingService = container.Resolve<IApplicationSettingService>();

            _client = container.Resolve<IHttpClient>();
        }

        public EnterpriseApiService() { }

        public Task<HttpResponseMessage> CallApiAsync(Uri uri, HttpMethod method, HttpContent content, HttpRequestHeaders requestHeaders, bool addJsonMimeAccept = true)
        {
            IUnityContainer container = UnityCommon.GetContainer();
            HttpRequestMessage request;

           _client.BaseAddress = new Uri(uri.GetLeftPart(UriPartial.Authority));

            if (addJsonMimeAccept)
                _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            request = new HttpRequestMessage(method, uri.AbsoluteUri);

            // Removed logic that built request with content, requestHeaders and method

            return _client.SendAsync(request, UserOrProcessName);
        }
    }

我的问题:

  1. 这是重用 HttpClient 对象的合适方法吗?
  2. 静态 _httpClient 字段(填充有静态构造函数)是否为 EnterpriseApiService 的所有实例共享? 我想确认一下,因为正在被实例方法调用。
  3. 当调用 CallApiAsync() 时,当它对静态 HttpClient 进行更改时,例如“_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"))”这些值可能会在最后一个进程之前被另一个进程覆盖行“_client.SendAsync”被称为? 我担心在处理 CallApiAsync() 中途会更新静态实例。
  4. 既然它正在调用 SendAsync(),我们是否能保证将响应映射回正确的调用者? 我想确认回复不会发给另一个来电者。

更新:由于我已经删除了 USING 语句,并且 Garage Collection 没有调用 Dispose,我将采用更安全的方法在该方法中创建一个新实例。 即使在线程生命周期内重用 HttpClient 的实例,也需要对逻辑进行大量修改,因为该方法每次调用都会设置 HttpClient 属性。

你真的想要一个实例吗?

我不认为你想要一个应用程序范围的实例。 每个线程需要一个实例。 否则你不会得到很好的表现! 此外,这将解决您的问题 #3 和 #4,因为不会有两个线程同时访问同一个 HttpClient。

你不需要单身人士

只需将Container.ResolvePerThreadLifetimeManager一起使用。

对于那些有幸使用.NET Core 的人来说,这相当简单。

正如John Wu雄辩地指出的那样,您不想要单例本身,而是每个请求都需要单例。 因此,您需要的是AddScoped<TService>()方法。

在您的ConfigureServices(IServiceCollection services)方法中:

services.AddScoped<HttpClient>();

消费:

public class HomeController 
{
    readonly HttpClient client;

    public HomeController (HttpClient client)   
    {
        this.client = client;
    }

    //rest of controller code
}

既然它正在调用 SendAsync(),我们是否能保证将响应映射回正确的调用者? 我想确认回复不会发给另一个来电者。

这将通过回调指针处理。 它与使用 HttpClient 作为单例无关。 更多细节在这里 - https://stackoverflow.com/a/42307650/895724

这是我使用的

public abstract class BaseClient : IDisposable
{

    private static object locker = new object();
    private static volatile HttpClient httpClient;

    protected static HttpClient Client
    {
        get
        {
            if (httpClient == null)
            {
                lock (locker)
                {
                    if (httpClient == null)
                    {
                        httpClient = new HttpClient();
                    }
                }
            }

            return httpClient;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (httpClient != null)
            {
                httpClient.Dispose();
            }

            httpClient = null;
        }
    }
}

它在扩展方法中使用,如下所示:

public static Task<HttpResponseMessage> PostAsJsonAsync<T>(
        this HttpClient httpClient, string url, T data, string token, IDictionary<string, string> dsCustomHeaders = null)
    {
        ThrowExceptionIf.Argument.IsNull(httpClient, nameof(httpClient));
        var dataAsString = JsonConvert.SerializeObject(data);            
        var httpReqPostMsg = new HttpRequestMessage(HttpMethod.Post, url)
        {
            Content = new StringContent(dataAsString, Encoding.UTF8, "application/json")
        };
        httpReqPostMsg.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        httpReqPostMsg.Headers.Add(Constants.TelemetryCorrelationKey, Utilities.GetRequestCorrelationId());
        if (dsCustomHeaders != null) { 
            foreach (var keyValue in dsCustomHeaders)
            {
                httpReqPostMsg.Headers.Add(keyValue.Key, keyValue.Value);
            }
        }
        return httpClient.SendAsync(httpReqPostMsg);
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM