I've been trying to add a cancellation token to my web api async controller in order to detect when the httpClient times out (so I can stop any pending await calls), spending hours on that without success. Here is my desktop app (.Net 4.0) calling the web api:
public void GetServObjAsync()
{
try
{
var client = new HttpClient()
{
Timeout = TimeSpan.FromMilliseconds(10000)
};
var myUri = new Uri("http://mysWebApi.net/myController");
client.GetAsync(myUri ).ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Result.Content.ReadAsAsync<servObj>().ContinueWith(t2 =>
{
if (!t2.IsFaulted)
{
OnSerVObjReady(t2.Result);
}
}
}
}
}
catch
{
// Handle and notify
}
}
Here is my web api controller:
public class ServController : ApiController
{
public async Task<servObj> Get(CancellationToken Token)
{
try
{
await Task.Delay(15000);
var resp = await singleHttpCallAsync(Token);
if (Token.IsCancellationRequested)
{
return null;
}
var respObj = await multipleHttpCallsAsync(Token);
return respObj;
}
catch
{
return null;
}
}
private Async Task<servObj> multipleHttpCallsAsync(CancellationToken token)
{
var respObjs = new List<detailObj>();
var httpCalls = new Task<detailObj>[2];
httpCalls[0] = singleHttpCall2Async(0, token);
httpCalls[1] = singleHttpCall2Async(1, token);
await Task.WhenAll(httpCalls);
respObjs.Add(await httpCalls[0]);
respObjs.Add(await httpCalls[1]);
return new servObj(respObjs);
}
}
I have tried the approach stated in Client disconnects in ASP.NET Web API, an many others, played with different timeouts, but at the end, the request is complete and servObj(respObjs) is returned. Any help on why the httpClient.timeout is not reflected in my controller would be appreciated.
Related
I'm using the packages "FastEndpoints" & "FastEndpoints.Security" for creating the RESTApi.
This is my Endpoint:
public class LoginEndpoint : Endpoint<LoginRequest, LoginResponse>
{
IUserService _userSvc;
public LoginEndpoint(IUserService users)
{
_userSvc = users;
}
public override void Configure()
{
Verbs(Http.GET);
Routes("/api/login");
AllowAnonymous();
}
public override async Task HandleAsync(LoginRequest req, CancellationToken ct)
{
if (_userSvc.LoginValidByName(req.Name, req.Password))
{
var user = _userSvc.GetFromName(req.Name);
var expiresAt = DateTime.UtcNow.AddDays(1);
var token = JWTBearer.CreateToken(
GlobalSettings.TOKEN_SIGNING_KEY,
expiresAt,
user.Permissions.Select(p => p.Name));
await SendAsync(
new LoginResponse()
{
Token = token,
ExpiresAt = expiresAt
});
}
else
{
await SendUnauthorizedAsync();
}
}
}
Using Postman, the endpoints works as expected:
But when using RestSharp (and mind you, I'm very new to the whole RESTApi world), I get an error 'Request ended prematurely'.
This is my simple call:
public class ApiClient
{
private RestClient _restClient;
public ApiClient(string baseUrl)
{
_restClient = new RestClient(baseUrl);
//ServicePointManager.ServerCertificateValidationCallback += (s, c, ch, p) => true;
}
public async Task<bool> UserValid(string username, string password)
{
var request = new RestRequest("/api/login", Method.Get);
request.AddParameter("name", username);
request.AddParameter("password", password);
var result = await _restClient.GetAsync(request);
if (result.StatusCode == HttpStatusCode.OK)
return true;
else
return false;
}
}
Can someone fill me in?
Since it works with Postman, I suspect my call being bad.
Is:
_userSvc.LoginValidByName
Or any other function missing an await by chance?
My Xamarin app deadlocks when trying to make API call (asp.net core web API). The mobile app is using Android emulator. I created both API and client app following Microsoft's own tutorial. Requests run normally when making call with Postman or directly in the browser of my dev machine.
Constants class:
public static class Constants
{
public static string BaseAddress =
DeviceInfo.Platform == DevicePlatform.Android
? "https://10.0.2.2:44348"
:"https://localhost:44348";
public static string AppointmentsUrl = $"{BaseAddress}/api/appointments/";
}
Handler:
public class AppointmentHandler
{
HttpClient client;
JsonSerializerOptions serializerOptions;
private List<Appointment> Appointments { get; set; }
public AppointmentHandler()
{
client = new HttpClient();
serializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
}
public async Task<List<Appointment>> GetAppointments()
{
Appointments = new List<Appointment>();
try
{
Uri uri = new Uri(string.Format(Constants.AppointmentsUrl, string.Empty));
// the program deadlocks here
HttpResponseMessage response = await this.client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<List<Appointment>>(content, serializerOptions);
}
}
catch (Exception e)
{
var m = e.Message;
}
return null;
}
}
Have you tried using
.ConfigureAwait(false)
This is a common issue with async code and user interfaces. More here:
https://forums.xamarin.com/discussion/174173/should-i-use-configureawait-bool-or-not
I tried make it as you suggested (answer below this). Unfortunately still I have this problem.
This is my new TokenService.cs:
class TokenService
{
TokenKeeper tokenkeeper;
public TokenService()
{
tokenkeeper = new TokenKeeper();
}
public async void AwaitGetToken()
{
tokenkeeper = new TokenKeeper();
tokenkeeper = await GetToken();
}
private async Task<TokenKeeper> GetToken()
{
string stringuri = string.Format("{0}/token", RestAccess.HostUrl);
var dict = new Dictionary<string, string>();
dict.Add("grant_type", "password");
dict.Add("username", RestAccess.User);
dict.Add("password", RestAccess.Pass);
dict.Add("client_id", RestAccess.Client_ID);
dict.Add("client_secret", RestAccess.Client_secret);
tokenkeeper=new TokenKeeper();
var httpclient = new HttpClient();
try
{
var req = new HttpRequestMessage(HttpMethod.Post, stringuri) { Content = new FormUrlEncodedContent(dict) };
var res = await httpclient.SendAsync(req);
if (res.IsSuccessStatusCode)
{
var content = await res.Content.ReadAsStringAsync();
tokenkeeper = JsonConvert.DeserializeObject<TokenKeeper>(content);
return tokenkeeper;
}
return tokenkeeper;
}
catch
{
return tokenkeeper;
}
finally
{
httpclient.CancelPendingRequests();
httpclient.Dispose();
}
}
}
My tokenkeeper is simple class:
public class TokenKeeper
{
public string access_token { get; set; }
public string refresh_token { get; set; }
public TokenKeeper()
{
access_token = "";
refresh_token = "";
}
}
In my calling code I have as below:
...
tokenservice.AwaitGetToken();
GetXFMapUserFromAzureAndSetVariable(RestAccess.User);
_navigationService.NavigateAsync("ZleceniaListContentPage", par);
...
tokenservice.AwaitGetToken() and GEtXFMapUserFromAzureAndSetVariable(RestAccess.User) they are similar. Both await but tokenservice.AwaitGetToken() is POST and GEtXFMapUserFromAzureAndSetVariable is GET.
GEtXFMapUserFromAzureAndSetVariable is working correctly but if I call tokenservice.AwaitGetToken() the aplication get the token but before it's continue. At the end the instruction GEtXFMapUserFromAzureAndSetVariable it is being done tokenservice.AwaitGetToken() responds.
How can I wait with call GEtXFMapUserFromAzureAndSetVariable until I receive a reply from tokenservice.AwaitGetToken() ???
Delete your AwaitGetToken method, it is useless.
Now make you GetToken method public and call it with await:
await tokenService.GetToken();
await GetXFMapUserFromAzureAndSetVariable(RestAccess.User);
await _navigationService.NavigateAsync("ZleceniaListContentPage", par);
Basically you always need to await methods returning Task, or it will run in a parallel fashion, and you will lose consistency in your code.
You seem very confused by Task and async/await, I will strongly advise you to read tutorials and docs on it:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
I am very new to ASP.NET CORE MVC and I was wondering if anyone could help me about my problem.
I was working in a project that will get all projects within a specific azure devops organization.
Here is my controller code:
public async Task<ActionResult> Organization(string selectedOrg, string oauth)
{
var client = new HttpClient();
IndexViewModel model = new IndexViewModel();
model.Organizations = OrganizationData.Data;
if (selectedOrg == null)
{
selectedOrg = model.Organizations.FirstOrDefault().OrgName;
}
else
{
model.SelectedOrg = selectedOrg;
}
var token = _cache.Get<TokenModel>("Token" + HttpContext.Session.GetString("TokenGuid"));
oauth = token.AccessToken;
var url = "https://dev.azure.com/" + selectedOrg + "/_apis/projects?api-version=4.1";
try
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", oauth);
var response = await client.GetAsync(url);
var responseBody = response.Content.ReadAsStringAsync().Result;
model.Projects = JsonConvert.DeserializeObject<ProjectsModel>(responseBody);
client.Dispose();
return View("Index", model);
}
catch(Exception e)
{
client.Dispose();
return Json(Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(e.ToString()));
}
}
Can anyone help how to do unit testing with this one? Or do I have to refactor this one?
You have way to many dependencies.
Why does the signature of the method pass an oauth value that is never used?
First off, calling any external dependency via http inside a controller should be frowned upon. This whole thing should be abstracted into it's own call. Since it appears to be getting data, this should actually be at your data tier. Covering a whole n-tier approach with separate projects is most likely out of scope, so lets just cover the bare minimum for unit testing in my opinion.
First you need to abstract your HttpClient. You can't really do unit test methods if they make any calls outside themselves (for the most part) because then it's not a unit test, it's an integration test.
// I don't have a full grasp of your complete eco-system so based on the
// minimal information provided, this would at least get you close
public interface IAzureAPI
{
public Task<string> GetOrgAsync(string org, string oauth);
}
public class AzureAPI : IDisposable
{
public async Task<string> GetOrgAsync(string org, string oauth)
{
// use *using* not try/catch/finally/dispose
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", oauth);
var url = "https://dev.azure.com/" + org+ "/_apis/projects?api-version=4.1";
var response = await client.GetAsync(url);
// never use `.Result` unless you absolutely know what you are doing
// always using async/wait if possible
var result = await response.Content.ReadAsStringAsync();
return result;
}
}
}
Hopefully you are using a DI Framework:
public class MyController
{
private IAzureAPI _azureAPI;
public MyController(IAzureAPI azureAPI)
{
_azureAPI = azureAPI;
}
}
Now onto the hard part:
public async Task<ActionResult> Organization(string selectedOrg, string oauth)
{
IndexViewModel model = new IndexViewModel();
// I have no idea where model came from so
// this appears to block "unit-testing"
// strange that you don't validate `selectedOrg`, you just use it
model.Organizations = OrganizationData.Data;
if (selectedOrg == null)
{
selectedOrg = model.Organizations.FirstOrDefault().OrgName;
}
else
{
model.SelectedOrg = selectedOrg;
}
// no idea where `_cache` came from so
// also appears to block "unit-testing"
// As does `HttpContext` because you aren't using the
// Interface
var token = _cache.Get<TokenModel>("Token" + HttpContext.Session.GetString("TokenGuid"));
oauth = token.AccessToken;
try
{
var orgInfo = await _azureAPI.GetOrgAsync(selectedOrg, oauth);
model.Projects = JsonConvert.DeserializeObject<ProjectsModel>(orgInfo);
// return a view here???
return View("Index", model);
}
catch(Exception e)
{
// return JSON here instead????
return Json(Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(e.ToString()));
}
}
That's a general start, but there are too many unknowns, and to many dependencies to actually write a real unit test. Here is a quick structure and semi-test based on the information you've provided.
public MyControllerTests
{
// for 100% Cover Coverage you'd need all of these
public async Task Organization_OrgAsString_ReturnsView
{
//...
}
public async Task Organization_OrgAsNull_ReturnsView
{
// Arrange
var azureAPI = Substitute.For<IAzureAPI>();
azureAPI.GetOrgAsync(null, null)
.Returns("somestring");
var controller = new MyController(azureAPI);
// Act
var result = await controller.Organization(null, null);
// Assert
Assert.That(result....);
}
public async Task Organization_WithException_ReturnsJson
{
//...
}
}
I'm trying to implement a Polly Timeout policy using the new .NET Core 2.1 HttpClientFactory; however, I cannot seem to get the timeout to occur.
My ConfigureServices:
// Configure polly policies
TimeoutPolicy<HttpResponseMessage> timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(5, TimeoutStrategy.Pessimistic);
// Configure platform service clients
services.AddHttpClient<IDiscoveryClient, DiscoveryClient>()
.AddPolicyHandler(timeoutPolicy);
My POST method in DiscoveryClient:
public async Task<TResponse> PostXMLAsync<TResponse, TPostData>(string url, TPostData postData)
where TResponse : ClientResponse
where TPostData : ClientPostData
{
HttpResponseMessage response = await httpClient.PostAsXmlAsync(url, postData);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<TResponse>();
}
Unfortunately, the call times out after the default 100s rather than after the 5s defined in the Polly policy.
Any thoughts on what I'm doing wrong?
First let's define a mock server which does respond with 500 after 100 seconds:
const string address = "http://localhost:9000";
var delay = TimeSpan.FromSeconds(100);
var server = WireMockServer.Start(new WireMockServerSettings { Urls = new[] { address } });
server
.Given(Request.Create().WithPath("/").UsingPost())
.RespondWith(Response.Create().WithDelay(delay).WithStatusCode(500));
I've used WireMock.Net for this.
Now, let's see the IDiscoveryClient and DiscoveryClient:
interface IDiscoveryClient
{
Task<TResponse> SendRequest<TResponse, TPostData>(string url, TPostData data);
}
class DiscoveryClient : IDiscoveryClient
{
private readonly HttpClient httpClient;
public DiscoveryClient(HttpClient httpClient) => this.httpClient = httpClient;
public async Task<TResponse> SendRequest<TResponse, TPostData>(string url, TPostData data)
{
var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8);
var response = await httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
var rawData = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResponse>(rawData);
}
}
class TestRequest { public string Content { get; set; } }
class TestResponse { public string Data { get; set; } }
I've used json instead of xml, but that's not imporant from the question point of view.
And finally let's wire up the DI and issue a request:
AsyncTimeoutPolicy<HttpResponseMessage> timeoutPolicy =
Policy.TimeoutAsync<HttpResponseMessage>(5, TimeoutStrategy.Pessimistic);
IServiceCollection services = new ServiceCollection();
services.AddHttpClient<IDiscoveryClient, DiscoveryClient>()
.AddPolicyHandler(timeoutPolicy);
ServiceProvider serviceProvider = services.BuildServiceProvider();
var client = serviceProvider.GetService<IDiscoveryClient>();
Stopwatch sw = Stopwatch.StartNew();
try
{
TestResponse res = await client.SendRequest<TestResponse, TestRequest>(address, new TestRequest { Content = "Test"});
}
catch (TimeoutRejectedException ex)
{
sw.Stop();
Console.WriteLine(sw.Elapsed);
}
The printed output will be something like this:
00:00:05.0296804
The good thing is that it does work with Optimistic and Pessimistic strategies as well.