Async Await in Web API Controller
Introduction
In WebAPI (part of mvc 4), controllers are view-agnostic. They do not return data to a view – rather they return pure, formatted data (either JSON or XML – see Appendix A). The client can be a javascript enabled browser that can populate its UI from the JSON or any other client that can parse XML.
Synchronous Controller ( Action ) Methods
This is what an action method in a webapi controller (ValuesController) looks like:
/// <summary>
/// This action does not render any view - APIController just returns data (as XML or JSON).
/// Here we return only JSON (see WebApiConfig for configuration that forces JSON formatting)
/// <returns>JSON formatted Product object</returns>
public Product Get()
{
Thread.Sleep(5000); // simulate long wait
return new Product
{
Name = "Soda",
Price = 2.00M,
ProductCode = 1
};
}
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
[JsonIgnore]
public int ProductCode { get; set; } // omitted
}
If you GET the Product (localhost/api/values), you should see the browser tied up – waiting for the 5 seconds that we have delayed the Get call.
Async Await Controller
To avoid tying up the browser, you can make your GET method async. This is a simple exercise – as shown in the example below.
public Product Get()
{
var t = LongRunningTaskAsync();
if(t.IsCompleted)
return t.Result;
return null;
}
// now call the same longrunningtask using async await
private async Task<Product> LongRunningTaskAsync()
{
var func = Task<Product>.Factory.StartNew(GetProductLongRunningTask);
return await func;
}
// simulate long wait with thread.sleep
private Product GetProductLongRunningTask()
{
Thread.Sleep(5000); // simulate long wait
return new Product
{
Name = "Soda",
Price = 2.00M,
ProductCode = 1
};
}
The steps are:
- Mark your long running method as async (the GET action method would call this async method )
- Use a Task<Product> to return a Product object
- Use a factory method (Task<Product>.Factory.StartNew() ) to start the Task
- await the result from the async task
- Use the .Result property (of the awaited task) to get your Product object.
Another Example – Mvc.Controller Action Method
Using the same steps as above, we can write an async version of a regular (non webapi) controller method.
public class AccountController : Controller
{
[HttpRoute("accounts")]
public async Task<GetAccountsResponse> GetAccountsAsync([FromUri]GetAccountsRequest acctRequest)
{
var requestDispatcher = IoC.Container.Resolve<IRequestDispatcher>();
return await Task<GetAccountsResponse>.Factory.StartNew( () =>
{
var response = requestDispatcher.Get<GetAccountsResponse>(acctRequest);
return response;
});
}
[HttpRoute("accounts")]
public GetAccountsResponse GetAccountsSynchronous([FromUri]GetAccountsRequest acctRequest)
{
var requestDispatcher = IoC.Container.Resolve<IRequestDispatcher>();
var response = requestDispatcher.Get<GetAccountsResponse>(acctRequest);
return response;
}
}
Summary
Using async and await, one can turn a regular controller action into an async action – thereby avoiding a ‘waiting’ UI. Another task can be launched while you are awaiting the results from the long running task.
Appendix A – Ensure that only JSON is returned by the WebApi controller action.
In MVC 4, here are two different route config files:
RouteConfig.cs which has
the regular MVC 4.0 application controller routes.WebApiConfig.cs
which contains routes for the WebAPI controllers.
Modify WebApiConfig.cs as follows (so that it does not default to XML formatting for return data)
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// to default to JSON, remove the XML formatter
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
}
Leave a Reply