Reputation: 25
I am upgrading a Xamarin app to MAUI and thought of decoupling things a bit. Before i had a datastore which handled all requests to an API, now i have a service for each section of the app from which requests go to a HttpManager, problem is when the policy retries, it works for the first time but on the second retry it fails with the message "Cannot access a closed Stream". Searched a bit but couldn't find a fix.
I call the service from the viewModel.
LoginViewModel.cs
readonly IAuthService _authService;
public LoginViewModel(IAuthService authService)
{
_authService = authService;
}
[RelayCommand]
private async Task Login()
{
...
var loginResponse = await _authService.Login(
new LoginDTO(QRSettings.StaffCode, Password, QRSettings.Token));
...
}
In the service i set send the data to the HttpManager and process the response
AuthService.cs
private readonly IHttpManager _httpManager;
public AuthService(IHttpManager manager)
{
_httpManager = manager;
}
public async Task<ServiceResponse<string>> Login(LoginDTO model)
{
var json = JsonConvert.SerializeObject(model);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpManager.PostAsync<string>("Auth/Login", content);
...
}
And in here i send the request.
HttpManager.cs
readonly IConnectivity _connectivity;
readonly AsyncPolicyWrap _retryPolicy = Policy
.Handle<TimeoutRejectedException>()
.WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1), (exception, timespan, retryAttempt, context) =>
{
App.AppViewModel.RetryTextVisible = true;
App.AppViewModel.RetryText = $"Attempt number {retryAttempt}...";
})
.WrapAsync(Policy.TimeoutAsync(11, TimeoutStrategy.Pessimistic));
HttpClient HttpClient;
public HttpManager(IConnectivity connectivity)
{
_connectivity = connectivity;
HttpClient = new HttpClient();
}
public async Task<ServiceResponse<T>> PostAsync<T>(string endpoint, HttpContent content, bool shouldRetry = true)
{
...
// Post request
var response = await Post($""http://10.0.2.2:5122/{endpoint}", content, shouldRetry);
...
}
async Task<HttpResponseMessage> Post(string url, HttpContent content, bool shouldRetry)
{
if (shouldRetry)
{
// This is where the error occurs, in the PostAsync
var response = await _retryPolicy.ExecuteAndCaptureAsync(async token =>
await HttpClient.PostAsync(url, content, token), CancellationToken.None);
...
}
...
}
And this is the MauiProgram if it matters
...
private static MauiAppBuilder RegisterServices(this MauiAppBuilder builder)
{
...
builder.Services.AddSingleton<IHttpManager, HttpManager>();
builder.Services.AddSingleton<IAuthService, AuthService>();
return builder;
}
Can't figure out what the issue is... I tried various try/catches, tried finding a solution online but no luck. On the second retry it always gives that error
Upvotes: 2
Views: 896
Reputation: 1545
I am merely putting this out there because I ran across this stream closed error when I was doing web service calls with post in maui against a web service that I have written in azure. I'm converting some old code from xamarin to maui. I pulled my hair out for a couple of hours. then I looked at the server name and protocol I was using. I had inadvertently used http as the protocol. I switched to using https and all of the errors went away.
Upvotes: 0
Reputation: 22819
Disclaimer: In the comments section I've suggested to rewind the underlying stream. That suggestion was wrong, let me correct myself.
TL;DR: You can't reuse a HttpContent
object you need to re-create it.
In order to be able to perform a retry attempt with a POST verb you need to recreate the HttpContent
payload for each attempt.
There are several ways to fix your code:
async Task<HttpResponseMessage> Post(string url, string content, bool shouldRetry)
{
if (shouldRetry)
{
var response = await _retryPolicy.ExecuteAndCaptureAsync(async token =>
await HttpClient.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"), token), CancellationToken.None);
...
}
...
}
async Task<HttpResponseMessage> Post(string url, object content, bool shouldRetry)
{
if (shouldRetry)
{
var response = await _retryPolicy.ExecuteAndCaptureAsync(async token =>
await HttpClient.PostAsync(url, JsonContent.Create(content), token), CancellationToken.None);
...
}
...
}
JsonContent
type which was introduced in .NET 5async Task<HttpResponseMessage> Post(string url, object content, bool shouldRetry)
{
if (shouldRetry)
{
var response = await _retryPolicy.ExecuteAndCaptureAsync(async token =>
await HttpClient.PostAsJsonAsync(url, content, token), CancellationToken.None);
...
}
...
}
PostAsJsonAsync
HttpClientExtensions
HttpClientJsonExtensions
Upvotes: 2