Web API是一个比较宽泛的概念。这里我们提到Web API特指ASP.NET Web API。
这篇文章中我们主要介绍Web API的主要功能以及与其他同类型框架的对比,最后通过一些相对复杂的实例展示如何通过Web API构建http服务,同时也展示了Visual Studio构建.net项目的各种强大。
官方定义如下,强调两个关键点,即可以对接各种客户端(浏览器,移动设备),构建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常规服务,而SignalR主要负责的是构建实时服务,例如股票,聊天室,在线游戏等实时性要求比较高的服务。
Web API最重要的是可以构建面向各种客户端的服务。另外与WCF REST Service不同在于,Web API利用Http协议的各个方面来表达服务(例如 URI/request response header/caching/versioning/content format),因此就省掉很多配置。
当你遇到以下这些情况的时候,就可以考虑使用Web API了。
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 与MVC很类似,他们有哪些不同之处呢?先上图,这就是他们最大的不同之处。
详细点说他们的区别,
发布服务在Web API和WCF之间该如何取舍呢?这里提供些简单地判断规则,
ASP.NET网站上有很多简单的Web API实例,看看贴图和实例代码你就明白怎么用了。这里我们通过一个稍微复杂一点的实例来展示下Web API的功能。
在我们的实例里面用到了:
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
在Project Template中选择Web API
然后项目就创建成了,Controllers里面有一个ValuesController,是自动生成的一个最简单的Web API Controller。
正如我们前面所说,里面引用的是System.Web.Http命名空间。
2. 创建model
在model里面添加Contact类
代码如下,其中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。
另外我们需要在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
选择Empty API controller,将Controller命名为ContactsController
添加如下代码,可以看到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库,
Knockoutjs通过MVVM模式来实现动态html绑定数据,如下图,其中View-Model是客户端的javascript object保存的model数据。
先打开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
选择Create strongly-typed view,在model class中选择Contact类。
添加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 6 |
<ul id="menu"> <li>@Html.ActionLink("Home", "Index", "Home", new { area = "" }, null)</li> <li>@Html.ActionLink("API", "Index", "Help", new { area = "" }, null)</li> <li>@Html.ActionLink("Admin", "Admin", "Home")</li> </ul> |
5. 测试与调试
大功告成,直接运行下我们的作品,我们的admin链接也显示在右上角,
Admin页面的样子,Contact list是动态加载进来的,可以通过这个页面做添加,修改,删除的操作。
通过IE network capture来查看请求内容,
重新加载页面,可以看到回复的格式为JSON,
JSON内容就是我们mock的一些数据。
接下来我们修改,删除,又添加了一条记录,可以看到使用了不同的http method。
通过前面安装的mongovue来查看下DB种的数据,先添加的user也在其中,令我感到欣慰。。。
其实还有两个有趣的实例,不过文章一写就长了,不好意思耽误大家时间,只好先放放,以后再写。