Nghiên cứu Web API với ASP.NET MVC4

ASP.NET Web API là 1 framework để xây dựng các dịch vụ HTTP có thể truy cập từ nhiều client như trình duyệt hay các thiết bị di động. Web API được giới thiệu là 1 phần của MVC4. Trong bài viết này tôi sẽ giới thiệu thiết kế, khái niệm, tính năng và so sánh Web API với WCF.

Thực thi 1 WEB API project

Hãy bắt đầu với việ tạo 1 project ví dụ về Web API. Bước đầu cần tạo 1 ASP.NET MVC 4 project dùng Web API template như hình sau:

Tiếp đó ta cần tạo 1 Model tên là Product với các thuộc tính như sau:

Sau khi tạo Product model ta có thể tạo API controller để xử lý Product model:

Tiếp đó ta tạo 1 số dữ liệu mẫu:


public class ProductsController : ApiController

    {

        List<Product> products = new List<Product>();
        public IEnumerable<Product> GetAllProducts()
        {
           GetProducts();
           return products;
        }

        private void GetProducts()
        {
           products.Add(new Product {Id = 1, Name = "Television", Category="Electronic", Price=82000});
           products.Add(new Product { Id = 2, Name = "Refrigerator", Category = "Electronic", Price = 23000 });
           products.Add(new Product { Id = 3, Name = "Mobiles", Category = "Electronic", Price = 20000 });
           products.Add(new Product { Id = 4, Name = "Laptops", Category = "Electronic", Price = 45000 });
           products.Add(new Product { Id = 5, Name = "iPads", Category = "Electronic", Price = 67000 });
           products.Add(new Product { Id = 6, Name = "Toys", Category = "Gift Items", Price = 15000 });
        }

        public IEnumerable<Product> GetProducts(int selectedId)
        {
            if (products.Count() > 0)
            {
               return products.Where(p => p.Id == selectedId);
            }
            else
            {
                GetProducts();
                return products.Where(p => p.Id == selectedId);
            }
        }

Chạy project và thử truy cập API theo 2 url sau ta có thể thấy kết quả như mong đợi:

http://localhost:11715/api/products

http://localhost:11715/api/products?selectedID=2

Truyền những đối tượng phức tạp vào phương thức của Web API:

Hãy cùng thử 1 ví dụ nếu ta cần truyền 1 object tới phương thức của Web API như sau:

namespace WebApiProject.Controllers
{
    public class SampleController : ApiController
    {
        public string GetTime(Time t)
        {
            return string.Format("Received Time: {0}:{1}.{2}", t.Hour, t.Minute, t.Second);
        }
    }
    public class Time
    {
        public int Hour { get; set; }
        public int Minute { get; set; }
        public int Second { get; set; }
    }
}

Sau khi chạy thử project truyền vào các tham số Hour, Minute và Second. Ta có thể thấy nó sẽ trả về null exception.

<Error><Message>An error has occurred.</Message><ExceptionMessage>Object reference not set to an instance of an object.</ExceptionMessage><ExceptionType>System.NullReferenceException</ExceptionType><StackTrace>   at WebApiProject.Controllers.SampleController.GetTime(Time t) in c:\Users\Trungx\Documents\Visual Studio 2013\Projects\WebApiProject\WebApiProject\Controllers\SampleController.cs:line 14
   at lambda_method(Closure , Object , Object[] )
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__0.MoveNext()</StackTrace></Error>

Giờ hãy cùng xem sự thay đổi khi ta sửa lại code và thêm thuộc tính [FromUri] để xử lý object truyền vào từ query string:

  public string GetTime([FromUri] Time t)

Kết quả:

<string>Received Time: 10:25.12</string>

Làm việc với HttpClient API:

Ta có thể gọi đến Web API bằng nhiều cách trong đó có cách sử dụng lớp HttpClient có sẵn của ASP.NET, đoạn code dưới đây giới thiệu cách tạo 1 HttpClient object và dùng nó để truy cập bất đồng bộ đến phương thức API:


// asynchronous accessing of web api method
        async Task GetData()
        {
            StringBuilder result = new StringBuilder();
            // Define the httpClient object            using (HttpClient client = new HttpClient())
            {
                // Define the base address of the MVC application hosting the web api
                // accessing the web api from the same solution
                client.BaseAddress = new Uri(HttpContext.Current.Request.Url.AbsoluteUri);
                // Define the serialization used json/xml/ etc
                client.DefaultRequestHeaders.Accept.Add(
                    new MediaTypeWithQualityHeaderValue("application/json"));
                // Call the method
                HttpResponseMessage response = client.GetAsync("api/products").Result;
                if (response.IsSuccessStatusCode)
                {
                    // Convert the result into business object
                    var products = response.Content.ReadAsAsync<IEnumerable<Product>>().Result;
                    foreach (var p in products)
                    {
                        result.Append(string.Format("{0} --- [Price: {1} Category: {2}] ", p.Name, p.Price, p.Category));
                    }
                }
                else
                {
                    result.Append(string.Format("Error Code:-{0}  Error Details: {1}", (int)response.StatusCode, response.ReasonPhrase));
                }
            }
            data.Text = result.ToString();
        }

Truy cập Web API từ jQuery:

Việc truy cập Web API cũng rất dễ dàng với hàm getJSON:

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
    <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script type="text/javascript">
        $.getJSON("/api/products",
                       function (data) {
                        //Clear the div displaying the result
                           $("#productView").empty();
                          //Create a table and append the table body
                          var $table = $('<table border="2">');
                          var $tbody = $table.append('<tbody />').children('tbody');
                          //data - return value from the Web API method
                           for (var i = 0; i < data.length; i++) {
                                   //add a new row to the table
                               var $trow=$tbody.append('<tr />').children('tr:last');
                                   //add a new column to display Name
                               var $tcol = $trow.append("<td/>").children('td:last')
                                .append(data[i].Name);
                                   //add a new column to display Category
                              var $tcol = $trow.append("<td/>").children('td:last')
                                .append(data[i].Category);
                                 //add a new column to display Price
                               var $tcol = $trow.append("<td/>").children('td:last')
                                .append(data[i].Price);
                           }
                        //display the table in the div
                           $table.appendTo('#productView');
                       });
    </script>
</head>
<body>
    <div id="productView"></div>
</body>
</html>

Kết quả:

Television	Electronic	82000
Refrigerator	Electronic	23000
Mobiles	Electronic	20000
Laptops	Electronic	45000
iPads	Electronic	67000
Toys	Gift Items	15000

Để truyền tham số dùng jQuery ta thêm vào như sau:

 $.getJSON("api/products",
  { selectedId: '4' },
      function (data) {    ….});

Các phương thức Serialization:

Web API hỗ trợ nhiều phương thức serialization bao gồm: XML, JSON và MessagePack. Trong phần này chúng ta sẽ tìm hiểu cách thực thi 3 phương thức này và so sách cách thể hiện của chúng. Để làm việc đó trước hết hãy sửa project ví dụ của chúng ta trả về 1 lượng dữ liệu để chúng ta hiểu và so sánh hiệu suất của 3 phương thức 1 cách tốt hơn.

private void GetProducts()
        {
            for (int i = 0; i < 5000; i++)
            {
                products.Add(new Product { Id = i, Name = "Product -  "+i,
                    Category = "The ASP.NET and Visual Web Developer teams have released the ASP.NET and Web Tools 2012.2 update, which extends the existing ASP.NET runtime and adds new web tooling to Visual Studio 2012. Whether you use Web Forms, MVC, Web API, or any other ASP.NET technology, there is something cool in this update for you.",
                    Price = 1 });
            }
        }

Mặc định Web API dùng JSON là phương thức serialization mặc định. Để dùng XML serialization ta cần chỉnh sửa trong Global.asax thêm vào 2 dòng sau:

GlobalConfiguration.Configuration.Formatters.RemoveAt(0);
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;

Với MessagePack serialization:

Để dùng phương thức này trước hết ta phải cài đặt MessagePack tại địa chỉ: http://msgpack.org/. Sau khi cài đặt hãy thêm reference thư viện: MsgPack.dll. Tiếp đó tạo 1 MediaTypeFormatter sử dụng MessagePack để serialize.

public class MediaTypeFormatterCompatibleMessagePack : MediaTypeFormatter
    {
        private readonly string _mime = "application/x-msgpack";
        Func<Type, bool> IsAllowedType = (t) =>
            {
                if (!t.IsAbstract && !t.IsInterface && t != null && !t.IsNotPublic)
                    return true;
                if (typeof(IEnumerable).IsAssignableFrom(t))
                    return true;
                return false;
            };
        public MediaTypeFormatterCompatibleMessagePack()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue(_mime));
        }
        public override bool CanWriteType(Type type)
        {
            if (type == null)
                throw new ArgumentNullException("Type is null");
            return IsAllowedType(type);
        }
        public override Task WriteToStreamAsync(Type type, object value, System.IO.Stream stream, HttpContent content, TransportContext transportContext)
        {
            if (type == null)
                throw new ArgumentNullException("type is null");
            if (stream == null)
                throw new ArgumentNullException("Write stream is null");
            var tcs = new TaskCompletionSource<object>();
            if (type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type))
            {
                value = (value as IEnumerable<object>).ToList();
            }
            var serializer = MessagePackSerializer.Create<dynamic>();
            serializer.Pack(stream, value);
            tcs.SetResult(null);
            return tcs.Task;
        }
        public override Task<object> ReadFromStreamAsync(Type type,Stream stream, HttpContent content, IFormatterLogger formatterLogger)
        {
            var tcs = new TaskCompletionSource<object>();
            if (content.Headers != null && content.Headers.ContentLength == 0)
                    return null;
            try
            {
                var serializer = MessagePackSerializer.Create(type);
                object result;
                using (var mpUnpacker = Unpacker.Create(stream))
                {
                    mpUnpacker.Read();
                    result = serializer.UnpackFrom(mpUnpacker);
                }
                tcs.SetResult(result);
            }
            catch (Exception e)
            {
                if (formatterLogger == null) throw;
                formatterLogger.LogError(String.Empty, e.Message);
                tcs.SetResult(GetDefaultValueForType(type));
            }
            return tcs.Task;
        }
        public override bool CanReadType(Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type is null");
            return IsAllowedType(type);
        }
    }

Tiếp đó ta cấu hình trong Global.asax tương tự như với phương thức XML:

GlobalConfiguration.Configuration.Formatters.Clear();
 GlobalConfiguration.Configuration.Formatters.Add(new MediaTypeFormatterCompatibleMessagePack());

So sánh 3 phương thức ta thu được kết quả về khả năng nén dữ liệu: MessagePack > JSON > XML

WCF và Web API:

Tất nhiên WCF hỗ trợ rất nhiều giao thức trong đó có cả HTTP tuy nhiên để thực thi 1 service dựa trên HTTP bằng WCF khá khó khăn và phức tạp và không sử dụng được các tính năng chính của HTTP như Caching hay Status Code. Trong khi đó sử dụng WEB API ta có thể thực thi tất cả GET,POST,PUT,DELETE và đầy đủ các tính năng của HTTP 1 cách dễ dàng.