[.NET] Hướng dẫn tạo và sử dụng WebAPI cho ASP.NET MVC
Consuming a Web API Asynchronously in ASP.NET MVC or WPF
Abstract: The HTTP Client class provides useful methods
to call an ASP.NET Web API service asynchronously from the managed clients like
WPF or ASP.NET MVC. We will explore this class with some examples.
One of the benefits of
using ASP.NET Web API over WCF services is that the client does need to consume
a proxy in order to make a call to Web API service. This facilitates decoupling
between the client and the Web API service.
Web APIs are very useful
when we use client-side JavaScript frameworks or libraries to make an Ajax call
to the Web API. But the scope of Web API is not only limited to JavaScript Ajax
calls. Sometimes they can be called from managed clients too e.g. Desktop
Applications created using WPF or even in an ASP.NET client or ASP.NET MVC
client applications. In this case, we need to make sure that calls to Web API
can be handled asynchronously.
The following diagram
explains the architecture of calling Web API using a Client Application.
The above diagram
explains the basic arrangement of a Web API Service and the client application.
The API Service communicates with Database for writing and reading data and
uses ADO.NET EntityFramework to communicate with the database. The Data Access
Repository, decouples the ADO.NET EntityFramework from the ApiController class,
using the simple implementation of the repository pattern, implemented using
Unity.WebApi framework. The WEB API Controller class calls the Data Access
Repository for performing Read/Write operations. The Managed Client
Application, uses HttpClient class for performing an asynchronous calls to the
Web API.
HttpClient
This is the base class
used for making an asynchronous calls to the remote service repository using
its URL. This class is present under the System.Net.Http namespace
and provide methods like GetAsync (), PostAsync (), PutAsync () and DeleteAsync
().
The Implementation
Step 1: Open the free Visual Studio 2013 Community Edition and create a Blank solution, name this
solution as ‘MVC_ClientWebAPI’. In this solution, add a new ASP.NET Web API
Project of the name APIService and click OK.
This will create a Web
API project with the necessary folders for Models, Controllers, App_Start,
App_Data, etc.
Step 2: In the App_Data folder, add a new Sql Server
database of name ‘Application.mdf’. In this database add the EmployeeInfo table
using the following script.
CREATE TABLE [dbo].[EmployeeInfo] (
[EmpNo] INT IDENTITY (1, 1) NOT
NULL,
[EmpName] VARCHAR (50) NOT NULL,
[DeptName] VARCHAR (50) NOT NULL,
[Designation] VARCHAR
(50) NOT NULL,
[Salary] DECIMAL (18) NOT NULL,
PRIMARY KEY CLUSTERED
([EmpNo] ASC)
);
|
In this table add the
sample data as shown in the following script
INSERT INTO [dbo].[EmployeeInfo]
([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (1, N'MS', N'MS IT Services',
N'Director', CAST(780000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo]
([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (2, N'LS', N'Administration',
N'Manager', CAST(1556000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo]
([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (3, N'TS', N'Administration',
N'Dy. Manager', CAST(45000 AS Decimal(18, 0)))
|
Step 3: In the Models folder add a new ADO.NET EF.
Name this EF as DataAccess. This will start a Wizard. In this
wizard, select the EF Designer from Database, (Note: Since the
article uses VS2013 with Update 4, the EF wizard shows EF Designer from
Database, else the option may be Generate from Database).
The wizard selects Application.mdf, as shown in the following
image:
In the next step of
the wizard, select EmployeeInfo table as shown in the following image:
After completion of
this wizard, the table mapping will be generated. This creates EmployeeInfo
class and ApplicationEntities class in the Models folder.
Build the project.
Step 4: Since we will be using a simple Repository
pattern to decouple the Web API Controller class from the ADO.NET EF, we need
Unity framework for dependency injection for the Web API using constructor
injection. To implement this, in the project add Unity.WebApi.5.1 (or
whatever version is latest) NuGet package using the Package Manager
window. To implement this, right-click on the project and select Manage
NuGet Packages. From the NuGet Packages window, select
Unity.WebApi.5.1 as shown in the following image:
Click on the install button.
This will add Unity.WebApi reference in the project
references. The App_Start folder will be added with the UnityConfig.cs file
with UnityConfig class in it. We will modify this class in the
coming steps.
Step 5: We need to create a separate class to
implement the data access logic. In the project, add a new folder of name ‘DataAccessRepository’.
In this folder, add a new interface file with the code as shown as following:
public interface IDataAccessRepository<TEntity, in TPrimaryKey> where TEntity :
class
{
IEnumerable<TEntity> Get();
TEntity Get(TPrimaryKey id);
void Post(TEntity
entity);
void Put(TPrimaryKey
id,TEntity entity);
void Delete(TPrimaryKey
id);
}
|
This generic interface
contains methods for Get, Post, Put and Delete operations. This interface is
typed to the following parameters:
·
TEntity: This is the
class type, represents the entity class used during Read/Write operations.
·
TPrimaryKey: This is
an input parameter used during Reading of specific record and deleting it.
Step 5: In the DataAccessRepository folder,
add a new class of clsDataAccessRepository with the following
code:
using System.Collections.Generic;
using System.Linq;
using APIService.Models;
using Microsoft.Practices.Unity;
namespace APIService.DataAccessRepository
{
public class clsDataAccessRepository :
IDataAccessRepository<EmployeeInfo, int>
{
//The dendency for the DbContext specified the current
class.
[Dependency]
public ApplicationEntities ctx { get; set; }
//Get all Employees
public IEnumerable<EmployeeInfo> Get()
{
return ctx.EmployeeInfoes.ToList();
}
//Get Specific Employee based on Id
public EmployeeInfo Get(int
id)
{
return ctx.EmployeeInfoes.Find(id);
}
//Create a new Employee
public void Post(EmployeeInfo
entity)
{
ctx.EmployeeInfoes.Add(entity);
ctx.SaveChanges();
}
//Update Exisitng Employee
public void Put(int id, EmployeeInfo entity)
{
var Emp = ctx.EmployeeInfoes.Find(id);
if (Emp
!= null)
{
Emp.EmpName = entity.EmpName;
Emp.Salary = entity.Salary;
Emp.DeptName = entity.DeptName;
Emp.Designation = entity.Designation;
ctx.SaveChanges();
}
}
//Delete an Employee based on Id
public void Delete(int id)
{
var Emp = ctx.EmployeeInfoes.Find(id);
if (Emp
!= null)
{
ctx.EmployeeInfoes.Remove(Emp);
ctx.SaveChanges();
}
}
}
}
|
The above class code
is specified with the ApplicationEntities DbContext injection
using [Dependency] attribute on it. This eliminates need of creating instance
using new keyword. The class contains methods for
performing Read/Write operations using ADO.NET EF methods.
Step 6: To add the above code class in the Unity
Container, we need to change the UnityConfig class from the App_Start folder as
shown in the following code:
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new
UnityContainer();
container.RegisterType<IDataAccessRepository<EmployeeInfo,int>,
clsDataAccessRepository>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
|
The RegisterType
() method accepts the interface and class for registering in the Unity
container. The UnityDependencyResolver wraps the container object
of the type UnityContainer in it. This resolves the
dependencies registered inside the container. In our case, we have registered
IDataAccessRepository interface and clsDataAccessRepository class.
Open Global.asax, and
add the following line in it:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
UnityConfig.RegisterComponents();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
|
The above line of code
calls the RegisterComponents () method in the Application_Start event so that
all dependency must be registered in the beginning of the application.
Step 7: In the Controllers,add a new Empty Web API
Controller of the name EmployeeInfoAPIController. In this controller, add the
following code:
using System.Collections.Generic;
using System.Net;
using System.Web.Http;
using APIService.Models;
using APIService.DataAccessRepository;
using System.Web.Http.Description;
namespace APIService.Controllers
{
public class EmployeeInfoAPIController : ApiController
{
private IDataAccessRepository<EmployeeInfo, int> _repository;
//Inject the DataAccessRepository using Construction
Injection
public EmployeeInfoAPIController(IDataAccessRepository<EmployeeInfo,
int> r)
{
_repository = r;
}
public IEnumerable<EmployeeInfo> Get()
{
return _repository.Get();
}
[ResponseType(typeof(EmployeeInfo))]
public IHttpActionResult Get(int
id)
{
return Ok (_repository.Get(id));
}
[ResponseType(typeof(EmployeeInfo))]
public IHttpActionResult Post(EmployeeInfo emp)
{
_repository.Post(emp);
return Ok(emp);
}
[ResponseType(typeof(void))]
public IHttpActionResult Put(int
id ,EmployeeInfo emp)
{
_repository.Put(id,emp);
return StatusCode(HttpStatusCode.NoContent);
}
[ResponseType(typeof(void))]
public IHttpActionResult Delete(int
id)
{
_repository.Delete(id);
return StatusCode(HttpStatusCode.NoContent);
}
[ResponseType(typeof(void))]
public IHttpActionResult Details(int id)
{
_repository.Delete(id);
return StatusCode(HttpStatusCode.NoContent);
}
}
}
|
In the above
controller, the clsDataAccessRepository object is injected using Constructor
Injection. The controller contains methods for performing Http GET, POST, PUT
and DELETE operations.
Implementing the ASP.NET MVC Client Applciation
Step 1: In the same solution, add a new ASP.NET MVC
application of name ‘MVC_Client’. You can also select Empty MVC application. If
you select an empty mvc app, then in that case reference for Json.NET must
be added explicitly using NuGet Package Manager. This is for JSON Serialization
and Deserialization of the Entity object during Read/Write operation. To make
Http calls using C#, we need to add references for System.Net Http.
Note: The NuGet Package for Json.Net and
references for System.Net.Http is not required if an ASP.NET MVC ready template
is chosen.
Step 2: In this project in the Models folder add the
following EmployeeInfo class.
public class EmployeeInfo
{
public int EmpNo
{ get; set; }
public string EmpName { get; set; }
public decimal Salary { get; set; }
public string DeptName { get; set; }
public string Designation { get; set; }
}
|
This is the same class
available on the API Service side. We need this for the Serialization and
Deserialization during Read/Write operations.
Step 3: In the Controllers folder, add a new Empty MVC
Controller of the name EmployeeInfoController. In this controller, add the
following code:
using Newtonsoft.Json;
namespace MVC_Client.Controllers
{
public class EmployeeInfoController : Controller
{
HttpClient client;
//The URL of the WEB API Service
string url =
"http://localhost:60143/api/EmployeeInfoAPI";
//The HttpClient Class, this will be used for performing
//HTTP Operations, GET, POST, PUT, DELETE
//Set the base address and the Header Formatter
public EmployeeInfoController()
{
client = new
HttpClient();
client.BaseAddress = new
Uri(url);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
// GET: EmployeeInfo
public async Task<ActionResult> Index()
{
HttpResponseMessage responseMessage = await
client.GetAsync(url);
if (responseMessage.IsSuccessStatusCode)
{
var responseData = responseMessage.Content.ReadAsStringAsync().Result
;
var Employees =
JsonConvert.DeserializeObject<List<EmployeeInfo>>(responseData);
return View(Employees);
}
return View("Error");
}
public ActionResult Create()
{
return View(new EmployeeInfo());
}
//The Post method
[HttpPost]
public async Task<ActionResult> Create(EmployeeInfo Emp)
{
HttpResponseMessage responseMessage = await
client.PostAsJsonAsync(url,Emp);
if (responseMessage.IsSuccessStatusCode)
{
return
RedirectToAction("Index");
}
return RedirectToAction("Error");
}
public async Task<ActionResult> Edit(int id)
{
HttpResponseMessage responseMessage = await
client.GetAsync(url+"/"+id);
if (responseMessage.IsSuccessStatusCode)
{
var responseData = responseMessage.Content.ReadAsStringAsync().Result;
var Employee =
JsonConvert.DeserializeObject<EmployeeInfo>(responseData);
return View(Employee);
}
return View("Error");
}
//The PUT Method
[HttpPost]
public async Task<ActionResult> Edit(int id,EmployeeInfo Emp)
{
HttpResponseMessage responseMessage = await
client.PutAsJsonAsync(url+"/"
+id, Emp);
if (responseMessage.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
return RedirectToAction("Error");
}
public async Task<ActionResult> Delete(int id)
{
HttpResponseMessage responseMessage = await client.GetAsync(url
+ "/" +
id);
if (responseMessage.IsSuccessStatusCode)
{
var responseData =
responseMessage.Content.ReadAsStringAsync().Result;
var Employee = JsonConvert.DeserializeObject<EmployeeInfo>(responseData);
return View(Employee);
}
return View("Error");
}
//The DELETE method
[HttpPost]
public async Task<ActionResult> Delete(int id, EmployeeInfo Emp)
{
HttpResponseMessage responseMessage =await
client.DeleteAsync(url + "/"
+ id);
if (responseMessage.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
return RedirectToAction("Error");
}
//The GET method
public async Task<ActionResult> Details(int id)
{
HttpResponseMessage responseMessage = await client.GetAsync(url + "/" + id);
if (responseMessage.IsSuccessStatusCode)
{
var responseData =
responseMessage.Content.ReadAsStringAsync().Result;
var Employee =
JsonConvert.DeserializeObject<Employee>(responseData);
return View(Employee);
}
return View("Error");
}
}
}
|
The above controller
class has the following features:
·
The constructor of the
class instantiates the HttpClient class. This instance is set
with the properties like BaseAddress, this represents the
remote Web API URL with which the Read/Write operations are performed.
The DefaultRequestHeaders is set to accept the media type
formatter for json data.
·
The Index
() method uses the GetAsyc() method to receive the
HttpResponseMessage. Since the response message contains the Json string
contents, this content can be read using ReadAsStringAsync() function.
The string contents are DeSerialized to the List of Employees using
DeserializeObject () method of JsonConvert class. Since
this call is asynchronous, the Index() method returns Task<T> object
where T is ActionResult.
·
Create() function with
HttpPost uses PostJsonAsAsync() method to make the
asynchronous post of the EmployeeInfo object to the Web API Service.
·
Similarly Edit()
method uses PutAsJsonAsync() method and Delete method uses DeleteAsync()
methods for performing respective operations using Web API.
Step 4: To generate UI for the application, scaffold
views from the Index, Create, Edit, Delete (all HttpGet) methods. This adds
Index.cshtml, Create.cshtml, Edit.cshtml and Delete.cshtml in the EmployeeInfo
folder of the Views folder.
Step 5: To run the Index.cshtml, change the
RouteConfig class from RouteConfig.cs file of the App_Start folder.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection
routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new
{ controller = "EmployeeInfo", action =
"Index", id = UrlParameter.Optional }
);
}
}
|
Step 6: To run the API Service and MVC Client project,
right-click on the solution and set the multiple startup project as shown in
the following image:
Run the application,
the Index view of the Employee will be displayed as shown below:
Click on the Create
New link, and the Create View will be displayed. Enter the Employee data in it
as shown in the following image:
Click on the ‘Create’
button, the control will be redirected to the Index view with the record added
as shown in the following image.
Conclusion: We saw how the HTTPClient class provides
useful methods to call an ASP.NET Web API service asynchronously from the
managed clients like WPF or ASP.NET MVC.









Nhận xét
Đăng nhận xét