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("");

    // You can do work here that doesn't rely on the string from GetStringAsync.

    // 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.         }
  7.         // Changing synchonous code to asynchonous function
  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;
  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;
  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.                 }
  36.                 user.Id = RandomGenerator.Instance.NextInt();
  37.                 FakeDbContext.Instance.Users.Add(user);
  38.                 return Request.CreateResponse<User>(HttpStatusCode.Created, user);
  39.             });
  40.         }
  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.         }
  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.         }
  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.                 }
  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.                 }
  95.                 var index = FakeDbContext.Instance.Users.IndexOf(selectedUser);
  96.                 user.Id = selectedUser.Id;
  98.                 FakeDbContext.Instance.Users.RemoveAt(index);
  99.                 FakeDbContext.Instance.Users.Insert(index, user);
  101.                 return Request.CreateResponse<User>(HttpStatusCode.NoContent, user);
  102.             });
  103.         }
  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.                    }
  120.                    var selectedUser = FakeDbContext.Instance.Users.Find(u => u.Id == id);
  122.                    if (selectedUser == null)
  123.                    {
  124.                        return Request.CreateErrorResponse(HttpStatusCode.NotFound, "User not found.");
  125.                    }
  127.                    FakeDbContext.Instance.Users.Remove(selectedUser);
  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

Using Newtonsoft serializer in CosmosDB client

Problem In some scenarios engineers might want to use a custom JSON serializer for documents stored in CosmosDB.  Solution In CosmosDBV3 .NET Core API, when creating an instance of  CosmosClient one of optional setting in  CosmosClientOptions is to specify an instance of a Serializer . This serializer must be JSON based and be of  CosmosSerializer type. This means that if a custom serializer is needed this should inherit from CosmosSerializer abstract class and override its two methods for serializing and deserializing of an object. The challenge is that both methods from  CosmosSerializer are stream based and therefore might be not as easy to implement as engineers used to assume - still not super complex.  For demonstration purpose as or my custom serializer I'm going to use Netwonsoft.JSON library. Firstly a new type is needed and this must inherit from  CosmosSerializer.  using  Microsoft.Azure.Cosmos; using  Newtonsoft.Json; using  System.IO; using  System.Text; ///   <

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

Using Hortonworks Hive in .NET

A few months ago I decided to learn a big data. This sounds very complex and of course it is. All these strange names which actually tells nothing to person who is new in these area combined with different way of looking at data storage makes entire topic even more complex. However after reading N blogs and watching many, many tutorials today I finally had a chance to try to write some code. As in last week I managed to setup a Hortonworks distribution of Hadoop today I decided to connect to it from my .NET based application and this is what I will describe in this post. First things first I didn`t setup entire Hortonworks ecosystem from scratch - I`d love to but for now it`s far beyond my knowledge thus I decided to use a sandbox environment provided by Hortonworks. There are multiple different VMs available to download but in my case I`ve choose a Hyper-V. More about setting this environment up you can read here . Picture 1. Up and running sandbox environment. Now whe