I want to call one 3rd party Rest API from the asp.net core application with the pooling mechanism.
Here in the below sample code, I want to call _client.DocumentProcess(model) API with some pooling mechanism after every 10 seconds for five times until I get the Result as "Success" or my pooling call limit gets exceeded.
public async Task<Result> TrackDocumentProcess(RequestModel model)
{
var response = await _client.DocumentProcess(model);
if(response.IsSuccess)
{
if(response.Value.Status != "Success")
//Call the _client.DocumentProcess(model) API after 10 seconds.
else
return Result.Success();
}
}
public async Task<Result<ResponseModel>> DocumentProcess(RequestModel model)
{
var authToken = GetToken();
using var httpClient = new HttpClient();
using var request = new HttpRequestMessage(new HttpMethod("POST"), $"https://apiurl.com/api/TrackDocumentProcessStatus/{model.DocumentId}");
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {authToken.Result.Value.Token}");
var response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(content))
{
var userRequestResponse = JsonSerializer.Deserialize<ResponseModel>(content);
return Result.Success(userRequestResponse);
}
return Result.Failure<ResponseModel>("Error...");
}
Can you please suggest any best practices to solve this problem?
Thanks.
Here in the below sample code, I want to call
_client.DocumentProcess(model) API with some pooling mechanism after every 10 seconds for five times until I get the Result as "Success" or
my pooling call limit gets exceeded.
Well based on your scenario, we can consider two standard implementaion. First one we can implement using PeriodicTimer which provides a periodic timer that would call as per the given time interval.
Another one, would be using BackgroundService worker process which would continue calling API unless the certain condition are meet.
Using PeriodicTimer:
public async Task<IActionResult> DocumentProcess()
{
var status = await TrackDocumentProcessStatus();
if (status == "Success")
{
return Ok(status);
}
else
{
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync())
{
status = await TrackDocumentProcessStatus();
if (status == "Success")
{
break;
}
continue;
}
}
return Ok();
}
Explanation:
As you can see, when DocumentProcess method would invocke, it will call the TrackDocumentProcessStatus method thus, the API and if API return pending it update the status and will call again within next 10 seconds and process would continue until it gets success status.
Method Would Invoked Every 10 Seconds:
public async Task<string> TrackDocumentProcessStatus()
{
var rquestObject = new StatusRequestModel();
rquestObject.RequestId = 3;
var data_ = JsonConvert.SerializeObject(rquestObject);
HttpClient _httpClient = new HttpClient();
var buffer_ = System.Text.Encoding.UTF8.GetBytes(data_);
var byteContent_ = new ByteArrayContent(buffer_);
byteContent_.Headers.ContentType = new MediaTypeHeaderValue("application/json");
string _urls = "http://localhost:5094/api/Rest/CheckDocumentStatus";
var responses_ = await _httpClient.PostAsync(_urls, byteContent_);
if (responses_.StatusCode != HttpStatusCode.OK)
{
return "Pending";
}
string response = await responses_.Content.ReadAsStringAsync();
return response;
}
Output:
Note:
If you would like to know more details on it you could check our official document here
BackgroundService worker process:
It would be a individual background worker service which would continue running and check your status behind. You can achieve your requirement as following:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
var status = await TrackDocumentProcessStatus();
status = await TrackDocumentProcessStatus();
if (status == "Success")
{
break;
}
Console.WriteLine(status);
await Task.Delay(1000, stoppingToken);
}
}
public async Task<string> TrackDocumentProcessStatus()
{
var rquestObject = new StatusRequestModel();
rquestObject.RequestId = 1;
var data_ = JsonConvert.SerializeObject(rquestObject);
HttpClient _httpClient = new HttpClient();
var buffer_ = System.Text.Encoding.UTF8.GetBytes(data_);
var byteContent_ = new ByteArrayContent(buffer_);
byteContent_.Headers.ContentType = new MediaTypeHeaderValue("application/json");
string _urls = "http://localhost:5094/api/Rest/CheckDocumentStatus";
var responses_ = await _httpClient.PostAsync(_urls, byteContent_);
if (responses_.StatusCode != HttpStatusCode.OK)
{
return "Pending";
}
string response = await responses_.Content.ReadAsStringAsync();
return response;
}
Note:
Both implementaion would call your API after a certain interval for checking status from another API. The difference of these implementation is background worker service would continue in background, no other action wwould required just like widows service, If you would like to know more details on background worker service you could check our official document here
Related
I created a .Net Framework 4.7.2 console app that concurrently makes many requests in an API hosted in AWS. My problem is that the requests are taking too long.
The API's response time is usually 100ms-400ms according to the taget group monitoring in AWS console but in my application the time elapsed of each request starts at 1 second and keeps increasing until 11 seconds.
I'm already aware that HttpClient doesn't close connections properly so we shouldn't use using and instead always use one instance for each application.
I already found a similar question but the answer didn't solve it.
When I set MaxDegreeOfParallelism to 1, the response time in the application is similar to the app. This seem to be problem that occurs in HttpClient in a multi thread.
This is how i'm doing the requests :
public static class RequestMaker
{
private static readonly string _urlHttp = "http://apidomain.com/api/apiname";
private static readonly HttpClient _httpClient = new HttpClient();
public static async Task<string> PostAsync(string postData)
{
bool IsSuccessStatusCode = false;
int maxRetries = 5;
int count = 0;
do
{
try
{
Stopwatch watcher = Stopwatch.StartNew();
using (HttpContent content = new StringContent(postData, Encoding.UTF8, "application/json"))
using (HttpResponseMessage result = await _httpClient.PostAsync(_urlHttp, content).ConfigureAwait(false))
{
watcher.Stop();
Console.WriteLine("Elapsed = " + watcher.ElapsedMilliseconds.ToString("N0"));
IsSuccessStatusCode = result.IsSuccessStatusCode;
if (IsSuccessStatusCode)
return await result.Content.ReadAsStringAsync().ConfigureAwait(false);
count++;
if (count > maxRetries)
return "";
Console.WriteLine($"Retrying request because of request status code {result.StatusCode}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
count++;
if (count > maxRetries)
return "";
}
} while (!IsSuccessStatusCode);
return "";
}
}
This is my function calling the request concurrently :
static void RunBatchMany(List<string> list)
{
var getCustomerBlock = new TransformBlock<string, long>(
async lstRec =>
{
ApiInputObject apiInput = new ApiInputObject();
// PrepareInputObject
string postData = JsonConvert.SerializeObject(apiInput);
Stopwatch watcher = Stopwatch.StartNew();
string json = await RequestMaker.PostAsync(postData);
ApiResponseObject res = JsonConvert.DeserializeObject<ApiResponseObject>(json);
watcher.Stop();
return watcher.ElapsedMilliseconds;
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 8
});
foreach (var id in list)
getCustomerBlock.Post(id);
getCustomerBlock.Complete();
getCustomerBlock.Completion.Wait();
}
Try to isolate the await logic and make only one call to the URL without using a loop :
HttpResponseMessage result = await _httpClient.PostAsync(_urlHttp, content).ConfigureAwait(false)
Make a Unit Test out of that call. Call it once. Is the HttpResponse received in the suggested time? If Yes: Then slowly add the loop and the other logic things around. You use a lot of async programming, but why? For what exactly dou you need it? Why do you use a "static" Request Maker?
I'm creating a Task in C# but I'm not sure what I do is correct. I'm using Restsharp and in Restsharp there are two methods: Execute and ExecuteAsync. I want to do an Async call but I also need to return data to the client without blocking the execution.
Therefore I created a task which will use Execute instead of ExecuteAsync. The reason why is because I have to wait until I get a response back and then return it in the right data structure. So I thought there is no use in using ExecuteAsync if I have to await it in a Task...
My code looks as follows:
public Task<Response> ExecuteAsync()
{
return new Task<Response>(() =>
{
var client = new RestClient(URL);
if (_useBasicAuth)
{
client.Authenticator = new HttpBasicAuthenticator(_username, _password);
}
var request = RequestBuilder(_method);
var response = client.Execute(request);
return new Response()
{
HttpStatusCode = response.StatusCode,
HttpStatusDescription = response.StatusDescription,
Content = response.Content,
Cookies = ExtractCookies(response.Cookies),
Headers = ExtractHeaders(response.Headers)
};
});
}
Is this correct? The client should be able to call ExecuteAsync without blocking the execution.
I strongly suspect you should really just use ExecuteAsync and write an async method:
public async Task<Response> ExecuteAsync()
{
var client = new RestClient(URL);
if (_useBasicAuth)
{
client.Authenticator = new HttpBasicAuthenticator(_username, _password);
}
var request = RequestBuilder(_method);
var response = await client.ExecuteAsync(request).ConfigureAwait(false);
return new Response
{
HttpStatusCode = response.StatusCode,
HttpStatusDescription = response.StatusDescription,
Content = response.Content,
Cookies = ExtractCookies(response.Cookies),
Headers = ExtractHeaders(response.Headers)
};
}
Here is case:
I have wpf application on about 80 clients and they communicate single .net framework api for data process. I have stopwatch tracker some places for tracking durations on Wpf and api apps. Code samples :
My api attribute :
public class DurationControlLoggerAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var controller = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var action = actionContext.ActionDescriptor.ActionName;
actionContext.ActionArguments.Add("_stopwatch_", Stopwatch.StartNew());
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (ConfigurationManager.AppSettings["ApiLogger"].ToLower().Trim() != "true")
return;
var controller = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var action = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;
var stopWatch = (Stopwatch)actionExecutedContext.ActionContext.ActionArguments["_stopwatch_"];
stopWatch.Stop();
var scope = actionExecutedContext.ActionContext.Request.GetDependencyScope();
var commonService = (ICommonService)scope.GetService(typeof(ICommonService));
commonService.InsertLog(new Model.Common.LogModel
{
InsertDate = DateTime.Now.ToString(),
LogLevel = "API",
MachineName = "API",
Message = $"Controller : {controller} - Action : {action} - TotalSeconds : {stopWatch.Elapsed.TotalSeconds}",
StackTrace = string.Empty
});
}
}
Action sample :
[HttpPost]
[DurationControlLogger]
public bool InsertProduct(ProductModel model)
{
return _mainService.TracingService.InsertProduct(model);
}
This action process duration is about 0,03 seconds. On the other hand, wpf api call duration is about 10 seconds. Wpf code blocks are down below:
var Stopwatch = Stopwatch.StartNew();
var isSuccess = DataHelper.InsertProduct(Product);
Stopwatch.Stop();
if (Stopwatch.Elapsed.TotalSeconds > 2)
DataHelper.InsertTraceLog($"ProducrtBusiness - InsertProduct TotalSecond : {Stopwatch.Elapsed.TotalSeconds}");
DataHelper.InsertProduct method does basic http post request. Code is here :
public static class HttpClientHelper
{
public static T Post<T>(object model, string url)
{
var resultStatus = false;
T resultData = default(T);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json");
HttpResponseMessage response = client.PostAsync(url, content).Result;
if (response.IsSuccessStatusCode)
{
string data = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<T>(data);
resultStatus = response.StatusCode == System.Net.HttpStatusCode.OK;
resultData = result;
}
}
return resultStatus ? resultData : default(T);
}
.....
Does anyone has any idea about that situation?
EDIT
I add one last log code. Here is code:
`
var stopwatch = Stopwatch.StartNew();
HttpResponseMessage response = client.PostAsync(url, content).Result;
stopwatch.Stop();
if (stopwatch.Elapsed.TotalSeconds > 2)
{
AppendToFile($"{DateTime.Now.ToString()} - {model.ToString()} - {stopwatch.Elapsed.TotalSeconds} - Content: {jsonData}");
}
`
This log duration still sometimes is 8-10 sec.
Well, it is likely (from what Ive seen) that you are running this code on the main message pump, (UI thread). During this method call you are performing an asynchronous call (PostAsync) and calling the 'Result' - making it run synchronously. However, while your UI thread was waiting, some other messages (UI updates?) where queued on the message pump, and were processed before your call to end the stopwatch (higher DispatcherPriority?) - delaying the timer end.
This is pure speculation, but I would recommend that you use some async/await in your asynchronous code, and try and perform these data access tasks on a separate thread
I changed my HttpClientHelper class post method to this :
public static async Task<T> PostAsync<T>(object model, string url)
{
var resultStatus = false;
T resultData = default(T);
using (var client = new HttpClient(
new HttpClientHandler()
{
UseProxy = false
}
))
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var jsonData = JsonConvert.SerializeObject(model);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
var stopwatch = Stopwatch.StartNew();
HttpResponseMessage response = await client.PostAsync(url, content);
stopwatch.Stop();
if (stopwatch.Elapsed.TotalSeconds > 2)
{
AppendToFile($"{DateTime.Now.ToString()} - {model.ToString()} - {stopwatch.Elapsed.TotalSeconds} - Content: {jsonData}");
}
if (response.IsSuccessStatusCode)
{
string data = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<T>(data);
resultStatus = response.StatusCode == System.Net.HttpStatusCode.OK;
resultData = result;
}
}
return resultStatus ? resultData : default(T);
}
client.PostAsync() method has to run async for my issue. I think that related some thread managment issues. But this code works for me now.
i have the following problem, i try to wait for for an Async Web Response.
But it never finished.
public string getTermine(string trmId)
{
System.Threading.Tasks.Task<string> lisi = LoadTermine((HttpWebRequest)WebRequest.Create("http://" + curent.usrCH + apiKey + curent.phrase + apiTrmIDIS + trmId));//Request get String result like http://ajax.googleapis.com/ajax/services/search/web?v=1.0&start="+i+"&q=
lisi.Wait();
return lisi.Result;
}
private async System.Threading.Tasks.Taskstring>LoadTermine(HttpWebRequest myRequest)
{
//List<Termine> terminListe = new List<Termine>();
List<Appointment> Resu = null;
using (WebResponse response = await myRequest.GetResponseAsync())
{
using (System.IO.StreamReader reader = new System.IO.StreamReader(response.GetResponseStream()))
{
Resu = reader.ReadToEnd();
}
}
return Resu;
}
P.S. I cant use and synchronous request because this methods are an part of the Base code which is used by iOS, WinPhone and Android and i dont know why i cant get an synchronous WebResponse.
You are creating a deadlock by calling .Result on the task.
You could do something like this where the remoteUrl variabled is the url of your web service
private async System.Threading.Tasks.Task<string> LoadTermineAsync(HttpWebRequest myRequest)
{
using (var client = new HttpClient()) {
using (var request = new HttpRequestMessage(HttpMethod.Get, myRemoteUrl)) {
var response = await client.SendAsync(request).ConfigureAwait(false);
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return result;
}
}
}
For more info on Async/Await
And this evolve video is a little bit more advanced.
I'm working with Windows Phone Runtime API.
I declare a timer, which every 2 seconds does async http connection in Listen method.
Timer t = new Timer(Listen, null, 0, 2000);
Listen method:
private async void Listen(object state)
{
string url = "http://www.mywebpage.com?data=my_data";
string responseBodyAsText = null;
var response = new HttpResponseMessage();
var httpClient = new HttpClient();
try
{
response = await httpClient.GetAsync(new Uri(url));
responseBodyAsText = await response.Content.ReadAsStringAsync();
}
catch
{
//...
}
Debug.WriteLine(responseBodyAsText);
httpClient.Dispose();
}
My problem is that responseBodyAsText contains always the same data (given the same uri) and not as I would expect different data according to my external actions (modifying web page or different results with the same uri).
Does HttpClient remembers content during liftime of application? How can I solve this problem?
HttpClient does have caching on by default. You can turn it off by passing it an HttpBaseProtocolFilter:
var filter = new HttpBaseProtocolFilter
{
CacheControl.ReadBehavior = HttpCacheReadBehavior.MostRecent,
CacheControl.WriteBehavior = HttpCacheWriteBehavior.NoCache;
}
Side note: You could also, instead of a Timer, use Task.Delay to achieve the timer behavior (it internally uses one):
private async Task ListenAsync()
{
while (someCondition)
{
string url = "http://www.mywebpage.com?data=my_data";
string responseBodyAsText = null;
var response = new HttpResponseMessage();
using (var httpClient = new HttpClient())
{
try
{
response = await httpClient.GetAsync(new Uri(url));
responseBodyAsText = await response.Content.ReadAsStringAsync();
}
catch
{
//...
}
Debug.WriteLine(responseBodyAsText);
await Task.Delay(2000);
}
}