ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Net.Http.HttpClient'.
means your code disposed an HttpClient and then called it again. Find the
using or Dispose() on the shared client and remove it, so the
client lives as long as the class that holds it.
In the test below a class kept one client in a field and wrapped each call in
using (_http). The first call worked and disposed the client; the second
threw. Dropping the using made both calls succeed.
The error
PS ...\Cli> dotnet run --no-build -- disposed
200 from /api/v1/patients: Maria Garcia, David Chen
Unhandled exception. System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Http.HttpClient'.
at System.Net.Http.HttpClient.CheckRequestBeforeSend(HttpRequestMessage request)
at System.Net.Http.HttpClient.GetStringAsync(Uri requestUri, CancellationToken cancellationToken)
at PatientApi.GetPatientsAsync() in ...\Cli\PatientApi.cs:line 9
at Program.<Main>$(String[] args) in ...\Cli\Program.cs:line 98
at Program.<Main>(String[] args)
The class that threw, called twice in a row:
class PatientApi
{
private readonly HttpClient _http = new() { BaseAddress = new Uri("http://127.0.0.1:15801/api/v1/") };
public async Task<string> GetPatientsAsync()
{
using (_http)
{
return await _http.GetStringAsync("patients");
}
}
}
Why it happens
using (_http) calls Dispose() on the field when the block ends,
even though the field is meant to serve later calls. The first call returned the test
server's answer and then disposed the client on the way out. The second call reached
HttpClient.CheckRequestBeforeSend and threw there; the stack trace ends in that
method, with no connection code below it.
The pattern is easy to write by reflex, because HttpClient implements
IDisposable and wrapping a disposable in using looks tidy. The
question to ask is ownership: dispose the client where its owner is done with it, not
inside each method that uses it. The first call succeeding is what hides the bug in a
quick test that makes one request.
The fix
class PatientApiFixed
{
private readonly HttpClient _http = new() { BaseAddress = new Uri("http://127.0.0.1:15801/api/v1/") };
public async Task<string> GetPatientsAsync()
{
return await _http.GetStringAsync("patients");
}
}
PS ...\Cli> dotnet run --no-build -- disposed-fixed
200 from /api/v1/patients: Maria Garcia, David Chen
200 from /api/v1/patients: Maria Garcia, David Chen
The only change is the removed using. The field stays alive with the object,
and two calls in a row both returned 200. When the owning object itself is finished, that
is the place to dispose the client, once.
How it was reproduced
A console app (dotnet new console, no packages) on .NET SDK 10.0.403 with a
PatientApi class in PatientApi.cs called
GetPatientsAsync() twice. The calls went to a minimal API
(dotnet new web) on http://127.0.0.1:15801 with a GET route at
/api/v1/patients, started for the test and stopped after it. The fixed class
differs from the failing one only by the missing using block.
Frequently asked
- Why does HttpClient throw Cannot access a disposed object on the second call?
- The first call disposed it. In the test a using block around a field-held client disposed the client at the end of the first call, and the second call threw ObjectDisposedException.
- Should I wrap HttpClient in a using statement?
- Not when the same instance serves later calls. Dispose the client where its owner is finished with it; in the test, removing the using from the method made two calls in a row succeed.
- Where does HttpClient throw the ObjectDisposedException?
- In the test the stack trace ended in HttpClient.CheckRequestBeforeSend, called from GetStringAsync, with no connection code in the trace.
More decoded errors in the Fixes category. The EF Core version of this message is covered in Cannot access a disposed context instance.