Nghiên cứu Web API với ASP.NET MVC4
Bài đăng này đã không được cập nhật trong 9 năm
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.
All rights reserved