Web API 强势入门指南


Web API是一个比较宽泛的概念。这里我们提到Web API特指ASP.NET Web API。

这篇文章中我们主要介绍Web API的主要功能以及与其他同类型框架的对比,最后通过一些相对复杂的实例展示如何通过Web API构建http服务,同时也展示了Visual Studio构建.net项目的各种强大。

目录

什么是 Web API

官方定义如下,强调两个关键点,即可以对接各种客户端(浏览器,移动设备),构建http服务的框架。

ASP.NET Web API is a framework that makes it easy to build HTTP services that reach a broad range of clients, including browsers and mobile devices. ASP.NET Web API is an ideal platform for building RESTful applications on the .NET Framework.

Web API在ASP.NET完整框架中地位如下图,与SignalR一起同为构建Service的框架。Web API负责构建http常规服务,而SingalR主要负责的是构建实时服务,例如股票,聊天室,在线游戏等实时性要求比较高的服务。

Picture20

 

为什么要用 Web API

Web API最重要的是可以构建面向各种客户端的服务。另外与WCF REST Service不同在于,Web API利用Http协议的各个方面来表达服务(例如 URI/request response header/caching/versioning/content format),因此就省掉很多配置。

Picture2

 

当你遇到以下这些情况的时候,就可以考虑使用Web API了。

  • 需要Web Service但是不需要SOAP
  • 需要在已有的WCF服务基础上建立non-soap-based http服务
  • 只想发布一些简单的Http服务,不想使用相对复杂的WCF配置
  • 发布的服务可能会被带宽受限的设备访问
  • 希望使用开源框架,关键时候可以自己调试或者自定义一下框架

功能简介

Web API的主要功能

1. 支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, update, delete)操作

    通过不同的http动作表达不同的含义,这样就不需要暴露多个API来支持这些基本操作。

2. 请求的回复通过Http Status Code表达不同含义,并且客户端可以通过Accept header来与服务器协商格式,例如你希望服务器返回JSON格式还是XML格式。

3. 请求的回复格式支持 JSON,XML,并且可以扩展添加其他格式。

4. 原生支持OData

5. 支持Self-host或者IIS host。

6. 支持大多数MVC功能,例如Routing/Controller/Action Result/Filter/Model Builder/IOC Container/Dependency Injection。

Web API vs MVC

你可能会觉得Web API 与MVC很类似,他们有哪些不同之处呢?先上图,这就是他们最大的不同之处。

Picture1

详细点说他们的区别,

  • MVC主要用来构建网站,既关心数据也关心页面展示,而Web API只关注数据
  • Web API支持格式协商,客户端可以通过Accept header通知服务器期望的格式
  • Web API支持Self Host,MVC目前不支持
  • Web API通过不同的http verb表达不同的动作(CRUD),MVC则通过Action名字表达动作
  • Web API内建于ASP.NET System.Web.Http命名空间下,MVC位于System.Web.Mvc命名空间下,因此model binding/filter/routing等功能有所不同
  • 最后,Web API非常适合构建移动客户端服务

Web API vs WCF

发布服务在Web API和WCF之间该如何取舍呢?这里提供些简单地判断规则,

  • 如果服务需要支持One Way Messaging/Message Queue/Duplex Communication,选择WCF
  • 如果服务需要在TCP/Named Pipes/UDP (wcf 4.5),选择WCF
  • 如果服务需要在http协议上,并且希望利用http协议的各种功能,选择Web API
  • 如果服务需要被各种客户端(特别是移动客户端)调用,选择Web API

Web API 实战 (Web API + MongoDB + knockoutjs)

ASP.NET网站上有很多简单的Web API实例,看看贴图和实例代码你就明白怎么用了。这里我们通过一个稍微复杂一点的实例来展示下Web API的功能。

涉及技术

在我们的实例里面用到了:

服务URI Pattern

Action Http verb URI
Get contact list GET /api/contacts
Get filtered contacts GET /api/contacts?$top=2
Get contact by ID GET /api/contacts/id
Create new contact POST /api/contacts
Update a contact PUT /api/contacts/id
Delete a contact DELETE /api/contacts/id

准备工作

1. 下载并安装Mongo DB,步骤看这里

2. Mongo DB C# driver下载可以在nuget搜索mongocsharpdriver。

3. 如果想本地察看数据库中内容,下载MongoVUE

4. Knockoutjs下载可以在nuget搜索knockoutjs。

代码实现

1. 创建项目

创建MVC4 Web Application

1

在Project Template中选择Web API

2

然后项目就创建成了,Controllers里面有一个ValuesController,是自动生成的一个最简单的Web API Controller。

正如我们前面所说,里面引用的是System.Web.Http命名空间。

3

2. 创建model

在model里面添加Contact类

4

代码如下,其中BsonId需要mongocsharpdriver。

1
2
3
4
5
6
7
8
9
public class Contact
     {
         [BsonId]
         public string Id { get ; set ; }
         public string Name { get ; set ; }
         public string Phone { get ; set ; }
         public string Email { get ; set ; }
         public DateTime LastModified { get ; set ; }
     }

我们需要添加mongosharpdriver。

7

8

另外我们需要在Model中添加Repository,Controller通过该类来访问Mongo DB。

1
2
3
4
5
6
7
public interface IContactRepository {
         IEnumerable GetAllContacts();
         Contact GetContact( string id);
         Contact AddContact(Contact item);
         bool RemoveContact( string id);
         bool UpdateContact( string id, Contact item);  
     }

ContactRepository的完整实现如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class ContactRepository : IContactRepository
     {
         MongoServer _server = null ;
         MongoDatabase _database = null ;
         MongoCollection _contacts = null ;
 
         public ContactRepository( string connection)
         {
             if ( string .IsNullOrWhiteSpace(connection))
             {
                 connection = "mongodb://localhost:27017" ;
             }
 
             _server = new MongoClient(connection).GetServer();
             _database = _server.GetDatabase( "Contacts" );
             _contacts = _database.GetCollection( "contacts" );
 
             // Reset database and add some default entries
             _contacts.RemoveAll();
             for ( int index = 1; index < 5; index++)
             {
                 Contact contact1 = new Contact
                 {
                     Email = string .Format( "test{0}@example.com" , index),
                     Name = string .Format( "test{0}" , index),
                     Phone = string .Format( "{0}{0}{0} {0}{0}{0} {0}{0}{0}{0}" , index)
                 };
                 AddContact(contact1);
             }
         }
 
         public IEnumerable GetAllContacts()
         {
             return _contacts.FindAll();
         }
 
         public Contact GetContact( string id)
         {
             IMongoQuery query = Query.EQ( "_id" , id);
             return _contacts.Find(query).FirstOrDefault();
         }
 
         public Contact AddContact(Contact item)
         {
             item.Id = ObjectId.GenerateNewId().ToString();
             item.LastModified = DateTime.UtcNow;
             _contacts.Insert(item);
             return item;
         }
 
         public bool RemoveContact( string id)
         {
             IMongoQuery query = Query.EQ( "_id" , id);
             WriteConcernResult result = _contacts.Remove(query);
             return result.DocumentsAffected == 1;
         }
 
         public bool UpdateContact( string id, Contact item)
         {
             IMongoQuery query = Query.EQ( "_id" , id);
             item.LastModified = DateTime.UtcNow;
             IMongoUpdate update = Update
                 .Set( "Email" , item.Email)
                 .Set( "LastModified" , DateTime.UtcNow)
                 .Set( "Name" , item.Name)
                 .Set( "Phone" , item.Phone);
             WriteConcernResult result = _contacts.Update(query, update);
             return result.UpdatedExisting;
         }
     }

3. 添加Controller

右键Controllers目录选择添加Controller

5

选择Empty API controller,将Controller命名为ContactsController

6

添加如下代码,可以看到Controller中的API方法名就是以http verb命名的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class ContactsController : ApiController
     {
         private static readonly IContactRepository _contacts = new ContactRepository( string .Empty);
 
         public IQueryable Get()
         {
             return _contacts.GetAllContacts().AsQueryable();
         }
 
         public Contact Get( string id)
         {
             Contact contact = _contacts.GetContact(id);
             if (contact == null )
             {
                 throw new HttpResponseException(HttpStatusCode.NotFound);
             }
 
             return contact;
         }
 
         public Contact Post(Contact value)
         {
             Contact contact = _contacts.AddContact(value);
             return contact;
         }
 
         public void Put( string id, Contact value)
         {
             if (!_contacts.UpdateContact(id, value))
             {
                 throw new HttpResponseException(HttpStatusCode.NotFound);
             }
         }
 
         public void Delete( string id)
         {
             if (!_contacts.RemoveContact(id))
             {
                 throw new HttpResponseException(HttpStatusCode.NotFound);
             }
         }
     }

4. 添加View

首先添加Knockoutjs库,

9

Knockoutjs通过MVVM模式来实现动态html绑定数据,如下图,其中View-Model是客户端的javascript object保存的model数据。

webapi_ef16

先打开HomeController,里面添加一个新的Action代码如下,因为我们要在MVC中对于ContactsController添加对应的View。

1
2
3
4
5
6
7
public ActionResult Admin()
         {
             string apiUri = Url.HttpRouteUrl( "DefaultApi" , new { controller = "contacts" , });
             ViewBag.ApiUrl = new Uri(Request.Url, apiUri).AbsoluteUri.ToString();
 
             return View();
         }

然后右键Admin方法,选择添加View

10

选择Create strongly-typed view,在model class中选择Contact类。

11

添加View的完整代码,注意view中我们通过js去访问WebAPI,以及通过动态绑定将数据呈现在网页上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@model WebAPIDemo.Models.Contact
 
@{
     ViewBag.Title = "Admin";
}
 
@section Scripts {
   @Scripts.Render("~/bundles/jqueryval")
   < script type="text/javascript" src="@Url.Content("~/Scripts/knockout-2.3.0.js")"></ script >
   < script type="text/javascript">
       function ProductsViewModel() {
           var self = this;
           self.products = ko.observableArray();
 
           var baseUri = '@ViewBag.ApiUrl';
 
           self.create = function (formElement) {
               // If valid, post the serialized form data to the web api
               $(formElement).validate();
               if ($(formElement).valid()) {
                   $.post(baseUri, $(formElement).serialize(), null, "json")
                       .done(function (o) { self.products.push(o); });
               }
           }
 
           self.update = function (product) {
               $.ajax({ type: "PUT", url: baseUri + '/' + product.Id, data: product });
           }
 
           self.remove = function (product) {
               // First remove from the server, then from the UI
               $.ajax({ type: "DELETE", url: baseUri + '/' + product.Id })
                   .done(function () { self.products.remove(product); });
           }
 
           $.getJSON(baseUri, self.products);
       }
 
       $(document).ready(function () {
           ko.applyBindings(new ProductsViewModel());
       })
   </ script >
}
 
< h2 >Admin</ h2 >
< div class="content">
     < div class="float-left">
     < ul id="update-products" data-bind="foreach: products">
 
         < li >
             < div >
                 < div class="item">ID</ div > < span data-bind="text: $data.Id"></ span >
             </ div >
             < div >
                 < div class="item">Name</ div >
                 < input type="text" data-bind="value: $data.Name"/>
             </ div >
             < div >
                 < div class="item">Phone</ div >
                 < input type="text" data-bind="value: $data.Phone"/>
             </ div >
             < div >
                 < div class="item">Email</ div >
                 < input type="text" data-bind="value: $data.Email"/>
             </ div >
             < div >
                 < div class="item">Last Modified</ div > < span data-bind="text: $data.LastModified"></ span >
             </ div >
             < div >
                 < input type="button" value="Update" data-bind="click: $root.update"/>
                 < input type="button" value="Delete Item" data-bind="click: $root.remove"/>
             </ div >
         </ li >
     </ ul >
     </ div >
 
     < div class="float-right">
     < h2 >Add New Product</ h2 >
     < form id="addProduct" data-bind="submit: create">
         @Html.ValidationSummary(true)
         < fieldset >
             < legend >Contact</ legend >
             @Html.EditorForModel()
             < p >
                 < input type="submit" value="Save" />
             </ p >
         </ fieldset >
     </ form >
     </ div >
</ div >

接下来在_layout.cshtml中添加一个admin页面的链接如下

1
2
3
4
5