我在api中使用http客户端获取此异常。
执行请求时发生了未处理的异常。 System.InvalidOperationException:此实例已启动一个或多个请求。只能在发送第一个请求之前修改属性。
我将我的服务注入
services.AddSingleton<HttpClient>()
我认为单身人士是我最好的bet。可能是我的问题?
编辑我的用法
class ApiClient
{
private readonly HttpClient _client;
public ApiClient(HttpClient client)
{
_client = client;
}
public async Task<HttpResponseMessage> GetAsync(string uri)
{
_client.BaseAddress = new Uri("http://localhost:5001/");
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json");
var response = await _client.GetAsync(uri);
return response;
}
}
答案 0 :(得分:40)
这是班级HttpClient .Net Core Source的设计。
这里有趣的方法是CheckDisposedOrStarted()
。
private void CheckDisposedOrStarted()
{
CheckDisposed();
if (_operationStarted)
{
throw new InvalidOperationException(SR.net_http_operation_started);
}
}
现在在设置属性
时调用此方法BaseAddress
Timeout
MaxResponseContentBufferSize
因此,如果您计划重复使用HttpClient
实例,则应设置一个预设这3个属性的实例,并且所有用途必须 NOT 修改这些属性。
另外,您可以创建工厂或使用简单的AddTransient(...)
。请注意,AddScoped
不适合此处,因为每个请求范围都会收到相同的实例。
编辑基本工厂
现在工厂只不过是一个负责向另一个服务提供实例的服务。这是一个基础工厂来构建你的HttpClient
现在意识到这只是最基本的,你可以扩展这个工厂按你的意愿去做,并预设HttpClient
的每个实例
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);
}
}
现在为什么我使用和界面?这是通过使用依赖注入和IoC完成的,我们可以轻松地交换&#34;部分应用程序非常容易。现在,我们不是试图访问HttpClientFactory
,而是访问IHttpClientFactory
。
services.AddScoped<IHttpClientFactory, HttpClientFactory>();
现在,在您的类,服务或控制器中,您将请求工厂界面并生成实例。
public HomeController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
readonly IHttpClientFactory _httpClientFactory;
public IActionResult Index()
{
var client = _httpClientFactory.CreateClient();
//....do your code
return View();
}
这里的关键是。
每个请求都会创建一次范围生命周期服务。
答案 1 :(得分:7)
单身人士是正确的方法。使用范围或瞬态将阻止连接池并导致性能下降和端口耗尽。
如果您有一致的默认值,那么可以在注册服务时初始化一次:
var client = new HttpClient();
client.BaseAddress = new Uri("http://example.com/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
services.AddSingleton<HttpClient>(client);
...
var incoming = new Uri(uri, UriKind.Relative); // Don't let the user specify absolute.
var response = await _client.GetAsync(incoming);
如果没有一致的默认值,则不应使用BaseAddress和DefaultRequestHeaders。改为创建一个新的HttpRequestMessage:
var incoming = new Uri(uri, UriKind.Relative); // Don't let the user specify absolute urls.
var outgoing = new Uri(new Uri("http://example.com/"), incoming);
var request = new HttpRequestMessage(HttpMethod.Get, outgoing);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await _client.SendAsync(request);
答案 2 :(得分:0)
最好在消息中添加请求的 url 和标头,而不是在客户端上。除非万不得已,否则最好不要使用 BaseAddress
或 DefaultRequestHeaders
。
HttpRequestMessage yourmsg = new HttpRequestMessage {
Method = HttpMethod.Put,
RequestUri = new Uri(url),
Headers = httpRequestHeaders;
};
httpClient.SendAsync(yourmsg);
它适用于为多个请求重用单个 HttpClient