ASP.NET Web API详解


在这篇文章中我们将讨论Web API设计、概念、功能,和对比Web API与WCF。

1. 实现一个Web API项目

我们从一个简单的实例开始讨论。我们使用 Visual Studio 2012做为开发环境。我们第一步是基于Web API模版创建一个ASP.NET MVC 4.0项目,如图1所示:
图1:基于Web API模板创建一个ASP.NET MVC 4项目

下一步,我们在model文件夹创建一个简单的数据模型。在解决方案资源文件夹中右键点击Model文件夹,选择 Add -> Class,如图2所示:

图2:添加新的数据模型

这只是一个教程,我定义一个产品数据模型,如表1所示。
public class Product
{
public int Id
{ get; set; }
public string Name
{ get; set; }
public string Category
{ get; set; }
public decimal Price
{ get; set; }}

表1: 定义产品模型
创建产品数据模型后,我们可以在Controllers文件夹中创建一个新的API控制器,用来处理产品数据模型。默认情况下,一个基于Web API模版的MVC项目可以添加两个控制器: 一个继承于Controller类,另一个继承于ApiController类。在解决方案资源文件夹中右击Controllers文件夹,在弹出窗口中选择“Empty API controller”,添加一个新的controller。如图3所示:
图3:添加Empty API controller
Controller代码如表2所示:
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);
            }
        }


表2:添加ApiController类到实例项目

运行项目并连接到API,通过“/api/Products”,例如: http://localhost:8747/api/Products 。你也可以传递 SelectedId 参数去调用GetProducts方法,例如: http://localhost:8747/api/Products?SelectedId=2

2. 传递复杂的对象给Web API的方法

在Web API中传递一个简单的对象是容易的,但是大部分情况下,你需要传递一个复杂的对象参数。你可以通过[FromUrl]或[FromBody]属性来传递对象。[FromBody]属性从request body里面读取数据。但是,这个属性在方法的参数列表中只能使用一次。

让我们用代码段去解释使用复杂参数的重要性,如表3所示:
public class SampleController : ApiController
    {
        /// <summary>
        /// Get time
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        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; }
    }


表3: 给方法 传递复杂的参数

现在,让我们使用Hour,Minute和Second参数给GetTime方法传值。你可以看见它出错了,返回空引用错误。如图4所示:

图4:当调用GetTime方法时出错了

虽然我们已经意识到输入值需要关联到Time对象,但是API方法不能 正确地 映射输入值与对象属性。现在我们看看,是否修改代码去包含[FromUri]属性能够处理URL查询字符串里的复杂对象。下面的片段中我们在Time对象前申明了[FromUri]属性:
  public string GetTime([FromUri] Time t)
         { -------------------}

现在我们通过这个方法,传递相同的值,将会有不同的结果,如图5所示:


图5:调用GetTime方法成功

3. 使用HttpClient API
HttpClient是一个可扩展API,可以通过Http方式访问任何服务或网站。这个HttpClient被引进到WCF Web API, 而且现在 .NET Framework 4.5中的 ASP.NET Web API也有了这个功能。你可以在后台和services(例如:WCF)中使用HttpClient去访问Web API方法。

这个代码段创建了一个HttpClient对象并且异步访问实例中的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();
        }

表4:创建一个HttpClient对象去访问实例中的API方法

4. 用jQuery访问Web API
在Html5 Web应用中,你可以使用jQuery直接访问Web API。你可以使用getJSON()方法调用API得到结果,然后取出业务对象。jQuery能识别内部字段关联的业务对像,例如:data[i].Price。

这个代码段演示了从Product API方法查找数据并显示数据到一个表格。
<head>
    <title></title>
    <script src="Scripts/jquery-1.8.2.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>

表5:使用jQuery从Product API方法查找数据

5. 在jQuery中传递参数
另外,从Web API方法查找数据,你可以使用jQuery去传递参数。jQuery支持多个方法传递参数。第一个方法是使用单个的参数。下面的代码段演示了如何去传递单个参数:
 $.getJSON("api/products",
  { selectedId: '4' },
      function (data) {    ….});

你也可以在一个集合中传递多个参数,示范如下:
$.getJSON("api/products",
           { selectedId: '4', name: 'TV' },
                       function (data) {    ….});


另外一个方法,你可以直接把参数放在URL中,示范如下:
$.getJSON("api/products?selectedId =4",
                       function (data)  {    ….});


第一个方法从URL中分开了查询字符和参数,这个适用于复杂的或多个数据传递。第二个方法更适用于一个或两个参数的情况。


6. Web API代码框架
ASP.NET Scaffolding是一个ASP.NET的Web应用代码生成框架。Visual Studio包含预安装代码生成器,用于MVC和Web API项目。Visual Studio自动生成需要的API代码在指定的数据模型和数据源中执行多种操作。


基于代码框架的Entity Framework
验证Web API项目中代码框架如何工作,我们可以使用Entify Framework去定义我们的数据库并且生成Web API方法。首先,我们需要添加一个ADO.NET entity数据模型,用来定义数据。我们的实例数据将基于两张表,Couse和Status,如图6所示:

图6:基于Course和Status表添加一个entity数据模型

定义数据模型后,我们可以生成与数据模型一致的Web API controller。右击Controller,然后点击Add Controller,将会弹出Add Controller窗口。在Controler name的输入框,输入Controller name。从Template下拉列表中,选择API controller with read/write actions, using Entity Framework模板,如图7所示:

图7:基于数据模型添加一个Controller

下一步,从Model class下拉列表中选择一个表,在Data context class下拉列表中指定数据context。表6展示的是CourseController API controller自动生成的代码。它包含了get,put,post,和delete方法,这些方法和model一致。

public class CourseController : ApiController
{
   private SampleDBEntities db = new SampleDBEntities();
   // GET api/Course
   public IEnumerable<Course> GetCourses()
   {
     return db.Courses.AsEnumerable();
   }
// GET api/Course/5
     public Course GetCourse(int id)
    {
       Course course = db.Courses.Single(c => c.CourseId == id);
       if (course == null)
        {
      throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
        }
    return course;
}
// PUT api/Course/5
 public HttpResponseMessage PutCourse(int id, Course course)
 {
    if (!ModelState.IsValid)
   {
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
if (id != course.CourseId)
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
db.Courses.Attach(course);
db.ObjectStateManager.ChangeObjectState(course, EntityState.Modified);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
// POST api/Course
public HttpResponseMessage PostCourse(Course course)
{
if (ModelState.IsValid)
{
db.Courses.AddObject(course);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, course); 
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = course.CourseId }));
return response;
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
// DELETE api/Course/5
public HttpResponseMessage DeleteCourse(int id)
{
Course course = db.Courses.Single(c => c.CourseId == id);
if (course == null) {
return Request.CreateResponse(HttpStatusCode.NotFound);
}
db.Courses.DeleteObject(course);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
}
return Request.CreateResponse(HttpStatusCode.OK, course);
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}

表6:CourseController API controller生成的代码

Web API代码框架功能减少了开发人员对常用增删改查功能的操作步聚。

7. ASP.NET Web API路由规则

Web API使用统一的路由选择器(URIs)实现多种行为。在解决方案资源文件夹的App_Start文件夹有一个WebApiConfig文件,它为Web API定义了不同的路由机制。这个机制基于http方法、动作和属性。但是,我们可以定义我们自己的路由机制来支持自定义路径。

例如,在我们的Web API项目实例中,我们使用/api/Products去接收产品详情。但是,我们想用api/Products/GetAllProducts替换,可以修改默认的路由逻辑,需要在URI中包含{action},代码片断如下:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

更新规则后,我们可以在Web API方法上使用ActionName属性去指定action名字,代码片断如下:
[ActionName("GetAllProducts")]
public IEnumerable<Product> GetAllProducts()
{……………………………..} 

注意:使用[NonAction]属性可以指明Web API方法不是一个API action方法。

我们可以使用一个Http方法或Acceptverbs属性去指定用何种HTTP动作连接Web API方法。下面的代码片断使用了HttpGet和HttpPost方法:
[HttpGet]
public IEnumerable<Product> GetProducts(int selectedId)
{………….. }
[HttpPost]
public void AddProduct(Product p)
{………….. }  


另一种方式,使用Acceptsverbs属性,如下代码断:
[AcceptVerbs("GET","PUT")]
public IEnumerable<Product> GetProducts(int selectedId)
{………….. } 

Web API 2提倡尽可能使用属性。更多详情请查看文件: Attribute Routing in Web API v2

8. 序列化

Web API支持很多序列化方法,包括XML,JSON,和MessagePack。在下面的章节,我们将研究下如何去执行这三个方法,并比较他们。在学习之前,我们修改Web API项目的实例代码去返回大块的数据,这样我们可以更好的理解和比较序列化的作用。表7展示如何用GetProducts()方法返回大块数据。
    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 });
            }
        }

表7:更新GetProducts()方法

9. 使用JSON序列化

Web API使用JSON做为默认的序列化方法。这样,当你运行Web API项目,自动返回为JSON格式。例如,接收Products详情,你只需要使用 http://<;<server>>:<<port>>/api/Products去得到JSON格式的结果。

使用XML序列化
在Web API项目中使用XML序列化,你必须修改Global.aspx文件在Application_Start()方法插入下面两行代码:
GlobalConfiguration.Configuration.Formatters.RemoveAt(0);
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;

执行XML序列化方法后,你可以使用 http://<;<server>>:<<port>>/api/Products查看详情。

使用MessagePack序列化
要使用 MessagePack方法,你必须先安装 MessagePack(http://msgpack.org/)。 MessagePack可通过Nuget添加和管理。你也可以从GitHub下载源码,并在Visual Studio中生成应用。

当你安装了MessagePack,把MsgPack.dll引用到MessagePack library。下一步,创建一个自定义的媒体类型格式,这个格式用来序列化Web API数据。表8是GitHub上展示的媒体类型格式 ( https://gist.github.com/filipw/3693339 )
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);
        }
    }

表8: 创建一个MessagePack媒体类型格式。

创建媒体类型格式后,你必须更新Global.asax。 Application_Start()方法插入下面两行代码:
GlobalConfiguration.Configuration.Formatters.Clear();
 GlobalConfiguration.Configuration.Formatters.Add(new MediaTypeFormatterCompatibleMessagePack());

比较序列化方法:

你可以使用Fiddler工具测试Web API中的JSON,XML,和MessagePack序列化,选择Request Builder标签。 Request Builder中 输入MessagePack Web API地址,并且在Request Header添加application/x-msgpack的Content-Type,如图8:

图8:Fiddler工具中的Request Builder

现在,点击Execute按钮去调用Web API方法。重复以上步骤去添加基于JSON和XML的Web API方法,不需要设置Content-Type。对比结果,如图9:

图9:在Fiddler方法中对比序列化方法

如上图所示,结果是messagepack压缩最小。JSON的尺寸要比XML小。

10. 从XML文档中生成帮助页面

默认情况下,Web API提供帮助页面,叙述项目中API方法的定义。根据方法定义中的XML文档,我们可以生成帮助页面。

创建帮助页面,打开资源管理器中的Areas,并打开HelpPageConfig.cs文件,如图10所示:
图10:在Web API项目实例中打开HelpPageConfig.cs

在HelpPageConfig.cs中,取消注释语句:
config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml"))); 


下一步,在项目属性的Build章节的XML documentation file选项中选择启用XML documentation,复制config文件中指定的文件名到XML documentation filet选项,如图11所示。

图11:在Web API project项目中启用XML docmentation file

现在我们在API controller实例中添加XML文档,如表9所示。只需要每个方法定义前面指定”///“,得到这个方法默认的XML文档。进一步修改注解内容。

 /// <summary>
        /// Get All products for our Sample
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Product> GetAllProducts()
        {
                 ---------------
        }
 
        /// <summary>
        /// Return the details of selected product depends on the product id passed.
        /// </summary>
        /// <param name="selectedId"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public IEnumerable<Product> GetProducts(int selectedId)
        {
      
                 ------------------
        }

表 9: 在API controller实例中添加XML文档

一旦你更新完controller,运行应用并点击屏幕右上方的API链接,如图12:

图12:查看应用窗口中的API链接


注意XML文档注释已经添加到Controller,如图13。

图13:在Web API帮助文件中显示注释

11. 对比WCF与Web API

WCF支持多种协义和messaging格式。在Web API中,我们只可以创建基于HTTP的服务。显然,WCF也可以创建HTTP服务,但是他里面隐藏了个别的抽象层。另外,WCF的执行要比Web API复杂。WCF中的HTTP执行使用的是Post方式,并且我们不能使用HTTP协义相关的主要功能,例如缓存和HTTP状态代码。使用Web API,我们可以执行所有的动作,如Get,Post,和Put,并且我们可以使用HTTP协义的多种功能。

WCF需要复杂的生成和地址操作,还有配置,但是Web API简单并直接的使用。Web API也是创建RESTfull服务的较好选择。通过这篇文章你能看到WCF和Web API的详细对比:WCF and ASP.NET Web API. 

更新1: 

使用jQuery传递复杂的数据类型给Web API方法,可以用JSON.Stringify。

例如:下面的代码代显示你可以传递自定义数据”user“给Web API方法

 var user = {};<br />    user.name = 'Mathew';<br />    user.job = 'ITA';<br />    user.rating = 'Good';

$.getJSON("api/getEmployeeDetail",<br />      {JSON.stringify(user)},<br />                       function (data) { ......}<br /> ); 




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值