Skip to main content

Creating API with MVC ApiController part 3 - moving to asynchronous code

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 
Usage of both new keywords is very easy as far as you can see on the example presented below (from MSDN) but still restrictions still and you can read about it here

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
  1. [HttpGet]
  2.         public HttpResponseMessage Get()
  3.         {
  4.             return Request.CreateResponse<ReadOnlyCollection<User>>(HttpStatusCode.OK, FakeDbContext.Instance.Users.AsReadOnly());
  5.         }
  6.  
  7.         // Changing synchonous code to asynchonous function
  8.  
  9.         [HttpGet]
  10.         public async Task<HttpResponseMessage> Get()
  11.         {
  12.             return await Task<HttpResponseMessage>.Factory.StartNew(() =>
  13.             {
  14.                 var allUsers = FakeDbContext.Instance.Users.ToArray();
  15.                 var resultCollection = new Collection<User>(allUsers);
  16.                 return Request.CreateResponse<Collection<User>>(HttpStatusCode.OK, resultCollection);
  17.             });
  18.         }

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
  1. using MvcApplication1.Controllers;
  2.  
  3. namespace WebApi.Controllers
  4. {
  5.     using System;
  6.     using System.Collections.ObjectModel;
  7.     using System.Net;
  8.     using System.Net.Http;
  9.     using System.Threading.Tasks;
  10.     using System.Web.Http;
  11.     using WebApi.Common;
  12.     using WebApi.Models;
  13. using MvcApplication1.Controllers;
  14.  
  15.     /// <summary>
  16.     /// Asynchonous controller for managing <see cref="User"/>.
  17.     /// </summary>
  18.     public class AsyncController : ApiController
  19.     {
  20.         /// <summary>
  21.         /// Add a new <see cref="User"/> to the collection.
  22.         /// </summary>
  23.         /// <param name="user">User to add.</param>
  24.         /// <returns>The added <see cref="User"/>.</returns>
  25.         /// <remarks>GET http://xy.z/api/async/ </remarks>
  26.         [HttpPost]
  27.         public async Task<HttpResponseMessage> Post(User user)
  28.         {
  29.             return await Task<HttpResponseMessage>.Factory.StartNew(() =>
  30.             {
  31.                 if (user == null)
  32.                 {
  33.                     return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid user.");
  34.                 }
  35.  
  36.                 user.Id = RandomGenerator.Instance.NextInt();
  37.                 FakeDbContext.Instance.Users.Add(user);
  38.                 return Request.CreateResponse<User>(HttpStatusCode.Created, user);
  39.             });
  40.         }
  41.  
  42.         /// <summary>
  43.         /// Returns all users.
  44.         /// </summary>
  45.         /// <returns>Collection of all users.</returns>
  46.         /// <remarks>GET http://xy.z/api/async/ </remarks>
  47.         [HttpGet]
  48.         public async Task<HttpResponseMessage> Get()
  49.         {
  50.             return await Task<HttpResponseMessage>.Factory.StartNew(() =>
  51.             {
  52.                 var allUsers = FakeDbContext.Instance.Users.ToArray();
  53.                 var resultCollection = new Collection<User>(allUsers);
  54.                 return Request.CreateResponse<Collection<User>>(HttpStatusCode.OK, resultCollection);
  55.             });
  56.         }
  57.  
  58.         /// <summary>
  59.         /// Get user by user ID.
  60.         /// </summary>
  61.         /// <param name="id">Id of the user.</param>
  62.         /// <returns>User with specyfied ID.</returns>
  63.         /// <remarks>GET http://xy.z/api/async/32</remarks>
  64.         [HttpGet]
  65.         public async Task<HttpResponseMessage> GetById(int id)
  66.         {
  67.             return await Task<HttpResponseMessage>.Factory.StartNew(() =>
  68.             {
  69.                 var selectedUser = FakeDbContext.Instance.Users.Find(u => u.Id == id);
  70.                 return Request.CreateResponse<User>(HttpStatusCode.OK, selectedUser);
  71.             });
  72.         }
  73.  
  74.         /// <summary>
  75.         /// Updates user.
  76.         /// </summary>
  77.         /// <param name="user">User to update.</param>
  78.         /// <returns>Updated user.</returns>
  79.         [HttpPut]
  80.         public async Task<HttpResponseMessage> Put(User user)
  81.         {
  82.             return await Task<HttpResponseMessage>.Factory.StartNew(() =>
  83.             {
  84.                 if (user == null || user.Id == 0)
  85.                 {
  86.                     return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid user.");
  87.                 }
  88.  
  89.                 var selectedUser = FakeDbContext.Instance.Users.Find(u => u.Id == user.Id);
  90.                 if (selectedUser == null)
  91.                 {
  92.                     return Request.CreateErrorResponse(HttpStatusCode.NotFound, "User not found.");
  93.                 }
  94.  
  95.                 var index = FakeDbContext.Instance.Users.IndexOf(selectedUser);
  96.                 user.Id = selectedUser.Id;
  97.  
  98.                 FakeDbContext.Instance.Users.RemoveAt(index);
  99.                 FakeDbContext.Instance.Users.Insert(index, user);
  100.  
  101.                 return Request.CreateResponse<User>(HttpStatusCode.NoContent, user);
  102.             });
  103.         }
  104.  
  105.         /// <summary>
  106.         /// Delete user from storage.
  107.         /// </summary>
  108.         /// <param name="id">ID of the user to remove.</param>
  109.         /// <returns>Response without content.</returns>
  110.         [HttpDelete]
  111.         public async Task<HttpResponseMessage> Delete(int id)
  112.         {
  113.             return await Task<HttpResponseMessage>.Factory.StartNew(() =>
  114.                {
  115.                    if (id == 0)
  116.                    {
  117.                        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid ID.");
  118.                    }
  119.  
  120.                    var selectedUser = FakeDbContext.Instance.Users.Find(u => u.Id == id);
  121.  
  122.                    if (selectedUser == null)
  123.                    {
  124.                        return Request.CreateErrorResponse(HttpStatusCode.NotFound, "User not found.");
  125.                    }
  126.  
  127.                    FakeDbContext.Instance.Users.Remove(selectedUser);
  128.  
  129.                    return Request.CreateResponse(HttpStatusCode.NoContent);
  130.                });
  131.         }
  132.     }
  133. }

Whole source code of the project is available here.

Thank you.

Popular posts from this blog

Full-Text Search with PDF in Microsoft SQL Server

Last week I get interesting task to develop. The task was to search input text in PDF file stored in database as FileStream. The task implementation took me some time so I decided to share it with other developers. Here we are going to use SQL Server 2008 R2 (x64 Developers Edition), external driver from Adobe, Full-Text Search technology and FileStream technology.Because this sems a little bit comlicated let`s make this topic clear and do it step by step. 1) Enable FileStream - this part is pretty easy, just check wheter You already have enabled filestream on Your SQL Server instance - if no simply enable it as in the picture below. Picture 1. Enable filestream in SQL Server instance. 2) Create SQL table to store files  - mainly ther will be PDF file stored but some others is also be allright. Out table DocumentFile will be created in dbo schema and contain one column primary key with default value as sequential GUID. Important this is out table contains FileStream

Playing with a .NET types definition

In the last few days I spent some time trying to unify structure of one of the project I`m currently working on. Most of the changes were about changing variable types because it`s were not used right way. That is why in this post I want to share my observations and practices with you. First of all we need to understand what ' variable definition ' is and how it`s different from ' variable initialization '. This part should be pretty straightforward:   variable definition  consist of data type and variable name only <data_type> <variable_name> ; for example int i ; . It`s important to understand how variable definition affects your code because it behaves differently depends weather you work with value or reference types. In the case of value types after defining variable it always has default value and it`s never null value. However after defined reference type variable without initializing it has null value by default. variable initialization  is

Persisting Enum in database with Entity Framework

Problem statement We all want to write clean code and follow best coding practices. This all engineers 'North Star' goal which in many cases can not be easily achievable because of many potential difficulties with converting our ideas/good practices into working solutions.  One of an example I recently came across was about using ASP.NET Core and Entity Framework 5 to store Enum values in a relational database (like Azure SQL). Why is this a problem you might ask... and my answer here is that you want to work with Enum types in your code but persist an integer in your databases. You can think about in that way. Why we use data types at all when everything could be just a string which is getting converted into a desirable type when needed. This 'all-string' approach is of course a huge anti-pattern and a bad practice for many reasons with few being: degraded performance, increased storage space, increased code duplication.  Pre-requirements 1. Status enum type definition