C# How to polly the GetStreamAsync api call - c#

I have the following code to call the bank api, and convert the response into the object.
public static async Task<ExchangeModel> LoadExchange(string currency, string date)
{
string url = "here is the bank's api url";
var streamTask = await _clientPolicy.LinearHttpRetry.ExecuteAsync(
() => _client.GetStreamAsync(url));
var deserialized = await JsonSerializer.DeserializeAsync<Root>(await streamTask);
var rate = deserialized.ExchangeModel.FirstOrDefault(r => r.Currency == currency);
return rate;
}
I wanted to add polly in order to retry api call if it failed. To do so, I created a policy client, but I don't know how to check if GetStreamAsync is succeed as it is a System.IO.Stream and it has no succeed status or something.
public AsyncRetryPolicy<System.IO.Stream> LinearHttpRetry { get; }
public ClientPolicy()
{
LinearHttpRetry = Policy.HandleResult<System.IO.Stream>(
res => !res.IsSuccessStatusCode).
WaitAndRetryAsync(5, retryAttempts => TimeSpan.FromSeconds(3));
}
In this example I kept it !res.IsSuccessStatusCode but there is no such parameter in my case.

Rather than calling directly the GetStreamAsync you can do the following "trick":
var response = await _client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
//TODO: Check response status code then
using var stream = await response.Content.ReadAsStreamAsync();
First you need to alter your policy definition like this:
public IAsyncPolicy<HttpResponseMessage> LinearHttpRetry { get; }
public ClientPolicy()
{
LinearHttpRetry = Policy
.HandleResult<HttpResponseMessage>(res => !res.IsSuccessStatusCode)
.WaitAndRetryAsync(5, _ => TimeSpan.FromSeconds(3));
}
Then you need to adjust the usage as well
public static async Task<ExchangeModel> LoadExchange(string currency, string date)
{
string url = "here is the bank's api url";
var response = await _clientPolicy.LinearHttpRetry.ExecuteAsync(
async () => await _client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead));
using var stream = await response.Content.ReadAsStreamAsync();
var deserialized = await JsonSerializer.DeserializeAsync<Root>(stream);
var rate = deserialized.ExchangeModel.FirstOrDefault(r => r.Currency == currency);
return rate;
}

Related

I don't understand where I missed async\await

My code returns Task<List<...>>.
There is a type conversion error from Task<List<...>> to List<...>.
Tell me, please, where I did not finish await ?
public async Task<List<DepartamentsResponse>> Handle(GetDepartmentsRequest token, CancellationToken cancellationToken)
{
var departments = await _departmentApiClient.GetReception(token.accessToken, OdpgDepartmentType.Reception);
var result = departments.ConvertAll(async d => new DepartamentsResponse
{
FederalDistrict = GetFederalDistrictCode(d.FederalRegion.DistrictCode),
SubjectName = d.Name,
Supervisor = GetDirector(d.Users.Where(u => u.InOdpgRole(OdpgUserRole.Director)).FirstOrDefault()),
ContactsSupervisor = GetContacts(d.Users.Where(u => u.InOdpgRole(OdpgUserRole.Director)).FirstOrDefault()),
Schedule = "C 9:00 18:00",
ReceptionContacts = await GetAddressAsync(d.Addresses.FirstOrDefault(d => d.AddressType == DepartmentAddressType.Reception), token)
});
return result;
}
private async Task<string> GetAddressAsync(DepartmentAddressDto? address, GetDepartmentsRequest token)
{
if (address != null)
{
var fullAddress = await _fiasApiClient.GetFullAddress(token.accessToken,
new ESOG.Fias.Api.Model.GetFullAddressRequest
{ BaseAddressId = address.FiasId, Building = address.Building, Room = address.Room });
//var res = JsonConvert.DeserializeObject<DepartmentAddress>(fullAddress);
return fullAddress;
}
return "";
}
GetFederalDistrictCode, GetDirector, GetContacts - these methods are not asynchronous
It should just return a List<>, not Task<List<>>
Your call to departments.ConvertAll is returning a List<Task<DepartamentsResponse>>, but you want a Task<List<DepartamentsResponse>>.
Look at the Task.WhenAll method for how you would convert a collection of Tasks to a single Task that returns a collection. Then await that single Task and build your final list.

How to store only " in C# string

I want to get JSON from string and I need to extract " instead of \" in my code.
Here is my code that I want to use it in:
internal static string ReturnRedditJsonPage(string subredditname)
{
return
$"https://reddit.com/r/{subredditname}.json";
}
internal static Reddit ParseReddit(string subredditname)
{
WebResponse response = HttpWebRequest.CreateHttp(ReturnRedditJsonPage(subredditname)).GetResponse();
string responseContent = new StreamReader(response.GetResponseStream()).ReadToEnd().Replace("\\",#"\").Replace("\"",((char)0x0022).ToString()).Trim();
return JsonConvert.DeserializeObject<Reddit>(responseContent);
}
internal static Uri[] GetMemesLinks(string subredditname)
{
Reddit jsonData = ParseReddit(subredditname);
List<Uri> result = new List<Uri>();
foreach(Child child in jsonData.Data.Children)
{
result.Add(child.Data.Url);
}
return result.ToArray();
}
It gives me back JSON that I cannot parse because of \" in string instead of ". How can I fix it?
You can use JSON.NET coupled with a little LINQ magic to extract all URI(s) out of the sub-reddit API.
Here's a demo, tweak to your requirements:
internal static string ReturnRedditJsonURI(string SubRedditName)
{
return $"https://reddit.com/r/{SubRedditName}.json";
}
// Does a HTTP GET request to the external Reddit API to get contents and de-serialize it
internal static async Task<JObject> ParseReddit(string SubRedditName)
{
string exampleURI = ReturnRedditJsonURI(SubRedditName);
JObject response = new JObject();
using (HttpClient client = new HttpClient())
{
// Make the HTTP request now
HttpResponseMessage msg = await client.GetAsync(exampleURI);
// If HTTP 200 then go ahead and de-serialize
if (msg.IsSuccessStatusCode)
{
string responseBody = await msg.Content.ReadAsStringAsync();
response = JsonConvert.DeserializeObject<JObject>(responseBody);
}
}
return response;
}
// Driver method to extract the URI(s) out of the reddit response
internal static async Task<List<Uri>> GetRedditURI(string SubRedditName)
{
string subRedditName = "Metallica";
JObject redditData = await ParseReddit(SubRedditName);
List<Uri> redditURIList = new List<Uri>();
try
{
// TODO: instead of JObject use concrete POCO, but for now this seems to be it.
redditURIList = redditData["data"]?["children"]?
.Select(x => x["data"])
.SelectMany(x => x)
.Cast<JProperty>()
.Where(x => x.Name == "url")
.Select(x => x.Value.ToString())
.Select(x => new Uri(x, UriKind.Absolute)).ToList() ?? new List<Uri>();
return redditURIList;
}
catch (Exception ex)
{
return redditURIList;
}
}

Making parallel api calls and handling each response asynchronously in c#

I have a scenario where I need to make multiple api calls (same api with different parameters) parallely in c# (Xamarin iOS and Xamarin Android). And I don't want to wait for all tasks to complete, instead whenever a response comes I should process it and update the UI accordingly.
Method that needs to be called multiple times
public async Task<Response> GetProductsAsync(int categoryId, int pageNo = -1, int pageSize = -1)
{
try
{
string url = "";
if (pageNo == -1 || pageSize == -1)
url = $"catalog/v1/categories/{categoryId}/products";
else
url = $"catalog/v1/categories/{categoryId}/products?page-number={pageNo}&page-size={pageSize}";
var response = await client.GetAsync(url);
string responseString = await response.Content.ReadAsStringAsync();
GetParsedData(response.IsSuccessStatusCode, responseString);
}
catch (Exception e)
{
apiResponse.status = "internalError";
apiResponse.data = e.Message;
}
return apiResponse;
}
From calling function you may write the code as below
public void CallingFunctionToGetProductsAsync() {
Task.Run(async () =>
{
var response = await GetProductsAsync(1);
ProcessResponse(response);
});
Task.Run(async () =>
{
var response = await GetProductsAsync(2);
ProcessResponse(response);
});
}
This is how you can wait for multiple tasks asynchron and update the UI whenever any of them completes.
async Task GetSomeProductsAsync( IEnumerable<int> categoryIds )
{
List<Task<Response>> tasks = categoryIds
.Select( catId => GetProductsAsync( catId ) )
.ToList();
while ( tasks.Any() )
{
var completed = await Task.WhenAny( tasks );
tasks.Remove( completed );
var response = completed.Result;
// update the ui from this response
}
}
As a side note:
You should add ConfigureAwait(false) to your awaiting code in GetProducsAsync to avoid uneccessary sync with the caller thread (wich would be the UI here)
public async Task<Response> GetProductsAsync(int categoryId, int pageNo = -1, int pageSize = -1)
{
try
{
string url = "";
if (pageNo == -1 || pageSize == -1)
url = $"catalog/v1/categories/{categoryId}/products";
else
url = $"catalog/v1/categories/{categoryId}/products?page-number={pageNo}&page-size={pageSize}";
var response = await client.GetAsync(url).ConfigureAwait(false);
string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
GetParsedData(response.IsSuccessStatusCode, responseString);
}
catch (Exception e)
{
apiResponse.status = "internalError";
apiResponse.data = e.Message;
}
return apiResponse;
}
You can read more about it in Stephen Cleary's blog article: Don't block on Async Code

How to call async method from sync in C#?

I am adding a new web API call to existing functionality. I want to make this API call async but looks like it is causing deadlock. I have to make a lot more changes if I want to make entire code channel async which is not possible.
Questions I have are:
Is it possible to call async method from regular method?
What am I missing here? OR What is the correct approach here?
Code:
// Exisitng Method
public Tuple<RestaurantDeliveryProvider, DeliveryHubResult, Task<DeliveryManagerQuoteResponse>> CreateDeliveryRequest(OrderContextDTO orderContextDto)
{
var provider = RestaurantBl.GetDeliveryProviderInformationByRestaurantId(orderContextDto.RestaurantId ?? 0);
var deliveryHubResult = RestaurantBl.GetDeliveryHubResult(orderContextDto.OrderId ?? 0);;
// New Call which always comes back with "Not Yet Computed" result
Task<DeliveryManagerQuoteResponse> deliveryManagerQuoteResponse = _deliveryManager.CreateQuoteRequestAsync(orderContextDto, orderInfo);
return Tuple.Create(provider, deliveryHubResult, deliveryManagerQuoteResponse);
}
Async Methods:
public async Task<DeliveryManagerQuoteResponse> CreateQuoteRequestAsync(OrderContextDTO orderContextDto, OrderInfoDTO orderInfo)
{
DeliveryManagerQuoteResponse deliveryManagerQuoteResponse = null;
try
{
var restaurantInfo = RestaurantApi.GetRestaurant(orderInfo.RestaurantId);
var quoteRequest = new DeliveryManagerQuoteRequest
{
DeliveryProvider = null,
Country = orderContextDto.DeliveryEstimateRequestDto.RequestedDeliveryAddress.Country,
Concept = "BK",
StoreName = "BK-TEST-US-4",
OrderId = orderInfo.OrderId.ToString(),
AllowCash = false,
PaymentType = OrderPaymentType.Prepaid_Credit,
Note = orderInfo.DeliveryInstructions,
};
deliveryManagerQuoteResponse = await Quote(quoteRequest);
}
catch (Exception ex)
{
Log.ErrorFormat("Get Delivery Manager Quote failed: Error: {0}, OrderId: {1}", ex.Message, orderContextDto.OrderId);
}
return deliveryManagerQuoteResponse;
}
public async Task<DeliveryManagerQuoteResponse> Quote(DeliveryManagerQuoteRequest quoteRequest)
{
DeliveryManagerQuoteResponse deliveryManagerQuoteResponse;
var client = HttpClientFactory.GetClient();
var content = HttpClientFactory.JsonContentFactory.CreateJsonContent(quoteRequest);
var response = await client.PostAsync("https://myUrl", content);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
deliveryManagerQuoteResponse = JsonConvert.DeserializeObject<DeliveryManagerQuoteResponse>(data);
}
else
{
throw new Exception((int)response.StatusCode + "-" + response.StatusCode);
}
return deliveryManagerQuoteResponse;
}
I tried following as well but same result:
public async Task<DeliveryManagerQuoteResponse> Quote(DeliveryManagerQuoteRequest quoteRequest)
{
DeliveryManagerQuoteResponse deliveryManagerQuoteResponse;
using (var client = new HttpClient())
{
var content = HttpClientFactory.JsonContentFactory.CreateJsonContent(quoteRequest);
var response = await client.PostAsync("https://myUrl", content);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
deliveryManagerQuoteResponse = JsonConvert.DeserializeObject<DeliveryManagerQuoteResponse>(data);
}
else
{
throw new Exception((int)response.StatusCode + "-" + response.StatusCode);
}
}
return deliveryManagerQuoteResponse;
}
Output (sorry for the blurry output, if you click on it, you will see clear result):
don't
don't
Basically, there is no good or workable way to call an async method from a sync method and wait for the answer. There's "sync over async", but that's an anti-pattern and should be aggressively avoided.
So either:
rewrite the caller to be async
implement a synchronous version of the API

How to test WP app with web requests?

I am developing windows phone application, where data comes from web. I would like to test some method:
public void PrintSomeObject()
{
var apiInstance = new Api();
apiInstance.GetSomeObject("bar",
(res) =>
{
Debug.WriteLine(res.data);
});
}
public class Api
{
public GetSomeObject(string path, Action<SomeObject> callback,)
{
HTTPRequest("http://foo.com/" + path,
(resultStr) =>
{
SomeObject t = ParseSomeObject(resultStr);
callback(t);
});
}
void async void HTTPRequest(string baseUri, Action<string> resultCallback)
{
var result = await httpClient.PostAsync(new Uri(baseUri, UriKind.Absolute), content);
var resultStr = await result.Content.ReadAsStringAsync();
// var resultStr = "{data: 'some fake data', number: 42}" // I want insert fake data here
resultCallback(resultStr);
}
}
I know want PrintSomeObject() should print in some cases, and I'd like to test it. But I need to control data which I get in request response. Is it possible, or I need test each method separatly?

Categories

Resources