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?
Related
We are using Microsoft Graph SDK. Implemented a POC in console application where the code works fine but when added this code in MVC its not working. code stucks at await call
Calling from controller as
[HttpPost]
public ActionResult InviteUser([Bind(Include = "EmailId")] UserLogin userLogin)
{
if (ModelState.IsValid)
{
string result = AzureADUtil.InviteUser(userLogin.EmailId);
}
return View();
}
Method implementation is as below
public static string InviteUser(string emailAddress)
{
string result = string.Empty;
result = InviteNewUser(emailAddress).Result;
return result;
}
private static async Task<string> InviteNewUser(string emailAddress)
{
string result = string.Empty;
try
{
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
// Send Invitation to new user
var invitation = new Invitation
{
InvitedUserEmailAddress = emailAddress,
InviteRedirectUrl = "https://myapp.com",
SendInvitationMessage = true,
InvitedUserType = "Member"
};
// It stucks at this line
await graphClient.Invitations
.Request()
.AddAsync(invitation);
}
catch (Exception ex)
{
result = ex.Message;
}
return result;
}
Mixing async-await and blocking code like .Result or .Wait() tends to lead to deadlocks, especially on asp.net-mvc.
If going async then go all the way.
[HttpPost]
public async Task<ActionResult> InviteUser([Bind(Include = "EmailId")] UserLogin userLogin) {
if (ModelState.IsValid) {
string result = await AzureADUtil.InviteUser(userLogin.EmailId);
}
return View();
}
With implementation refactored to be async as well
public static async Task<string> InviteUser(string emailAddress)
{
string result = string.Empty;
result = await InviteNewUser(emailAddress);
return result;
}
InviteUser is now redundant since it basically wraps the private InviteNewUser call.
Reference Async/Await - Best Practices in Asynchronous Programming
The best would be to update your code to run async the whole way down the request chain. You could do that as follows:
[HttpPost]
public async Task<ActionResult> InviteUser([Bind(Include = "EmailId")] UserLogin userLogin)
{
if (ModelState.IsValid)
{
string result = await AzureADUtil.InviteNewUser(userLogin.EmailId).ConfigureAwait(false);
}
return View();
}
This is an example of an Action method inside HomeController:
[HttpPost]
public async Task<dynamic> UnitTest(string data)
{
var httpClient = new HttpClient();
var request = JsonConvert.SerializeObject(data);
var url = "https://jsonplaceholder.typicode.com/posts";
var response = await httpClient.PostAsync(url, new StringContent(request, Encoding.UTF8, "application/json"));
string responseContent = await response.Content.ReadAsStringAsync();
return responseContent;
}
I want to test it, but I do not know how. I tried the following:
[TestMethod]
public async Task JsonRightTest()
{
MyModelR model1 = new MyModelR
{
Title = "foo",
Body = "bar",
UserId = 1
};
string output1 = JsonConvert.SerializeObject(model1);
var url = "Home/UnitTest";
var response = await _client.PostAsync(url, new StringContent(output1, Encoding.UTF8, "application/json"));
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var responseModel = JsonConvert.DeserializeObject<MyModel>(responseContent);
// Assert
Assert.AreEqual(1,
responseModel.UserId);
}
internal class MyModel
{
public string Title { get; set; }
public string Body { get; set; }
public int UserId { get; set; }
public int Id { get; set; }
}
internal class MyModelR
{
public string Title { get; set; }
public string Body { get; set; }
public int UserId { get; set; }
}
Unfortunately, the above does not work. Since I am very confused could you give me some answers to the following:
What's the best way to test UnitTest action? Is my approach wrong? Do I just have to call the API from JsonRightTest method and not involve the action?
Actually, in that case do we have a unit or integrated test?
I want to call the actual external end point.
The API (https://jsonplaceholder.typicode.com/posts) is found on the Internet and is available for testing purposes.
This appears to be an XY problem and a mixing of concerns.
The code under test is tightly coupled to implementation concerns and should encapsulate that external call behind a service abstraction that can be mocked during isolated unit tests.
Some refactoring steps that should be followed....
Those models being constructed in the test should be in the action.
[HttpPost]
public async Task<IActionResult> UnitTest([FromBody]MyDataR data) {
var httpClient = new HttpClient();
var requestJson = JsonConvert.SerializeObject(data);
var url = "https://jsonplaceholder.typicode.com/posts";
var response = await httpClient.PostAsync(url, new StringContent(requestJson, Encoding.UTF8, "application/json"));
if(response.IsSuccessStatusCode) {
var responseContent = await response.Content.ReadAsStringAsync();
var responseModel = JsonConvert.DeserializeObject<MyModel>(responseContent);
return Ok(responseModel);
}else
return StatusCode(response.StatusCode);
}
Refactoring further, the actual calling of the external endpoint should be abstracted away
public interface IExternalService {
Task<MyModel> PostDataAsync(MyData data);
}
and implemented accordingly
public class ExternalService : IExternalService {
// should consider abstracting this as well but that is another matter
static Lazy<HttpClient> _httpClient = new Lazy<HttpClient>(() => new HttpClient());
private HttpClient httpClient {
get { return _httpClient.Value; }
}
public async Task<MyModel> PostDataAsync(MyData data) {
var requestJson = JsonConvert.SerializeObject(data);
var url = "https://jsonplaceholder.typicode.com/posts";
var content = new StringContent(requestJson, Encoding.UTF8, "application/json")
var response = await httpClient.PostAsync(url, content);
var responseContent = await response.Content.ReadAsStringAsync();
if(response.IsSuccessStatusCode) {
var responseContent = await response.Content.ReadAsStringAsync();
var responseModel = JsonConvert.DeserializeObject<MyModel>(responseContent);
return responseModel;
}else
return null;
}
}
with the action in the the controller now looking like
private readonly IExternalService externalService; // Assumed injected into the controller
[HttpPost]
public async Task<IActionResult> UnitTest([FromBody]MyDataR data) {
var responseModel = await externalService.PostDataAsync(data);
if(responseModel != null) {
return Ok(responseModel);
}else
return BadRequest();
}
By removing the tight coupling to the external service call , this would allow the controller to be tested in isolation as needed to verify that it behaves as expected.
The external service call implementation can now be tested on its own if the desire is to check that the external end point behaves as expected. This would be considered an integration test due to its reliance on the actual external endpoint.
[TestMethod]
public async Task JsonRightTest() {
// Arrange
var expected = 1;
var model = new MyModelR {
Title = "foo",
Body = "bar",
UserId = 1
};
var target = new ExternalService(); // System under test
// Act
var responseModel = await target.PostDataAsync(model);
// Assert
Assert.IsNotNull(responseModel);
var actual = responseModel.UserId;
Assert.AreEqual(expected, actual);
}
This should now allow for easier inspection of the external service to verify that it behaves as expected.
In production you would make sure that the external service abstraction and its implementation are registered in the composition root.
services.AddTransient<IExternalService, ExternalService>();
so that it is injected into the dependent controller correctly.
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.
How do I force my controller to return a 404?
[HttpGet]
[Route("account({accountid})/printgroup", Name = "Get")]
public HttpResponseMessage Get(int accountid)
{
var query = Request.RequestUri.Query;
var uri = new Uri(Client.Instance.BaseAddress.ToString().Replace("[accountid]", accountid.ToString()) + query);
var request = new HttpRequestMessage {RequestUri = uri, Method = HttpMethod.Get};
var clientResponse = Client.Instance.SendAsync(request).Result;
return clientResponse;
}
When the clientResponse is 404, the method does not return a response. It just times out.
What am I doing wrong? How do I force it to return a 404 if the client response is 404?
EDIT:
Per macceturra's helpful comments, I've created my own object:
Yet the behavior is the same! The method will go out of scope, yet the client does not see anything. It just times out.
My latest update is:
[HttpGet]
[Route("account({accountid})/bill({billingRunId})", Name = "GetInvoiceSummary")]
public async Task<IHttpActionResult> GetInvoiceSummary(int accountid, int billingRunId)
{
var query = Request.RequestUri.Query;
var uri = new Uri(Client.Instance.BaseAddress.ToString() + accountid + "/" + billingRunId + query);
var request = new HttpRequestMessage { RequestUri = uri, Method = HttpMethod.Get };
var response = await Client.Instance.SendAsync(request);
if (response.StatusCode == HttpStatusCode.NotFound)
return NotFound();
return ResponseMessage(response);
}
The behavior is once again the same.
Update
Based on discussion in comments it was advised that you review the response pipeline as it was determined that the code in the action was handling/executing the request as intended. The issue was being encountered by the response on its way out the pipeline which cause the client to time out. Check your message handlers and middle-ware in the pipeline that may be causing the response to block on its way out the pipeline based on how it was handled by your controllers.
Original answer
Consider the following, using the preferred Web API 2.* syntax along with proper use of async/await.
[HttpGet]
[Route("account({accountid})/printgroup", Name = "Get")]
public async Task<IHttpActionResult> Get(int accountid) {
var query = Request.RequestUri.Query;
var uri = new Uri(Client.Instance.BaseAddress.ToString().Replace("[accountid]", accountid.ToString()) + query);
var request = new HttpRequestMessage { RequestUri = uri, Method = HttpMethod.Get };
var clientResponse = await Client.Instance.SendAsync(request);
if (clientResponse.StatusCode == System.Net.HttpStatusCode.NotFound)
return NotFound();
return ResponseMessage(clientResponse);
}
After adding this piece of middleware, the problem has been resolved:
public class NotFoundMiddleware : OwinMiddleware
{
public NotFoundMiddleware(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
if (context.Response.StatusCode == (int)HttpStatusCode.NotFound
&& !context.Response.Headers.ContainsKey("X-ServiceFabric")
)
context.Response.Headers.Add("X-ServiceFabric", new[] { "ResourceNotFound" });
}
}
Is there a way to deserialize/serialize JSON POST content/response within the body of a Web API controller in the same exact way that the controller would deserialize/serialize the JSON content/response had it been an argument/explicit type?
My motivation is that I am trying to build a "magic" API controller that can provide access to my service commands quickly if developers do not have enough time to build a strongly-typed action to access them. Say I have the following actions:
public Brownie Brownie(BrownieInputModel inputModel)
{
...
var brownie = brownieService.GetBrownie(inputModel);
return brownie;
}
public Pizza Pizza(PizzaInputModel inputModel)
{
...
var pizza = pizzaService.GetPizza(inputModel);
return pizza;
}
Would it be possible to do something like this (in psuedo-code):
public object FoodStuff(string methodName)
{
var inputModel = WebApi.Deserialize(Request.JsonContent); // <-- pseudo
var serviceMethod = GetServiceMethodFromMethodName(methodName);
var result = serviceMethod.Execute();
return WebApi.Serialize(result); // <-- pseudo
}
I think HttpRequestMessage and HttpResponseMessage classes can help you solve this problem. Using them you code can look like that:
public async Task<HttpResponseMessage> Post(
string methodName, HttpRequestMessage request)
{
HttpResponseMessage response;
switch (methodName)
{
case "brownie":
response = await HandleBrownieAsync(request);
break;
case "pizza":
response = await HandlePizzaAsync(request);
break;
default:
throw new NotSupportedException();
}
return response;
}
private async Task<HttpResponseMessage> HandleBrownieAsync(HttpRequestMessage request)
{
var brownie = await GetRequestContentAsync<Brownie>(request);
return await CreateJsonResponseAsync(brownie);
}
private async Task<HttpResponseMessage> HandlePizzaAsync(HttpRequestMessage request)
{
var pizza = await GetRequestContentAsync<Pizza>(request);
return await CreateJsonResponseAsync(pizza);
}
private async Task<T> GetRequestContentAsync<T>(HttpRequestMessage request)
{
var contentString = await request.Content.ReadAsStringAsync();
return await JsonConvert.DeserializeObjectAsync<T>(contentString);
}
private async Task<HttpResponseMessage> CreateJsonResponseAsync<T>(T content)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(
await JsonConvert.SerializeObjectAsync(content),
Encoding.UTF8,
"application/json")
};
return response;
}
And if you use these simplified models:
public class Brownie
{
public bool HasNuts { get; set; }
}
public class Pizza
{
public string CheeseType { get; set; }
}
Then you can make POST requests:
URL: http://api.url?methodName=brownie,
Body: {"hasNuts":true}
or
URL: http://api.url?methodName=pizza,
Body: {"cheeseType":"Mozzarella"}