In my two previous posts (part 1 and part 2) I described both simple and more advance approach of creating Rest-full API in MVC 4. Today I want to take another step forward and go a little deeper in the creation of API. In this post I`m going to describe how we can create asynchronous API functions which bring better performance to our applications.
The first thing that need to be understand is an asynchronous operation in .NET Framework. In .NET Framework 4.0 one of the most important changed was introducing Task class.The Tasks in System.Threading.Tasks namespace are a method of fine grained parallelism, similar to creating and using threads, but they have a few key differences and the main difference is that Tasks in .NET 4.0 don’t actually correlate to a new thread, they are executed on the new thread pool that is being shipped in .NET 4.0. More about task you can read here but to understand this article all you need to understand about the Task<T> class is that this type encapsulate asynchronous action which returns T type object. Task<T> type also expose a result of it in the Result<T> property and allow to verify execution state by checking Status or IsCompleted property.
Now we know more about asynchronous tasks in .NET Framework 4.0 but it`s not all because .NET Framework 4.5 offers a two completely new keywords in C# async and await. Those keywords exist together and work (in one function) to make functions completely asynchronous. To understand how to use it first of all we need to aware of plate to put it:
- async - exist in function signature right after access modifiers; it`s mark function to be asynchronous
- await - exists in the function body which is marked as async; it placed before function result which was called asynchronously
async Task<int> AccessTheWebAsync() { // You need to add a reference to System.Net.Http to declare client. HttpClient client = new HttpClient(); // GetStringAsync returns a Task<string>. That means that when you await the // task you'll get a string (urlContents). Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com"); // You can do work here that doesn't rely on the string from GetStringAsync. DoIndependentWork(); // The await operator suspends AccessTheWebAsync. // - AccessTheWebAsync can't continue until getStringTask is complete. // - Meanwhile, control returns to the caller of AccessTheWebAsync. // - Control resumes here when getStringTask is complete. // - The await operator then retrieves the string result from getStringTask. string urlContents = await getStringTask; // The return statement specifies an integer result. // Any methods that are awaiting AccessTheWebAsync retrieve the length value. return urlContents.Length; }
Now, when working with asynchronous operations is not problem for us, it`s time to back to our Rest API and make is asynchronous. As example of asynchronous API I want to use the same API I presented in my previous post and instead of changing logic just change it to fully asynchronous - so in other words I want to extend existing API with all CRUD operation form managing users to work non synchronous way.
[1] The very first thing I want to change is all API function signatures. I mean here change all functions from HttpResponseMessage Get() to async Task<HttpResponseMessage> Get(). I also believe that the new function signature is not mysterious for you after you read this article introduction.
[2] As second change I want to make is encapsulate all API functions logic execution to be part of the Task<T> as presented in example below.
Code Snippet
- [HttpGet]
- public HttpResponseMessage Get()
- {
- return Request.CreateResponse<ReadOnlyCollection<User>>(HttpStatusCode.OK, FakeDbContext.Instance.Users.AsReadOnly());
- }
- // Changing synchonous code to asynchonous function
- [HttpGet]
- public async Task<HttpResponseMessage> Get()
- {
- return await Task<HttpResponseMessage>.Factory.StartNew(() =>
- {
- var allUsers = FakeDbContext.Instance.Users.ToArray();
- var resultCollection = new Collection<User>(allUsers);
- return Request.CreateResponse<Collection<User>>(HttpStatusCode.OK, resultCollection);
- });
- }
That`s it! It was really straightforward as you can see. Now our API is working fully asynchronous and can be called without blocking. Let have overall look of it.
Code Snippet
- using MvcApplication1.Controllers;
- namespace WebApi.Controllers
- {
- using System;
- using System.Collections.ObjectModel;
- using System.Net;
- using System.Net.Http;
- using System.Threading.Tasks;
- using System.Web.Http;
- using WebApi.Common;
- using WebApi.Models;
- using MvcApplication1.Controllers;
- /// <summary>
- /// Asynchonous controller for managing <see cref="User"/>.
- /// </summary>
- public class AsyncController : ApiController
- {
- /// <summary>
- /// Add a new <see cref="User"/> to the collection.
- /// </summary>
- /// <param name="user">User to add.</param>
- /// <returns>The added <see cref="User"/>.</returns>
- /// <remarks>GET http://xy.z/api/async/ </remarks>
- [HttpPost]
- public async Task<HttpResponseMessage> Post(User user)
- {
- return await Task<HttpResponseMessage>.Factory.StartNew(() =>
- {
- if (user == null)
- {
- return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid user.");
- }
- user.Id = RandomGenerator.Instance.NextInt();
- FakeDbContext.Instance.Users.Add(user);
- return Request.CreateResponse<User>(HttpStatusCode.Created, user);
- });
- }
- /// <summary>
- /// Returns all users.
- /// </summary>
- /// <returns>Collection of all users.</returns>
- /// <remarks>GET http://xy.z/api/async/ </remarks>
- [HttpGet]
- public async Task<HttpResponseMessage> Get()
- {
- return await Task<HttpResponseMessage>.Factory.StartNew(() =>
- {
- var allUsers = FakeDbContext.Instance.Users.ToArray();
- var resultCollection = new Collection<User>(allUsers);
- return Request.CreateResponse<Collection<User>>(HttpStatusCode.OK, resultCollection);
- });
- }
- /// <summary>
- /// Get user by user ID.
- /// </summary>
- /// <param name="id">Id of the user.</param>
- /// <returns>User with specyfied ID.</returns>
- /// <remarks>GET http://xy.z/api/async/32</remarks>
- [HttpGet]
- public async Task<HttpResponseMessage> GetById(int id)
- {
- return await Task<HttpResponseMessage>.Factory.StartNew(() =>
- {
- var selectedUser = FakeDbContext.Instance.Users.Find(u => u.Id == id);
- return Request.CreateResponse<User>(HttpStatusCode.OK, selectedUser);
- });
- }
- /// <summary>
- /// Updates user.
- /// </summary>
- /// <param name="user">User to update.</param>
- /// <returns>Updated user.</returns>
- [HttpPut]
- public async Task<HttpResponseMessage> Put(User user)
- {
- return await Task<HttpResponseMessage>.Factory.StartNew(() =>
- {
- if (user == null || user.Id == 0)
- {
- return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid user.");
- }
- var selectedUser = FakeDbContext.Instance.Users.Find(u => u.Id == user.Id);
- if (selectedUser == null)
- {
- return Request.CreateErrorResponse(HttpStatusCode.NotFound, "User not found.");
- }
- var index = FakeDbContext.Instance.Users.IndexOf(selectedUser);
- user.Id = selectedUser.Id;
- FakeDbContext.Instance.Users.RemoveAt(index);
- FakeDbContext.Instance.Users.Insert(index, user);
- return Request.CreateResponse<User>(HttpStatusCode.NoContent, user);
- });
- }
- /// <summary>
- /// Delete user from storage.
- /// </summary>
- /// <param name="id">ID of the user to remove.</param>
- /// <returns>Response without content.</returns>
- [HttpDelete]
- public async Task<HttpResponseMessage> Delete(int id)
- {
- return await Task<HttpResponseMessage>.Factory.StartNew(() =>
- {
- if (id == 0)
- {
- return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid ID.");
- }
- var selectedUser = FakeDbContext.Instance.Users.Find(u => u.Id == id);
- if (selectedUser == null)
- {
- return Request.CreateErrorResponse(HttpStatusCode.NotFound, "User not found.");
- }
- FakeDbContext.Instance.Users.Remove(selectedUser);
- return Request.CreateResponse(HttpStatusCode.NoContent);
- });
- }
- }
- }
Whole source code of the project is available here.
Thank you.