2

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

4
  • Your HttpContent can not be reused out of the box if it is a stream. You need to rewind the underlying stream before re-issuing an http request. Commented Dec 14, 2022 at 15:15
  • what do you mean by rewinding it ? Commented Dec 14, 2022 at 15:29
  • see this stackoverflow.com/questions/26942514/… Commented Dec 14, 2022 at 15:31
  • Setting the stream's Position from the last to the first to be able to re-read its content Commented Dec 14, 2022 at 15:54

2 Answers 2

2

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:

Pass the serialized string as parameter

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); ... } ... } 

Pass the to-be-serialized object as parameter

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); ... } ... } 
  • Here we are taking advantage of the JsonContent type which was introduced in .NET 5

Pass the to-be-serialized object as parameter #2

async 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); ... } ... } 
Sign up to request clarification or add additional context in comments.

13 Comments

Coming back to this after a while, in the end i went with this var response = await _retryPolicy.ExecuteAndCaptureAsync(async token => await HttpClient.PostAsync(url, new StringContent(json, Encoding.UTF8, "application/json"), token), CancellationToken.None); and it has worked while using the local API on IIS but as soon as i connect to an API on a live server i get the cannot access a closed stream error again. Any ideas why ? :)
@AdrianRadulescu Could you please share with me the related code fragment via pastebin.com?
sure, pastebin.com/R3cERYqB, the other parts of the code are mainly the same, the only difference is that the class is static now, form which i control the requests sent from the app
@AdrianRadulescu How do you get the HttpClient instance?
@AdrianRadulescu I've checked it and I don't see any reason why do you have the observed behaviour. It seems like you recreate the disposables rather than reusing them (which is good), so I'm out of ideas. Sorry :(
|
0

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.