Reputation: 3199
I'm getting this exception using http client in my api.
An unhandled exception has occurred while executing the request. System.InvalidOperationException: This instance has already started one or more requests. Properties can only be modified before sending the first request.
and I injected my service as
services.AddSingleton<HttpClient>()
I thought singleton was my best bet. what could be my problem?
edit: my usage
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;
}
}
Upvotes: 30
Views: 41746
Reputation: 15630
It's preferable to add the url of your request and the headers at the message, rather than on the client. better not to use the BaseAddress
or DefaultRequestHeaders
unless you have have to.
HttpRequestMessage yourmsg = new HttpRequestMessage {
Method = HttpMethod.Put,
RequestUri = new Uri(url),
Headers = httpRequestHeaders;
};
httpClient.SendAsync(yourmsg);
It Works well for reusing a single HttpClient
for many requests
Upvotes: 1
Reputation: 6074
Singletons are the correct approach. Using scoped or transient will prevent connection pooling and lead to perf degradations and port exhaustion.
If you have consistent defaults then those can be initialized once when the service is registered:
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);
If you don't have consistent defaults then BaseAddress and DefaultRequestHeaders should not be used. Create a new HttpRequestMessage instead:
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);
Upvotes: 15
Reputation: 12683
This is the design of the class HttpClient .Net Core Source.
The interesting method here is the CheckDisposedOrStarted()
.
private void CheckDisposedOrStarted()
{
CheckDisposed();
if (_operationStarted)
{
throw new InvalidOperationException(SR.net_http_operation_started);
}
}
Now this is called when setting the properties
BaseAddress
Timeout
MaxResponseContentBufferSize
So if you are planning to reuse the HttpClient
instance you should setup a single instance that presets those 3 properties and all uses must NOT modify these properties.
Alternativly you can create a factory or use simple AddTransient(...)
. Note that AddScoped
is not best suited here as you will recieve the same instance per request scope.
Edit Basic Factory
Now a factory is nothing more than a service that is responsible for providing an instance to another service. Here is a basic factory to build your HttpClient
now realize this is only the most basic you can extend this factory to do as you wish and presetup every instance of the 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);
}
}
Now why did I use and interface? This is done as using dependency injection and IoC we can easily "swap" parts of the application out very easily. Now instead of trying to access the HttpClientFactory
we access the IHttpClientFactory
.
services.AddScoped<IHttpClientFactory, HttpClientFactory>();
Now in your class, service or controller you would request the factory interface and generate an instance.
public HomeController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
readonly IHttpClientFactory _httpClientFactory;
public IActionResult Index()
{
var client = _httpClientFactory.CreateClient();
//....do your code
return View();
}
The key here is.
Scoped lifetime services are created once per request.
Upvotes: 61