我有一个 ASP.NET MVC 应用程序,每次在 UI 中按下按钮时,它都会调用一个 ASP.NET Web API REST 服务。
每次按下此按钮时,DumpWarehouseDataIntoFile
都会执行以下方法。
public class MyClass
{
private static HttpClient client = new HttpClient();
public async Task DumpWarehouseDataIntoFile(Warehouse myData, string path, string filename)
{
try
{
//Hosted web API REST Service base url
string Baseurl = "http://XXX.XXX.XX.X:YYYY/";
//using (var client = new HttpClient()) --> I have declared client as an static variable
//{
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
//Define request data format
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Serialize parameter to pass to the asp web api rest service
string jsonParam = Newtonsoft.JsonConvert.SerializeObject(myData);
//Sending request to find web api REST service resource using HttpClient
var httpContent = new StringContent(jsonParam, Encoding.UTF8, "application/json");
HttpResponseMessage Res = await client.PostAsync("api/Warehouse/DumpIntoFile", httpContent);
//Checking the response is successful or not which is sent using HttpClient
if (Res.IsSuccessStatusCode)
{
// Some other sftuff here
}
//}
}
catch (Exception ex)
{
// Do some stuff here
} // End Try
} // End DumpWarehouseDataIntoFile method
} // End class
Warehouse
类对象:
public class Warehouse
{
public DataTable dt { get; set; }
public string Filepath { get; set; }
}
我在这篇文章中发现了这种模式:
using (var myClient = new HttpClient())
{
}
不建议使用,因为它会导致套接字耗尽 ( System.Net.Sockets.SocketException
)。建议将其HttpClient
用作静态变量并重用它,因为它有助于减少套接字的浪费。所以我使用了一个静态变量。
这种方法的问题(在我的场景中)是它只在第一次按下按钮时起作用,下一次按下按钮并DumpWarehouseDataIntoFile
执行方法,抛出以下异常:
执行请求时发生未处理的异常。
System.InvalidOperationException
: 此实例已启动一个或多个请求。只能在发送第一个请求之前修改属性。
正如错误所说,基地址等属性只能在发送第一个请求之前修改一次。
我用谷歌搜索并找到了一些建议的解决方案:
第一个解决方案
因此,正如这里提出的那样,单例模式似乎是一个不错的选择。在 Alper 提出的单例下:
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
//You need to install package Newtonsoft.Json > https://www.nuget.org/packages/Newtonsoft.Json/
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public class MyApiClient : IDisposable
{
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private const string ClientUserAgent = "my-api-client-v1";
private const string MediaTypeJson = "application/json";
public MyApiClient(string baseUrl, TimeSpan? timeout = null)
{
_baseUrl = NormalizeBaseUrl(baseUrl);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
}
public async Task<string> PostAsync(string url, object input)
{
EnsureHttpClientCreated();
using (var requestContent = new StringContent(ConvertToJsonString(input), Encoding.UTF8, MediaTypeJson))
{
using (var response = await _httpClient.PostAsync(url, requestContent))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
public async Task<TResult> PostAsync<TResult>(string url, object input) where TResult : class, new()
{
var strResponse = await PostAsync(url, input);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<TResult> GetAsync<TResult>(string url) where TResult : class, new()
{
var strResponse = await GetAsync(url);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<string> GetAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.GetAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> PutAsync(string url, object input)
{
return await PutAsync(url, new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, MediaTypeJson));
}
public async Task<string> PutAsync(string url, HttpContent content)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.PutAsync(url, content))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> DeleteAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.DeleteAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose()
{
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient()
{
_httpClientHandler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false)
{
Timeout = _timeout
};
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);
if (!string.IsNullOrWhiteSpace(_baseUrl))
{
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
}
private void EnsureHttpClientCreated()
{
if (_httpClient == null)
{
CreateHttpClient();
}
}
private static string ConvertToJsonString(object obj)
{
if (obj == null)
{
return string.Empty;
}
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
private static string NormalizeBaseUrl(string url)
{
return url.EndsWith("/") ? url : url + "/";
}
}
用法
using (var client = new MyApiClient("http://localhost:8080"))
{
var response = client.GetAsync("api/users/findByUsername?username=alper").Result;
var userResponse = client.GetAsync<MyUser>("api/users/findByUsername?username=alper").Result;
}
我在这里看到的问题是,如果您多次调用上述代码(在我的情况下是每次按下 UI 上的按钮并调用DumpWarehouseDataIntoFile
方法),您MyApiClient
每次都会创建一个实例,因此HttpClient
会创建一个新的实例我想重用HttpClient
,而不是制作很多实例。
第二种解决方案
按照Nico的建议创建一种工厂。在他建议的代码下方:
public interface IHttpClientFactory
{
HttpClient CreateClient();
}
public class HttpClientFactory : IHttpClientFactory
{
static string baseAddress = "http://example.com";
public HttpClient CreateClient()
{
var client = new HttpClient();
SetupClientDefaults(client);
return client;
}
protected virtual void SetupClientDefaults(HttpClient client)
{
client.Timeout = TimeSpan.FromSeconds(30); //set your own timeout.
client.BaseAddress = new Uri(baseAddress);
}
}
用法
public HomeController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
readonly IHttpClientFactory _httpClientFactory;
public IActionResult Index()
{
var client = _httpClientFactory.CreateClient();
//....do your code
return View();
}
在这里,您再次创建一个新实例,HttpClient
每次调用CreateClient
. 您不重用HttpClient
对象。
第三个解决方案
使用这里IHttpClientFactory
解释的HTTP 请求。问题是它只适用于 .NET Core,而不是标准的 ASP.NET Framework,尽管它似乎可以通过安装这个 nuget package来使用。它似乎自动有效地管理实例,我想将它应用到我的场景中。我想避免重新发明轮子。HttpClient
我从未使用过IHttpClientFactory
,也不知道如何使用它:配置一些功能,如基地址、设置请求标头、创建一个实例,HttpClient
然后调用PostAsync
它作为参数传递HttpContent
.
我认为这是最好的方法,所以有人可以告诉我我需要做的必要步骤,以便在DumpWarehouseDataIntoFile
方法中做同样的事情,但使用IHttpClientFactory
?我有点迷茫,我不知道如何申请做与我在方法IHttpClientFactory
内做的一样的事情。DumpWarehouseDataIntoFile
此处未提出的任何其他解决方案以及一些代码片段将不胜感激。