postasync example ejemplos await c# dotnet-httpclient httpcontent

ejemplos - postasync c# example



Reintentando solicitudes fallidas de HttpClient (8)

Estoy creando una función que, dado un objeto HttpContent, emitirá una solicitud y volverá a intentarlo si falla. Sin embargo, tengo excepciones que dicen que el objeto HttpContent se elimina después de emitir la solicitud. ¿Hay alguna forma de copiar o duplicar el objeto HttpContent para que pueda emitir varias solicitudes?

public HttpResponseMessage ExecuteWithRetry(string url, HttpContent content) { HttpResponseMessage result = null; bool success = false; do { using (var client = new HttpClient()) { result = client.PostAsync(url, content).Result; success = result.IsSuccessStatusCode; } } while (!success); return result; } // Works with no exception if first request is successful ExecuteWithRetry("http://www.requestb.in/xfxcva" /*valid url*/, new StringContent("Hello World")); // Throws if request has to be retried ... ExecuteWithRetry("http://www.requestb.in/badurl" /*invalid url*/, new StringContent("Hello World"));

(Obviamente no lo intento indefinidamente, pero el código anterior es esencialmente lo que quiero).

Se produce esta excepción.

System.AggregateException: One or more errors occurred. ---> System.ObjectDisposedException: Cannot access a disposed object. Object name: ''System.Net.Http.StringContent''. at System.Net.Http.HttpContent.CheckDisposed() at System.Net.Http.HttpContent.CopyToAsync(Stream stream, TransportContext context) at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) --- End of inner exception stack trace --- at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) at System.Threading.Tasks.Task`1.get_Result() at Submission#8.ExecuteWithRetry(String url, HttpContent content)

¿Hay alguna forma de duplicar un objeto HttpContent o reutilizarlo?


ASP.NET Core 2.1 Respuesta

ASP.NET Core 2.1 agregó soporte para Polly directamente. Aquí UnreliableEndpointCallerService es una clase que acepta un HttpClient en su constructor. Las solicitudes fallidas se reintentarán con un retroceso exponencial, de modo que el siguiente reintento se realice en un tiempo exponencialmente más largo después del anterior:

// sample usage: var response = await RequestAsync(() => httpClient.GetAsync(url)); Task<HttpResponseMessage> RequestAsync(Func<Task<HttpResponseMessage>> requester) { var retryPolicy = new RetryPolicy(transientErrorDetectionStrategy, retryStrategy); //you can subscribe to the RetryPolicy.Retrying event here to be notified //of retry attempts (e.g. for logging purposes) return retryPolicy.ExecuteAsync(async () => { HttpResponseMessage response; try { response = await requester().ConfigureAwait(false); } catch (TaskCanceledException e) //HttpClient throws this on timeout { //we need to convert it to a different exception //otherwise ExecuteAsync will think we requested cancellation throw new HttpRequestException("Request timed out", e); } //assuming you treat an unsuccessful status code as an error //otherwise just return the respone here return response.EnsureSuccessStatusCode(); }); }

Además, considere leer la publicación de mi blog "Configuración óptima de HttpClientFactory" .

Respuesta de otras plataformas

Esta implementación utiliza a Polly para reintentar con un retroceso exponencial, de modo que el siguiente reintento se realice en un tiempo exponencialmente más largo que el anterior. También se reintenta si se TaskCanceledException una HttpRequestException o TaskCanceledException debido a un tiempo de espera. Polly es mucho más fácil de usar que Topaz.

private sealed class TransientErrorCatchAllStrategy : ITransientErrorDetectionStrategy { public bool IsTransient(Exception ex) { return true; } }


Duplicar el StringContent no es probablemente la mejor idea. Pero una simple modificación podría solucionar el problema. Solo modifica la función y crea el objeto StringContent dentro del bucle, algo como:

public HttpResponseMessage ExecuteWithRetry(string url, string contentString) { HttpResponseMessage result = null; bool success = false; using (var client = new HttpClient()) { do { result = client.PostAsync(url, new StringContent(contentString)).Result; success = result.IsSuccessStatusCode; } while (!success); } return result; }

y luego llamalo

ExecuteWithRetry("http://www.requestb.in/xfxcva" /*valid url*/, "Hello World");


En lugar de implementar la funcionalidad de reintento que envuelve el HttpClient , considere construir el HttpClient con un HttpMessageHandler que realice la lógica de reintento internamente. Por ejemplo:

public class RetryHandler : DelegatingHandler { // Strongly consider limiting the number of retries - "retry forever" is // probably not the most user friendly way you could respond to "the // network cable got pulled out." private const int MaxRetries = 3; public RetryHandler(HttpMessageHandler innerHandler) : base(innerHandler) { } protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { HttpResponseMessage response = null; for (int i = 0; i < MaxRetries; i++) { response = await base.SendAsync(request, cancellationToken); if (response.IsSuccessStatusCode) { return response; } } return response; } } public class BusinessLogic { public void FetchSomeThingsSynchronously() { // ... // Consider abstracting this construction to a factory or IoC container using (var client = new HttpClient(new RetryHandler(new HttpClientHandler()))) { myResult = client.PostAsync(yourUri, yourHttpContent).Result; } // ... } }


Las respuestas actuales no funcionarán como se esperaba en todos los casos, específicamente en el caso muy común de solicitud de tiempo de espera (vea mis comentarios allí).

Además, implementan una estrategia de reintento muy ingenua: muchas veces querrás algo un poco más sofisticado, como el retroceso exponencial (que es el valor predeterminado en la API de Azure Storage Client).

Me topé con TOPAZ mientras leía una publicación de blog relacionada (también ofrecía el enfoque de reintento interno equivocado). Esto es lo que se me ocurrió:

int retries = 3; var minBackoff = TimeSpan.FromSeconds(3.0); var maxBackoff = TimeSpan.FromSeconds(120.0); var deltaBackoff= TimeSpan.FromSeconds(4.0); var strategy = new ExponentialBackoff(retries, minBackoff, maxBackoff, deltaBackoff);

Tenga en cuenta el parámetro de delegado requester . No debe ser un HttpRequestMessage ya que no puede enviar la misma solicitud varias veces. En cuanto a las estrategias, eso depende de tu caso de uso. Por ejemplo, una estrategia de detección de errores transitorios podría ser tan simple como:

services .AddHttpClient<UnreliableEndpointCallerService>() .AddTransientHttpErrorPolicy( x => x.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)));

En cuanto a la estrategia de reintento, TOPAZ ofrece tres opciones:

  1. FixedInterval
  2. Incremental
  3. ExponentialBackoff

Por ejemplo, aquí está el equivalente de TOPAZ de lo que la biblioteca de almacenamiento de Azure Client usa para la configuración predeterminada:

public class HttpRetryMessageHandler : DelegatingHandler { public HttpRetryMessageHandler(HttpClientHandler handler) : base(handler) {} protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) => Policy .Handle<HttpRequestException>() .Or<TaskCanceledException>() .OrResult<HttpResponseMessage>(x => !x.IsSuccessStatusCode) .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt))) .ExecuteAsync(() => base.SendAsync(request, cancellationToken)); } using (var client = new HttpClient(new HttpRetryMessageHandler(new HttpClientHandler()))) { var result = await client.GetAsync("http://example.com"); }

Para obtener más información, visite http://msdn.microsoft.com/en-us/library/hh680901(v=pandp.50).aspx

EDITAR Tenga en cuenta que si su solicitud contiene un objeto HttpContent , deberá regenerarlo cada vez que HttpClient también lo eliminará (gracias por capturar a Alexandre Pepin). Por ejemplo () => httpClient.PostAsync(url, new StringContent("foo"))) .


Lo probé y trabajé usando pruebas de unidad e integración. Sin embargo, se atascó cuando en realidad llamé desde la URL REST. Encontré esta interesante publicación que explica por qué se atasca en esta línea.

response = await base.SendAsync(request, cancellationToken);

La solución a esto es que tiene .ConfigureAwait(false) agregado al final.

response = await base.SendAsync(request, token).ConfigureAwait(false);

También agregué crear parte de token vinculado allí de esta manera.

var linkedToken = cancellationToken.CreateLinkedSource(); linkedToken.CancelAfter(new TimeSpan(0, 0, 5, 0)); var token = linkedToken.Token; HttpResponseMessage response = null; for (int i = 0; i < MaxRetries; i++) { response = await base.SendAsync(request, token).ConfigureAwait(false); if (response.IsSuccessStatusCode) { return response; } } return response;



también se refiere a Crear un controlador de reintento transitorio para la visita de .NET HttpClient. Consulte la publicación KARTHIKEYAN VIJAYAKUMAR .

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.SqlClient; using System.Net.Http; using System.Threading; using System.Diagnostics; using System.Net; using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling; namespace HttpClientRetyDemo { class Program { static void Main(string[] args) { var url = "http://RestfulUrl"; var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); var handler = new RetryDelegatingHandler(); handler.UseDefaultCredentials = true; handler.PreAuthenticate = true; handler.Proxy = null; HttpClient client = new HttpClient(handler); var result = client.SendAsync(httpRequestMessage).Result.Content.ReadAsStringAsync().Result; Console.WriteLine(result.ToString()); Console.ReadKey(); } //The retry handler logic is implementing within a Delegating Handler. This has a number of advantages. //An instance of the HttpClient can be initialized with a delegating handler making it super easy to add into the request pipeline. //It also allows you to apply your own custom logic before the HttpClient sends the request, and after it receives the response. //Therefore it provides a perfect mechanism to wrap requests made by the HttpClient with our own custom retry logic. class RetryDelegatingHandler : HttpClientHandler { public RetryPolicy retryPolicy { get; set; } public RetryDelegatingHandler() : base() { retryPolicy = CustomRetryPolicy.MakeHttpRetryPolicy(); } protected async override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { HttpResponseMessage responseMessage = null; var currentRetryCount = 0; //On Retry => increments the retry count retryPolicy.Retrying += (sender, args) => { currentRetryCount = args.CurrentRetryCount; }; try { await retryPolicy.ExecuteAsync(async () => { responseMessage = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); if ((int)responseMessage.StatusCode > 500) { //When it fails after the retries, it would throw the exception throw new HttpRequestExceptionWithStatus(string.Format("Response status code {0} indicates server error", (int)responseMessage.StatusCode)) { StatusCode = responseMessage.StatusCode, CurrentRetryCount = currentRetryCount }; }// returns the response to the main method(from the anonymous method) return responseMessage; }, cancellationToken).ConfigureAwait(false); return responseMessage;// returns from the main method => SendAsync } catch (HttpRequestExceptionWithStatus exception) { if (exception.CurrentRetryCount >= 3) { //write to log } if (responseMessage != null) { return responseMessage; } throw; } catch (Exception) { if (responseMessage != null) { return responseMessage; } throw; } } } //Retry Policy = Error Detection Strategy + Retry Strategy public static class CustomRetryPolicy { public static RetryPolicy MakeHttpRetryPolicy() { //The transient fault application block provides three retry policies that you can use. These are: return new RetryPolicy(strategy, exponentialBackoff); } } //This class is responsible for deciding whether the response was an intermittent transient error or not. public class HttpTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy { public bool IsTransient(Exception ex) { if (ex != null) { HttpRequestExceptionWithStatus httpException; if ((httpException = ex as HttpRequestExceptionWithStatus) != null) { if (httpException.StatusCode == HttpStatusCode.ServiceUnavailable) { return true; } else if (httpException.StatusCode == HttpStatusCode.MethodNotAllowed) { return true; } return false; } } return false; } } //Custom HttpRequestException to allow include additional properties on my exception, which can be used to help determine whether the exception is a transient error or not. public class HttpRequestExceptionWithStatus : HttpRequestException { public HttpRequestExceptionWithStatus() : base() { } public HttpRequestExceptionWithStatus(string message) : base(message) { } public HttpRequestExceptionWithStatus(string message, Exception inner) : base(message, inner) { } public HttpStatusCode StatusCode { get; set; } public int CurrentRetryCount { get; set; } } } }


//Could retry say 5 times HttpResponseMessage response; int numberOfRetry = 0; using (var httpClient = new HttpClient()) { do { response = await httpClient.PostAsync(uri, content); numberOfRetry++; } while (response.IsSuccessStatusCode == false | numberOfRetry < 5); } return response; .........