I have built an api caller which, for post requests, handles an input and output object:
public static async Task<TResponse> CallPostWebApi<TRequest, TResponse>(TRequest request, TResponse response, string serviceUrl)
{
using var client = new HttpClient { BaseAddress = new Uri(serviceUrl) };
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
var json = JsonConvert.SerializeObject(request);
var formData = new StringContent(json, Encoding.UTF8, "application/json");
var result = await client.PostAsync(serviceUrl, formData);
if (!result.IsSuccessStatusCode)
{
return response;
}
var data = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResponse>(data);
}
my api now returns an IActionResult with the object within it:
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("[Action]")]
public IActionResult MethodName(MyModel model)
{
try
{
var result = _reqRepo....
if (result.HasError)
{
return NotFound(result);
}
return Ok(result);
}
catch (Exception exception)
{
return NotFound(exception.Message);
}
}
when I call it though I don't know what to set as the return object
[HttpPost("[Action]")]
public async Task<IActionResult> SubmitConnectionToBilling(ConnectionModel model)
{
return await ApiCaller.CallPostWebApi(model, **???**,
_billingApiUrl + "/MyMethod");
}
Related
I call this WebApi endpoint:
public IActionResult MyEndPoint([FromBody] MyType myType)
{
// I do some stuff
var answer = new MyAnswer { Id = Guid.NewGuid() };
return Ok(answer);
}
The call to call the endpoint is this:
public async Task<string> httpPost(string url, string content)
{
var response = string.Empty;
using(var client = new HttpClient())
{
HttpRequestMessage request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(url),
Content = new StringContent(content, Encoding.UTF8, "application/json")
};
HttpResponseMessage result = await client.SendAsync(request);
if(result.IsSuccessStatusCode)
{
response = result.StatusCode.ToString(); //here
}
}
return response;
}
I'd like to have access MyAnswer object returned with the Ok() where the //here is. I put a breakpoint but nothing look like my object.
public async Task<MyAnswer> HttpPost(string url, string content)
{
var response = new MyAnswer();
using (var client = new HttpClient())
{
HttpRequestMessage request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(url),
Content = new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, "application/json")
};
HttpResponseMessage result = await client.SendAsync(request);
if (result.IsSuccessStatusCode)
{
var responseString = await result.Content.ReadAsStringAsync();
response = JsonSerializer.Deserialize<MyAnswer>(responseString, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
}
return response;
}
I am trying to UnitTest my MVC Controller method, which internally makes call to an WebAPI(using HttpClient). I'm not able to figure out how can I fake the httpclient call, as it should not go for actual request. Below is my source code and unit test case. Test case fails, as the call goes for actual HttpRequest (An error occurred while sending the request. A connection with the server could not be established)
Base MVC Controller
public class BaseController : Controller
{
public virtual async Task<T> PostRequestAsync<T>(string endpoint, Object obj) where T : class
{
var address = "http://localhost:5001/api/Login";
StringContent json = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "");
using (var client = new HttpClient())
{
try
{
var response = await client.PostAsync(address, json); // Test case fails here
if (response.IsSuccessStatusCode)
{
string data = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(data);
}
return default(T);
}
catch (WebException)
{
throw;
}
}
}
}
Derived class Controller
public class AccountController : BaseController
{
public AccountController() : base()
{
}
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
var result = await PostRequestAsync<ResultObject>(Constants.UserLoginAPI, model); // this is call for basecontroller method which actually has HttpClient call.
var output = JsonConvert.DeserializeObject<UserObject>(result.Object.ToString());
if (result.Succeeded && !string.IsNullOrEmpty(output.Email))
{
var userRoleInfo = await GetRequestAsync<List<UserRoleObject>>(string.Format(Constants.GetUserRoleInfoAPI, output.Email));
if (userRoleInfo != null)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, output.Email),
new Claim("Username", output.UserName)
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), new AuthenticationProperties { IsPersistent = model.RememberMe });
}
return View(new LoginViewModel());
}
}
return View(model);
}
}
TestMethod
[Fact]
public async Task LoginTest_Post_UserHasInValidCredentials()
{
// Arrange
var mockModel = new LoginViewModel { };
mockModel.Password = "TestPassword";
mockModel.Email = "test#test.com";
mockModel.RememberMe = false;
var commonResult = new CommonResult { Object = null, Succeeded = false, StatusCode = Common.Enums.ResponseStatusCodeEnum.Success };
var email = string.Empty;
var mockHttp = new MockHttpMessageHandler();
var mockBase = new Mock<BaseController>() { CallBase=true};
mockHttp.When("http://127.0.0.1:5001/*").Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON - using RichardSzalay.MockHttp;
//// Inject the handler or client into your application code
StringContent jsonInput = new StringContent(JsonConvert.SerializeObject(mockModel), Encoding.UTF8, "application/json");
var client = new HttpClient(mockHttp);
var response = await client.PostAsync("http://127.0.0.1:5001" + Constants.UserLoginAPI, jsonInput);
var json = await response.Content.ReadAsStringAsync();
mockBase.Setup(test => test.PostRequestAsync<CommonResult>(Constants.UserLoginAPI, mockModel)).Returns(Task.FromResult(CommonResult()));
var result = await accountController.Login(mockModel); // test case fails, as the call goes for actual HttpRequest (An error occurred while sending the request. A connection with the server could not be established)
//var viewResult = Assert.IsType<ViewResult>(result);
Assert.NotNull(commonResult);
Assert.False(commonResult.Succeeded);
Assert.Empty(email);
//Assert.NotNull(model.Email);
}
Tight coupling to HttpClient in the base controller makes it difficult to test derived classes in isolation. Review and refactor that code to follow DI.
No need to have a base controller and it is usually not advised.
Extract PostRequestAsync out into its own service abstraction and implementation.
public interface IWebService {
Task<T> PostRequestAsync<T>(string endpoint, Object obj) where T : class;
Task<T> GetRequestAsync<T>(string endpoint) where T : class;
}
public class WebService : IWebService {
static HttpClient client = new HttpClient();
public virtual async Task<T> PostRequestAsync<T>(string requestUri, Object obj) where T : class {
var content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "");
try {
var response = await client.PostAsync(requestUri, content); // Test case fails here
if (response.IsSuccessStatusCode) {
string data = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(data);
}
return default(T);
} catch (WebException) {
throw;
}
}
public async Task<T> GetRequestAsync<T>(string requestUri) where T : class {
try {
var response = await client.GetAsync(requestUri);
if (response.IsSuccessStatusCode) {
string data = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(data);
}
return default(T);
} catch (WebException) {
throw;
}
}
}
Refactor derived controllers to depend on the service abstraction
public class AccountController : Controller {
private readonly IWebService webService;
public AccountController(IWebService webService) {
this.webService = webService;
}
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) {
if (ModelState.IsValid) {
var result = await webService.PostRequestAsync<ResultObject>(Constants.UserLoginAPI, model);
if (result.Succeeded) {
var output = JsonConvert.DeserializeObject<UserObject>(result.Object.ToString());
if (output != null && !string.IsNullOrEmpty(output.Email)) {
var userRoleInfo = await webService.GetRequestAsync<List<UserRoleObject>>(string.Format(Constants.GetUserRoleInfoAPI, output.Email));
if (userRoleInfo != null) {
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, output.Email),
new Claim("Username", output.UserName)
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), new AuthenticationProperties { IsPersistent = model.RememberMe });
}
return View(new LoginViewModel());
}
}
}
return View(model);
}
}
This should now allow you to mock the dependency when testing in isolation without adverse side effects.
[Fact]
public async Task LoginTest_Post_UserHasInValidCredentials() {
// Arrange
var mockModel = new LoginViewModel { };
mockModel.Password = "TestPassword";
mockModel.Email = "test#test.com";
mockModel.RememberMe = false;
var commonResult = new CommonResult {
Object = null,
Succeeded = false,
StatusCode = Common.Enums.ResponseStatusCodeEnum.Success
};
var mockWebService = new Mock<IWebService>();
var accountController = new AccountController(mockWebService.Object) {
//HttpContext would also be needed
};
mockWebService
.Setup(test => test.PostRequestAsync<CommonResult>(Constants.UserLoginAPI, mockModel))
.ReturnsAsync(commonResult);
//
//Act
var result = await accountController.Login(mockModel);
//Assert
//...Make assertions here
}
I would inject an IHttpClient interface, and in release register a HttpClient wrapper that implements that interface.
I'm learning web api, well trying to - I've hit a snag. I can post to it fine and get a response when there are no parameters in the method on the api, e.g this works...
client
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + T.Access_Token);
var req = new HttpRequestMessage(HttpMethod.Post, "https://baseurl/api/controller/sites");
var response = await client.SendAsync(req);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
List<Site_2016> sites = Library.Data.Json.FromJson<List<Site_2016>>(result);
return sites;
}
else return new List<Site_2016>();
}
Api
[HttpPost]
[Route("sites")]
public async Task<IHttpActionResult> GetSites()
{
//Do stuff
return Ok(Sites);
}
But I can't for the life of me get it to work when I pass through some parameters like so...
client
using (var client = new HttpClient())
{
Dictionary<String, String> dict = new Dictionary<string, string>();
dict.Add("identifier", instance);
dict.Add("endOfDay", IsEndOfDayOnly.ToString());
var content = new FormUrlEncodedContent(dict);
client.BaseAddress = new Uri("https://baseurl/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + T.Access_Token);
var response = await client.PostAsync("api/controller/lastsaledate", content);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
KeyValuePair<String, DateTime> LastPush = Library.Data.Json.FromJson<KeyValuePair<String, DateTime>>(result);
return LastPush;
}
else
{
return new KeyValuePair<string, DateTime>();
}
}
Api
[HttpPost]
[Route("lastsaledate")]
public async Task<IHttpActionResult> GetLastSalesUpdate(string identifier, bool endOfDay)
{
//do stuff
return Ok(Result);
}
The response returns 404, I was wandering if it could be a routing issue perhaps? The bearer token is valid for both posts, if I disable authorization then I get the same result. The api/controller/action url is definitely correct.
Yes, in this case in particular you can try this
var response = await client.PostAsync(string.format("api/controller/lastsaledate?identifier={0}&endOfDay{1}",instance,IsEndOfDayOnly), content);
And remove the dict
Or you can try this
[HttpPost]
[Route("lastsaledate")]
public async Task<IHttpActionResult> GetLastSalesUpdate([FromBody]string identifier, [FromBody]bool endOfDay)
{
//do stuff
return Ok(Result);
}
Or you can try this
[HttpPost]
[Route("lastsaledate")]
public async Task<IHttpActionResult> GetLastSalesUpdate(testClass myParam)
{
//do stuff
return Ok(Result);
}
public class testClass
{
public string identifier;
public bool endOfDay;
}
I have this C# controller which preforming a different SQL functions by string receive as input.
public HttpResponseMessage GetFunction(string SQLstring)
{
HttpResponseMessage response = new HttpResponseMessage()
{
Content = new StringContent(SQLFunctions.SQLsyncFunctionGet(SQLstring), System.Text.Encoding.UTF8, "application/json")
};
return response;
}
I'm trying to rebuild it in Async method:
First I change the SQL sync function to async without any problem:
public async Task<string> SQLasyncFunctionGET(string SQLString)
How do I change the GetFunction class to activate it in the Web API that I've built?
public async Task<IHttpActionResult> GetFunction(string SQLString)
{
var content = await ???????????????
return ok(content);
}
I'm not that familiar with this stack, so I don't remember if you can return Task<HttpResponseMessage> or not. But if you can, this should work:
public async Task<HttpResponseMessage> GetFunction(string SQLstring)
{
HttpResponseMessage response = new HttpResponseMessage()
{
Content = new StringContent(await SQLFunctions.SQLsyncFunctionGet(SQLstring), System.Text.Encoding.UTF8, "application/json")
};
return response;
}
I have a web api which I'm calling (this is working correctly)
I call this like so
public ActionResult Index()
{
var mod = Checksomething();
return View();
}
public async Task Checksomething()
{
try
{
var client = new HttpClient();
var content = new StringContent(JsonConvert.SerializeObject(new UserLogin { EmailAddress = "SomeEmail#Hotmail.com", Password = "bahblah" }));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("http://localhost:28247/api/UserLoginApi2/CheckCredentials", content);
var value = await response.Content.ReadAsStringAsync();
// I need to return UserProfile
var data = JsonConvert.DeserializeObject<UserProfile[]>(value);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
My web api passes back a model called UserProfile, I'm having great difficulty trying to return data back to the Index controller, would someone please enlighten me.
You need to change your method signature to use the generic version of Task
public async Task<ActionResult> Index()
{
UserProfile[] profiles = await Checksomething();
if (profiles.Any())
{
var user = profiles.First();
string username = user.FirstName;
// do something w/ username
}
return View();
}
public async Task<UserProfile[]> Checksomething()
{
try
{
var client = new HttpClient();
var content = new StringContent(JsonConvert.SerializeObject(new UserLogin { EmailAddress = "SomeEmail#Hotmail.com", Password = "bahblah" }));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("http://localhost:28247/api/UserLoginApi2/CheckCredentials", content);
var value = await response.Content.ReadAsStringAsync();
// I need to return UserProfile
return JsonConvert.DeserializeObject<UserProfile[]>(value);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
The returned Task will be unwrapped and your caller will be given the Result of the Task, which in this case will be UserProfile[]