I'm new to async Task in c#. What I want to accomplish is following:
- Api that calls 3 other api's async
- Return 3 datatsets as one
[System.Web.Http.AcceptVerbs("GET", "POST")]
[System.Web.Mvc.HttpGet]
public async Task<string> ServiceModelsForTournamentBase(int id)
{
var matchInfoJson = await GetJsonFromApi("api/asyncdata/searchmatchfortournament/" + id, _siteUrl);
var scoringPlayersJson = await GetJsonFromApi("api/asyncdata/scoringplayersfortournament/" + id, _siteUrl);
var teamsJson = await GetJsonFromApi("api/asyncdata/tournamentteams/" + id, _siteUrl);
// return json containing all three
}
private async Task<string> GetJsonFromApi(string serviceUrl, Uri siteUrl)
{
using (var client = new HttpClient())
{
client.BaseAddress = siteUrl;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(serviceUrl);
return response.IsSuccessStatusCode
? await response.Content.ReadAsStringAsync()
: string.Empty;
}
}
[System.Web.Http.AcceptVerbs("GET", "POST")]
[System.Web.Mvc.HttpGet]
public async Task<string> ServiceModelsForTournamentBase(int id)
{
var jsons = await Task.WhenAll(
GetJsonFromApi("api/asyncdata/searchmatchfortournament/" + id, _siteUrl),
GetJsonFromApi("api/asyncdata/scoringplayersfortournament/" + id, _siteUrl),
GetJsonFromApi("api/asyncdata/tournamentteams/" + id, _siteUrl)
);
var matchInfoJson = jsons[0];
var scoringPlayersJson = jsons[1];
var teamsJson = jsons[2];
// return json containing all three
}
Related
I'd like to call a Web API in .NET 6 with httpclient. The code works fine when I return OK(result), I get the right result. The problem is when I return a BadRequest, I'd like to have access to the properties EN, FR, NL. In the HttpRequestException, I just receive the message "500 internal exception error", not the properties EN, FR, NL.
How can I do to get these values ?
[HttpPost(nameof(Testing), Name = "Testing")]
public async Task<ActionResult<MyResponseDto>> Testing(ParameterDto parameter)
{
var res = new MyResponseDto
{
//Few properties her
};
//return Ok(res);
return BadRequest(new { FR = "My Error FR", NL = "My Error NL", EN = "My Error EN" });
}
I call web api from console application doe testing purpose with this code :
in program.cs
var result = await Api.Testing();
public static class Api
{
public static async Task<string> Testing()
{
string response = await HttpRequests.Post(
"/api/Testing",
new { /* few parameter here */ });
var result = JsonConvert.DeserializeObject<MyResponseDto>(response);
}
}
public static class MyHttpRequests
{
private static readonly HttpClient client = new HttpClient();
private const string url = "myURL";
public static async Task<string> Post(string entryPoint, Object dto)
{
string url = $"{url}{entryPoint}";
string dto = JsonConvert.SerializeObject(dto);
HttpContent httpContent = new StringContent(dto, Encoding.UTF8, "application/json");
try
{
using(HttpResponseMessage response = await client.PostAsync(url, httpContent))
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
return responseBody;
}
}
catch(HttpRequestException e)
{
Console.WriteLine($"Message :{e.Message} ");
}
return await Task.FromResult(string.Empty);
}
}
This line is throwing the exception:
response.EnsureSuccessStatusCode();
Without it, you would continue to return the response body which will contain a json representation of your error variables.
So instead of throwing an exception like this, you can manually check the response status code and parse the json. EG:
public static async Task<string> Post(string entryPoint, Object dto)
{
string url = $"{url}{entryPoint}";
string dto = JsonConvert.SerializeObject(dto);
HttpContent httpContent = new StringContent(dto, Encoding.UTF8, "application/json");
using(HttpResponseMessage response = await client.PostAsync(url, httpContent))
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
return responseBody;
Console.WriteLine($"Error: {responseBody}");
}
return await Task.FromResult(string.Empty);
}
I am starting WebApi tutorial but I just faced a problem that parameter in action is a null.
Below is the tutorial code.
WebApi
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var raw = Request.Content.ReadAsStringAsync();
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, Result.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
WebApi Client
static string Register(string email, string password)
{
var Result = new RegisterBindingModel()
{
Email = email,
Password = password,
ConfirmPassword = password
};
using (var client = new HttpClient())
{
var response = client.PostAsJsonAsync(
"http://localhost:7399/api/Account/Register",
Result).Result;
return response.StatusCode.ToString();
}
}
Register action receives http request but model is always null. So the raw variable shows like this
{"Email":"test#gmail.com","Password":"Test#123","ConfirmPassword":"Test#123"}
But when I tried sending http request using Postman it worked. As request body was read for model binding. The raw variable was empty. I don't know what's wrong with my client. I followed exactly tutorial code. Should I specify content-type?
make variable name same i.e. in Register method change var Result to var model and have a try.
it should be frombody , i.e. get parameter value from post body
[HttpPost]
public async Task<IHttpActionResult> Register([FromBody]RegisterBindingModel model)
Add HttpPost And Use below Methods
[AllowAnonymous]
[HttpPost]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
//your code
}
Use this Methods:-
public static async Task<HttpResponseMessage> PostAsJsonAsync(this HttpClient client,
string requestUri, object requestObject, Dictionary<string, string> requestHeaders = null,
int? timeoutMilliSeconds = null)
{
var jsonContent = JsonConvert.SerializeObject(requestObject);
var requestContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
return await SendAsync(client, requestUri, HttpMethod.Post,
requestContent, requestHeaders, timeoutMilliSeconds);
}
public static async Task<HttpResponseMessage> SendAsync(
this HttpClient client,
string requestUri, HttpMethod httpMethod, HttpContent requestContent = null,
Dictionary<string, string> requestHeaders = null, int? timeoutMilliSeconds = null)
{
var httpRequestMessage = new HttpRequestMessage
{
RequestUri = new Uri(requestUri),
Method = httpMethod,
Content = requestContent
};
if (requestHeaders != null)
{
foreach (var requestHeader in requestHeaders)
{
httpRequestMessage.Headers.Add(requestHeader.Key, requestHeader.Value);
}
}
if (timeoutMilliSeconds.HasValue)
{
var cts = new CancellationTokenSource();
cts.CancelAfter(new TimeSpan(0, 0, 0, 0, timeoutMilliSeconds.Value));
return await client.SendAsync(httpRequestMessage, cts.Token);
}
else
{
return await client.SendAsync(httpRequestMessage);
}
}
I am using Moq in .net core(1.1) and having a bit of torrid time understanding this behavior as all the examples on interweb points to the fact the this should work with no issues.
I have already tried with:
Returns(Task.FromResult(...)
Returns(Task.FromResult(...)
ReturnsAsync(...)
Mocking a IHttpClient interface to wrap PostAsync, PutAsync and GetAsync. All of these return an ApiResponse object.
var mockClient = new Mock<IHttpClient>();
Does not work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(new ApiResponse()));
PostSync definition:
public async Task<ApiResponse> PostAsync(string url, string body, string authToken = null)
Does work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(bool));
PostSync definition:
public async Task<bool> PostAsync(string url, string body, string authToken = null)
Usage:
var api = new ApiService(mockClient.Object);
var response = api.LoginAsync(body.Username, body.Password);
UPDATE
[Fact]
public async void TestLogin()
{
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null)).Returns(Task.FromResult(new ApiResponse()));
var api = new ApiService(mockClient.Object);
var response = await api.LoginAsync(body.Username, body.Password);
Assert.IsTrue(response);
}
Return Type:
public class ApiResponse
{
public string Content { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string Reason { get; set; }
}
LoginAsync:
public async Task<bool> LoginAsync(string user, string password)
{
var body = new { Username = user, Password = password };
try
{
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response .State == 1;
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
}
PostAsync:
public async Task<object> PostAsync(string url, string body, string authToken = null)
{
var client = new HttpClient();
var content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.PostAsync(new Uri(url), content);
var resp = await response.Result.Content.ReadAsStringAsync();
return new ApiResponse
{
Content = resp,
StatusCode = response.Result.StatusCode,
Reason = response.Result.ReasonPhrase
};
}
Assuming a simple method under test like this based on minimal example provided above.
public class ApiService {
private IHttpClient _http;
private string _login_url;
public ApiService(IHttpClient httpClient) {
this._http = httpClient;
}
public async Task<bool> LoginAsync(string user, string password) {
var body = new { Username = user, Password = password };
try {
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response.StatusCode == HttpStatusCode.OK;
} catch (Exception ex) {
//Logger.Error(ex);
return false;
}
}
}
The following test works when configured correctly
[Fact]
public async Task Login_Should_Return_True() { //<-- note the Task and not void
//Arrange
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<string>(), null))
.ReturnsAsync(new ApiResponse() { StatusCode = HttpStatusCode.OK });
var api = new ApiService(mockClient.Object);
//Act
var response = await api.LoginAsync("", "");
//Assert
Assert.IsTrue(response);
}
The above is just for demonstrative purposes only to show that it can work provided the test is configured properly and exercised based on the expected behavior.
Take some time and review the Moq quick start to get a better understanding of how to use the framework.
I am confused on where my deserialization logic should go.
I have a controller that returns data to the client specifically for a GET operation:
public accountscontroller:apicontroller
{
[HttpGet]
[Route("", Name = "GetAccount")]
public async Task<IHttpActionResult> GetAccount()
{
var query = Request.RequestUri.PathAndQuery.Split('/')[2];
var response = await _accountService.GetAccount(query);
if (response == null)
{
return NotFound();
}
return Ok(response);
}
//morestuff
}
and the AccountService.GetAccount code is the following:
public class AccountService
{
public async Task<Account> GetAccount(string query)
{
var task = await Client.HTTPCLIENT.GetAsync(Client.HTTPCLIENT.BaseAddress + query);
var jsonString = await task.Content.ReadAsStringAsync();
var value = JsonConvert.DeserializeObject<RootObject>(jsonString);
return value.value.FirstOrDefault();
}
//morestuff
}
as you can see, the deserialization is handled in the AccountService, not the AccountsController
however, if we look at the POST operation:
public class AccountController
{
[HttpPost]
[Route("", Name = "CreateAccount")]
public async Task<IHttpActionResult> CreateAccount([FromBody] JObject account)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var response = await _accountService.Create(account);
var newAccount = await response.Content.ReadAsAsync<object>();
return Ok(newAccount);
}
and the underlying Create method within the AccountService:
public async Task<HttpResponseMessage> Create(JObject account)
{
var request = new HttpRequestMessage(HttpMethod.Post, Client.HTTPCLIENT.BaseAddress + "accounts")
{
Content = new StringContent(account.ToString(), Encoding.UTF8, "application/json")
};
var response = await Client.HTTPCLIENT.SendAsync(request);
var uri = new Uri(response.Headers.GetValues("OData-EntityId").FirstOrDefault());
return await Client.HTTPCLIENT.GetAsync(uri);
}
you will see that in fact the deserialization happens on the controller level.
How can I encapsulate the deserialization logic for CRUD operations, such as GET/PUT/POST for consistency?
The aim is to make controller that uses async method in my custom service.
Controller:
[Route("api/data/summary")]
[HttpGet]
public async Task<IHttpActionResult> Get()
{
var result = await DataService.GetDataObjects();
return Ok(result);
}
Service:
public static async Task<IEnumerable<DataObject>> GetDataObjects()
{
var apiKey = "some-api-key";
var path = "path-to-external-service";
using (var client = new HttpClient())
{
var dataToProcess = // some data object
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.BaseAddress = new Uri(path);
HttpResponseMessage response = await client.PostAsJsonAsync("", dataToProcess);
var content = await response.Content.ReadAsStringAsync();
var result = MakeEntities(content); // some logic
return result;
}
}
But I came across with the problem that controller's action returns empty result before service actually finished processing data.
Could you please advice how to implement it correctly?
Your code is OK and controller doesn't seem to return a value before GetDataObjects returns value.
Except for the situations below:
MakeEntities uses some asynchronous operation and you don't await it inside MakeEntities. So MakeEntities return task.
Exception rises while your code is running. Make sure GetDataObjects and MakeEntities code works fine.
The aim is to make controller that uses async method in my custom service.
Controller:
[HttpGet]
[Route("api/data/summary")]
public async Task<IHttpActionResult> Get()
{
var result = await DataService.GetDataObjects().ConfigureAwait(false);
return Ok(result);
}
Service:
public static async Task<ResponseEntity> GetDataObjects()
{
ResponseEntity response = new ResponseEntity();
var apiKey = "some-api-key";
var path = "path-to-external-service";
using (var client = new HttpClient())
{
var dataToProcess = // some data object
client.BaseAddress = new Uri(path);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("", dataToProcess).ConfigureAwait(false);
string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject<ResponseEntity>(responseString);
return response;
}
}