Cannot send the same request message multiple times using Func<HttpRequestMessage> - c#

Our API has retry logic for calling another endpoint. But it kept giving me an error of
The request message was already sent. Cannot send the same request
message multiple times
Here's my code
public async Task<object> GetResponse()
{
var httpRequestMessage = ConstructHttpRequestForBatchUpdate(batchRequest, client, requestUri);
HttpResponseMessage httpResponseMessage = await _retryHttpRequest.ExecuteAsync(() => httpRequestMessage, client, maxRetryValue);
}
private HttpRequestMessage ConstructHttpRequestForBatchUpdate(JArray batchRequest, HttpClient client, Uri requestUri)
{
var batchReqStr = JsonConvert.SerializeObject(batchRequest);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var httpRequestMessage = new HttpRequestMessage()
{
Content = new StringContent(batchReqStr, Encoding.UTF8, "application/json"),
Method = HttpMethod.Put,
RequestUri = requestUri
};
return httpRequestMessage;
}
public class RetryHttpRequest : IRetryHttpRequest
{
public async Task<HttpResponseMessage> ExecuteAsync(Func<HttpRequestMessage> requestMessage, HttpClient client, int maxTryValue)
{
var remainingTries = maxTryValue;
var exceptions = new List<Exception>();
do
{
--remainingTries;
try
{
return await ExecuteSingleAsync(requestMessage(), client);
}
catch (Exception e)
{
exceptions.Add(e);
}
}
while (remainingTries > 0);
throw new AggregateException(exceptions);
}
public async Task<HttpResponseMessage> ExecuteSingleAsync(HttpRequestMessage requestMessage, HttpClient client)
{
try
{
HttpResponseMessage httpResponseMessage = await client.SendAsync(requestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
return httpResponseMessage;
}
else
{
var exception = new InvalidOperationException();
throw new Exception();
}
}
catch (HttpRequestException httpException)
{
throw httpException;
}
}
}
To my understanding, Func<HttpRequestMessage> allows it to create a new instance of HttpRequestMessage. For example, for this line of code
return await ExecuteSingleAsync(requestMessage(), client);
requestMessage() is creating a new instance for every loop. But if my understanding is correct, i am not sure why it is still giving me this error of sending the same request.

requestMessage() is creating a new instance for every loop.
This is not the case - the Func you handed in is () => httpRequestMessage, which always returns the same instance.
Try this instead:
_retryHttpRequest.ExecuteAsync(() => ConstructHttpRequestForBatchUpdate(batchRequest, client, requestUri), client, maxRetryValue);

Related

How can I consume a FileContentResult returned from an .Net Core api inside a .Net WebApi?

I am trying something similar posted [here][1]
[1]: .Net Core Receive FileContentResult from one API to Another This is what I am trying and I think I am very close,just that not sure how to receive the in the receiver Api a response which is being sent by the Producer Api as FileContentResult, any help will be appreciated.
My effort is listed below:
//Source API-working well
[HttpPost("download")]
public async Task<FileContentResult> ExportApplicationUsers2(ExportApplicationUsersQuery command)
{
try
{
var filePath = await Mediator.Send(command);
return File(System.IO.File.ReadAllBytes(filePath), "application/octet-stream");
}
catch (Exception ex)
{
throw ex.InnerException;
}
}
//Controller in Consumer Api
public async Task<Result<FileContentResult>> ExportApplicationUsers(ExportUsersRequest model)
{
try
{
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.PostFileAsync("/users/download", JsonConvert.SerializeObject(model));
return response;
//return File(System.IO.File.ReadAllBytes(response), "application/octet-stream");
}
catch (Exception ex)
{
_logger.Error(ex.Message, ex);
return new Result<FileContentResult>(new List<string> { ex.Message });
}
}
The Actual PostFileAsync() which I need to fix first:
public async Task<FileContentResult> PostFileAsync(string uri, object data)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
if (data != null)
{
request.Content = new StringContent(data.ToString(), Encoding.UTF8, "application/json");
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
var response = default(FileContentResult);
var actionTask = _httpClient.SendAsync(request)
.ContinueWith(responseTask =>
{
FileStreamResult resposneMessage = responseTask.Result;
response = (FileContentResult)resposneMessage.ReadAsStreamAsync().Result;//This is where I need help as its not able to convert into FileContentResult!
});
actionTask.Wait();
return response;
}

HttpClient How to catch every response in one place

I'm working on Xamarin.Forms App connected with Web Api 2 Api and all requests and responses work with HttClient. This is my code:
class for all my requests and definiot of HttpClient
public class DataStore : IDataStore<object>
{
HttpClient client;
public DataStore()
{
client = new HttpClient()
{
BaseAddress = new Uri($"{App.Uri}")
};
}
Example of one of my requests :
public async Task<User> GetProfileSetup()
{
try
{
if (CrossConnectivity.Current.IsConnected)
{
string token = DependencyService.Get<ISharedFunctions>().GetAccessToken();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(#"api/User/GetProfilSetup");
if (response.IsSuccessStatusCode)
{
string jsonMessage;
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
jsonMessage = new StreamReader(responseStream).ReadToEnd();
}
User user = JsonConvert.DeserializeObject<User>(jsonMessage);
return user;
}
else
{
var m = response.Content.ToString();
return null;
}
}
else
{
return null;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
string error = ex.Message;
return null;
}
}
My idea is to check every response(Response Status Code) in one place. I need this for throw Alert Errors , for refresh token etc. Is there a possible way to this ? I want to have control on every request/response.
if anyone have problem with this , just need to implement custom handler , who will inherit form DelegatingHandler. My code example:
public class StatusCodeHandler : DelegatingHandler
{
public StatusCodeHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
public GetStatusCode GetStatusCode = new GetStatusCode();
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
response = await base.SendAsync(request, cancellationToken);
if (response.IsSuccessStatusCode)
{
return response;
}
else
{
var status_code = (int)response.StatusCode;
GetStatusCode.GetResponseCode(status_code);
}
return response;
}
}
This is not related to xamarin, its a question of abstraction in OOP. You can and should abstract HttpClient and its methods to remove all the boilerplate.
Example - GetAsync<T>(url) will check for connectivity, forms request adds necessary headers, waits for response, checks response status, reads response and finally returns the deserialised response. That way, if you want to add caching layer it's easier. Basic OOP.
Abstracting your code:
public async Task<T> GetAsync(string url)
{
try
{
if (!CrossConnectivity.Current.IsConnected)
{
// throw custom exception?
new NoNetworkException();
}
var token = DependencyService.Get<ISharedFunctions>().GetAccessToken();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
// read response and throw for logging?
new InvaidResponseException();// custom exceptions makes it easier for catching
}
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
// there should be an async overload to read too
var jsonMessage = new StreamReader(responseStream).ReadToEnd();
return JsonConvert.DeserializeObject<T>(jsonMessage);
}
}
catch(NoNetworkException ex)
{
// handle
}
catch(InvaidResponseException ex)
{
// handle
}
}

How send different client certificates on different requests with a single instance of HttpClient in .Net Core?

The recommendation for HttpClient is to reuse a single instance. But from the API, it looks like the way to add certificates is on the instance, not per request. If we add two certificates, how can we make sure that "cert 1" is only sent to "one.somedomain.com", for example?
//A handler is how you add client certs (is there any other way?)
var _clientHandler = new HttpClientHandler();
//Add multiple certs
_clientHandler.ClientCertificates.Add(cert1);
_clientHandler.ClientCertificates.Add(cert2);
_clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
//Pretend this is our long-living HttpClient
var client = new HttpClient(_clientHandler);
//Now if we make a post request, will both certs be used?
using (HttpResponseMessage response = _client.PostAsync("https://one.somedomain.com", content).Result)
{
//...elided...
}
Sorry. End of year a lot of work.
Yo can try to implement somthing like this:
public class CustomHttpHandler : HttpClientHandler
{
private readonly Dictionary<string, X509Certificate> _certMap;
public CustomHttpHandler():base()
{
_certMap = new Dictionary<string, X509Certificate>() { { "server1name", new X509Certificate("cert1") }, { "server2name", new X509Certificate("cert2") } };
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
string serverName = request.RequestUri.Host;
if (ClientCertificates.Contains(_certMap[serverName]))
{
try
{
var response = await base.SendAsync(request, cancellationToken);
return response;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
throw;
}
}
else
{
ClientCertificates.Clear();
ClientCertificates.Add(_certMap[serverName]);
try
{
var response = await base.SendAsync(request, cancellationToken);
return response;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
throw;
}
}
}
}
Just an idea, not tested it.
Or, alternatively you can use Headers collection in RequestMessage instance.
This article cover the topic: https://damienbod.com/2019/09/07/using-certificate-authentication-with-ihttpclientfactory-and-httpclient/
That is the recommendation, however, you can use "using" statements.
Once HttpClient is an IDisposable, you should use something like
using(var client = new HttpClient(_clientHandler))
{
//Your code here
}

Intercept Json before returning from web.api

I am looking to intercept the returning model from my web.api and run the returning data through a translation service, i.e. Google or Azure for languages other than English. Is there way to add an attribute or additional config to intercept the model, perform the translation, and then return back to the controller?
try this code also add this code
//add this line global.asax file
GlobalConfiguration.Configuration.MessageHandlers.Add(new CustomLogHandler());
public class CustomLogHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var logMetadata = await BuildRequestMetadata(request);
var response = await base.SendAsync(request, cancellationToken);
logMetadata = await BuildResponseMetadata(logMetadata, response);
await SendToLog(logMetadata);
return response;
}
private async Task<LogMetadata> BuildRequestMetadata(HttpRequestMessage request)
{
LogMetadata log = new LogMetadata
{
RequestMethod = request.Method.Method,
RequestTimestamp = DateTime.Now,
RequestUri = request.RequestUri.ToString(),
RequestContent = await request.Content.ReadAsStringAsync(),
};
return log;
}
private async Task<LogMetadata> BuildResponseMetadata(LogMetadata logMetadata, HttpResponseMessage response)
{
logMetadata.ResponseStatusCode = response.StatusCode;
logMetadata.ResponseTimestamp = DateTime.Now;
logMetadata.ResponseContentType = response.Content == null ? string.Empty : response.Content.Headers.ContentType.MediaType;
logMetadata.Response = await response.Content.ReadAsStringAsync();
return logMetadata;
}
private async Task<bool> SendToLog(LogMetadata logMetadata)
{
try
{
//write this code
}
catch
{
return false;
}
return true;
}
}

How to set the Timeout for request and get the response code for HttpRequest in MvvmCross xamarin

Code I have constructed so for:
public class RestService : IRestService
{
public async Task<StellaData> GetStellConfigData()
{
try
{
//Declare a Http client
HttpClient client = new HttpClient();
//Add a Base URl
//client.BaseAddress = new Uri(Constants.MUrl);
//Add the response type
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Add the API
var response =await client.GetStringAsync(new Uri(Constants.mUrl));
var myItems = Newtonsoft.Json.JsonConvert.DeserializeObject<StellaData>(response);
return myItems;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
}
What I am trying to do:
Set a Timeout for the request
Catch Related Exception for the timeout for the request
Get the response code for the request
This question really has nothing to do with MvvmCross, Xamarin or Android, since you're using the same HTTP client you would in any .NET application. Nevertheless, HttpClient has a Timeout property which you can set to ensure your requests time out after a certain interval. I've changed GetStringAsync to GetAsync, since GetAsync will throw a TaskCanceledException if the request times out, which you can catch and handle. GetStringAsync would handle the timeout internally, and you wouldn't be able to catch it. I've rewritten your method to achieve that (this example has a 30-second timeout), as well as assign the status code to a variable for you to use:
public async Task<StellaData> GetStellConfigData()
{
try
{
using (var client = new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(30000)
})
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(new Uri(Constants.mUrl));
HttpStatusCode statusCode = response.StatusCode;
var myItems = Newtonsoft.Json.JsonConvert.DeserializeObject<StellaData>(await response.Content.ReadAsStringAsync());
return myItems;
}
}
catch (TaskCanceledException tcex)
{
// The request timed out
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
This class can be refactored to reuse the client instead of creating a new instance for each request. Set the time-out on the client when initialized.
public class RestService : IRestService {
private static HttpClient client = new HttpClient();
static RestService() {
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromMilliseconds(Constants.DefaultTimeOut);
}
public async Task<StellaData> GetStellConfigData() {
try {
//Add the API
using(var response = await client.GetAsync(new Uri(Constants.mUrl))) {
if (response.IsSuccessStatusCode) {
return await response.Content.ReadAsAsync<StellaData>();
}
}
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
}

Categories