Web API 最佳入门指南

博客园

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

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

什么是 Web API
为什么要用 Web API
功能简介
Web API vs MVC
Web API vs WCF
Web API 实战 (Web API + MongoDB + knockoutjs)

什么是 Web API

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

ASP.NET Web API is a framework that makes it easy to build HTTPservices 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 responseheader/caching/versioning/content format),因此就省掉很多配置。

Picture2

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

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

功能简介

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

详细点说他们的区别,

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

Web API vs WCF

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

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

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

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

涉及技术

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

  1. Mongo DB数据库保存数据 (NoSQL, Document Store,跨平台,跨语言)
  2. Web API提供数据服务
  3. MVC作数据展示
  4. Knockoutjs动态绑定客户端数据,这里有一个简单的介绍

服务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。

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。

public interface IContactRepository { 
        IEnumerable GetAllContacts(); 
        Contact GetContact(string id); 
        Contact AddContact(Contact item); 
        bool RemoveContact(string id); 
        bool UpdateContact(string id, Contact item);   
}

ContactRepository的完整实现如下,

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命名的。

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。

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,以及通过动态绑定将数据呈现在网页上。

@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页面的链接如下

<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链接也显示在右上角,

12

Admin页面的样子,Contact list是动态加载进来的,可以通过这个页面做添加,修改,删除的操作。

13

通过IE network capture来查看请求内容,

重新加载页面,可以看到回复的格式为JSON,

14

JSON内容就是我们mock的一些数据。

image

接下来我们修改,删除,又添加了一条记录,可以看到使用了不同的http method。

image

通过前面安装的mongovue来查看下DB种的数据,先添加的user也在其中,令我感到欣慰。。。

image

其实还有两个有趣的实例,不过文章一写就长了,不好意思耽误大家时间,只好先放放,以后再写。

WebAPI 入门

分享到:
评论加载中,请稍后...
创APP如搭积木 - 创意无限,梦想即时!
回到顶部