refit: Content is null when ApiResponse followed by EnsureSucessStatusCodeAsync()
Hi,
I have noticed an issue with refit when getting a response wrapped in a refit ApiResponse which them calls EnsureSuccessStatusCodeAsync()
The ApiException thrown by the EnsureSuccessStatusCodeAsync() has null content even when content exists in the httpResponseMessage.
This sort of code:
Task<ApiResponse<InvestmentsResponseDto>> InsertInvestments(
......
// ApiResponse<t>
var investmentsResponse = await this.investmentsHttp.InsertInvestments(
commandContext.Command.PrivateQuoteId,
request,
InvestmentConstants.MediaTypes.InvestmentAccountStrategyV100,
etagValue);
await investmentsResponse.EnsureSuccessStatusCodeAsync();
The issue seems to be caused because ApiException disposes of the response content
try
{
exception.ContentHeaders = response.Content.Headers;
exception.Content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
response.Content.Dispose();
}
In RequestBuilderImplementation, on error for an ApiResponse response, it created an ApiException which is passed in to the ApiResponse
var exception = await ApiException.Create(rq, restMethod.HttpMethod, resp, restMethod.RefitSettings).ConfigureAwait(false);
if (isApiResponse)
{
return ApiResponse.Create<T>(resp, default(T), exception);
}
The call to EnsureSuccessStatusCodeAsync() then creates another ApiException (if not successful). However because the response content has been disposed by the previous ApiException creation, the content is now null.
There seems to be a couple of ways of resolving this.
- Remove the dispose calls from ApiException (and ApiResponse?)
- Change EnsureSucessStatusCodeAsync to use the exception passed in via the contructor (if it exists). This sort of code:
if (!IsSuccessStatusCode)
{
if (this.Error == null)
{
this.Error = await ApiException.Create(response.RequestMessage, response.RequestMessage.Method, response).ConfigureAwait(false);
}
Dispose();
throw this.Error;
}
return this;
I am happy to make the changes but will be guided by your suggestions for the best way.
Many thanks.
Core 2.1
Refit 4.6.30
Visual Studio 2017 v4.7.03056
Windows 10
About this issue
- Original URL
- State: open
- Created 6 years ago
- Reactions: 1
- Comments: 23 (7 by maintainers)
Ah. Well that’s where the code conspires with the other code to create the bug. See the comment by the OP.
The root cause is the self-disposal. I think its really odd to self-dispose. To my mind, the Dispose pattern is there for consumers.
If you implement
IDisposable
, you’re handing the disposal control over to the API consumer.Else if you’re going to encapsulate clearing down nicely, then don’t implement
IDisposable
and don’t expose theDispose
method.At present you have this situation where you’re saying “hey I implement IDisposable, you might wanna dispose of me… err, unless you call this method in which case I secretly dispose for you.”
Another way to think of it is that classes should throw
ObjectDisposedException
if touched after disposal. But if you did this, then this code would break.Does that make sense?
I’m not stubborn, I could be mad. I’d love someone else’s opinion.
v6 has the same error, Luke is right, Refit should not dispose the response content as a consumer. Can we re-open this issue? To fix this issue , we can just add a simple
if
check or remove theDispose()
call.This works for me though.
if (apiResponse.Error != null) { throw apiResponse.Error; } var response = apiResponse.Content; return response;