接下來進入的是俺在ASP.NET學習中最重要的WebAPI部分,在現在流行的互聯網場景下,WebAPI可以和HTML5、單頁應用程序SPA等技術和理念很好的結合在一起。所謂ASP.NET WebAPI,其核心概念就是構建REST風格的Web服務,把一起數據視為資源,無論是服務請求或者是數據操作,與以前的SOAP和XML-RPC架構風格有很大不同。說道這,很多讀者可能想到WCF中不是早都有了REST風格的服務么,為什么還需要這個WebAPI?確實如此,不過WCF中的該類型服務顯得比較復雜,因為其通信管道的構成由於集成了多種不同的通信協議,自然的其基礎程序集就顯得非常的龐大臃腫。
簡單來說,WebAPI就是簡單高效,"你值得擁有"!讓我們通過臨摹蔣老師的例子對它有個初步的了解,后端代碼如下:

1 public class ContactsController : ApiController 2 { 3 private static IList<Contact> contacts = new List<Contact> 4 { 5 new Contact{ 6 Id="001", 7 Name="Xixi", 8 PhoneNo="12132432", 9 EmailAddress="xixi@gmail.com" 10 }, 11 new Contact{ 12 Id="002", 13 Name="XiongEr", 14 PhoneNo="312", 15 EmailAddress="XiongEr@gmail.com" 16 } 17 }; 18 public IEnumerable<Contact> Get() 19 { 20 return contacts; 21 } 22 public Contact Get(string id) 23 { 24 return contacts.FirstOrDefault(c => c.Id == id); 25 } 26 public void Put(Contact contact) 27 { 28 contact.Id = Guid.NewGuid().ToString(); 29 contacts.Add(contact); 30 } 31 public void Post(Contact contact) 32 { 33 Delete(contact.Id); 34 contacts.Add(contact); 35 } 36 public void Delete(string id) 37 { 38 Contact tempContact = contacts.FirstOrDefault(c => c.Id == id); 39 contacts.Remove(tempContact); 40 } 41 }
前端代碼如下:

1 <html> 2 <head> 3 <title>聯系人管理</title> 4 <script type="text/javascript" src="~/Scripts/jquery-1.8.2.js"></script> 5 <script type="text/javascript" src="~/Scripts/knockout-2.2.0.js"></script> 6 </head> 7 <body> 8 <div id="contacts"> 9 <table> 10 <tr> 11 <th>姓名</th> 12 <th>電話號碼</th> 13 <th>Email地址</th> 14 <th></th> 15 </tr> 16 <tbody> 17 <!-- ko foreach: allContacts --> 18 <tr> 19 <td data-bind="text: Name" /> 20 <td data-bind="text: PhoneNo" /> 21 <td> 22 <input type="text" class="textbox long" data-bind="value: EmailAddress" /> 23 </td> 24 <td> 25 <a href="#" data-bind="click: $root.updateContact">修改</a> 26 <a href="#" data-bind="click: $root.deleteContact">刪除</a> 27 </td> 28 </tr> 29 <!-- /ko --> 30 <tr data-bind="with: addedContact"> 31 <td> 32 <input type="text" class="textbox" data-bind="value: Name" /></td> 33 <td> 34 <input type="text" class="textbox" data-bind="value: PhoneNo" /></td> 35 <td> 36 <input type="text" class="textbox long" data-bind="value: EmailAddress" /></td> 37 <td><a href="#" data-bind="click: $root.addContact" />添加</td> 38 </tr> 39 </tbody> 40 </table> 41 </div> 42 <script type="text/javascript"> 43 function ContactViewModel() { 44 self = this; 45 self.allContacts = ko.observableArray(); 46 self.addedContact = ko.observable(); 47 //加載聯系人列表 48 self.loadContacts = function () { 49 $.get("/api/contacts", null, function (data) { 50 self.allContacts(data); 51 var emptyContact = { Id: "", Name: "", PhoneNo: "", EmailAddress: "" }; 52 self.addedContact(emptyContact); 53 }); 54 } 55 //添加聯系人 56 self.addContact = function (data) { 57 if (!self.validate(data)) { 58 return; 59 } 60 $.ajax({ 61 url: "/api/contacts/", 62 data: self.addedContact(), 63 type: "PUT", 64 success: self.loadContacts 65 }); 66 }; 67 //修改聯系人 68 self.updateContact = function (data) { 69 $.ajax({ 70 url: "/api/contacts/", 71 data: data, 72 type: "POST", 73 success: self.loadContacts 74 }); 75 }; 76 //刪除聯系人 77 self.deleteContact = function (data) { 78 $.ajax({ 79 url: "/api/contacts/" + data.Id, 80 type: "DELETE", 81 success: self.loadContacts 82 }); 83 }; 84 self.validate = function (data) { 85 if (data.Name && data.PhoneNo && data.EmailAddress) { 86 return true; 87 } 88 alert("請輸入完整聯系人信息!"); 89 return false; 90 } 91 self.loadContacts(); 92 } 93 ko.applyBindings(new ContactViewModel()); 94 </script> 95 </body> 96 </html>
這個像補充的是,蔣老師在這用的是自帶的knockoutJS作為MVVM風格的部分前端框架。關於這一塊,有一個問題困擾了我很久,就是KnockoutJS和AngularJS誰的適用性更強,其實它們沒有可比性,KnockoutJS只提供了部分的工作。以下鏈接是對此問題的解釋,結論是我將學習並使用AngularJS。
http://blog.darkthread.net/post-2014-06-07-go-to-angularjs.aspx
說到這,我還想到了學習中的一個困惑,那么多的IOC框架到底哪個相對更好一些?結論是Autofac,它以被使用在Orchard開源的CMS系統中,順道提一嘴,nopCommerce的.net開源電商系統也不錯哦。之前IOC框架對比的詳情請見如下鏈接,李平老師做了最好的解釋:
http://www.cnblogs.com/liping13599168/archive/2011/07/17/2108734.html
接下來,介紹ASP.NET WebAPI的服務器管道,這一塊和之前學習的ASP.NET MVC管道很相似,但也有一些差異,不過個人感覺這個管道更加的像J2EE的管道了。由於很多內容比較相似,將進行簡單的介紹,不過框架中異步編程模型用的很多,值得學習參考。下圖簡單的表述了框架對請求的處理過程:
框架通過單例提供HttpControllerHandler對象,多個HttpWebRoute共享對象,並且它將創建右側的ASP.NET Web API處理管道,通過調用BeginProcessRequest方法激活管道運轉。該管道其實就是HttpMessgaeHandler鏈,HttpServer和HttpControllerDispatcher可以看做兩個特殊的HttpMessageHandler,接下來通過表格的形式對相關類型進行簡單的介紹:
類型 | 簡介 |
HttpMessageHandler | 核心類,針對請求的處理實現在SendAsync中,針對響應的處理通過返回類型Task<HttpResponseMessage>完成 |
HttpRequestMessage | Content屬性封裝Http主體信息 |
HttpResponseMessage | StatusCode、ReasonPhrase屬性表示響應狀態碼與描述 |
DelegatingHandler | 用於構建處理鏈,通過InnerHanlder屬性進行傳遞,是責任鏈模式的實現? |
HttpServer | Dispatcher屬性指向最終的分發器對象,Configuration屬性包含了所有的配置信息。 |
HttpConfiguration | DependencyResolverFilters: AuthorizationFilter, ActionFilter, ExceptionFilterFormatters:返回格式化器列表IncludeErrorDetailPolicy:客戶端異常顯示策略PropertiesServices: 返回ServiceContainer,一個簡易IocR容器,默認實現為DefaultServices,很常用。 |
HttpControllerHandler以延遲加載的方式來創建HttpServer,字典屬性Properties以Key為"MS_HttpContext"、"MS_HttpRouteData"的形式傳遞相關數據。HttpControllerDispatcher負責最后對請求做最后的處理,包括對ApiController的激活和目標Action的執行等操作,用下表簡述該過程:
行為 | 簡介 |
HttpController的激活 | 借助HttpControllerDescriptor,完成HttpController類型解析、選擇、創建等操作,可以通過自定義DependencyResolver或HttpControllerActivator來實現基於IOC的HttpController的激活。 |
HttpController的執行 | 通過ExecuteAsync方法,參數為HttpControllerContext,注意UrlHelper中Link代表絕對地址,Route相對地址 |
Action的選擇 | HttpActionDescriptor的ExecuteAsync方法實現Action的執行,Action支持7中不同的HTTP方法,默認為POST。通過HttpActionSelector組件實現對目標Action的選擇,方法GetActionMapping的返回值為一個ILookup<string, HttpActionDescriptor>類型 |
Model元數據的解析 | 與MVC基本一致 |
Action參數綁定 | 借助HttpParameterDescriptor、HttpActionBinding,通過HttpParameterBinding對象的ExecuteBindingAsync完成綁定,具體的實現類有: CancellationTokenParameterBinding ErrorParameterBinding FomatterParameterBinding:消息主體,html,json,xml HttpRequestParameterBinding:HttpRequestMessage ModelBinderParameterBinding:查詢字符串,路由數據 |
Model的驗證 | 包括DataAnnotationModelValidator RequiredMemberModelValidator ValidatableObjectAdapter ErrorModelValidator等驗證器,需要注意的是該框架中驗證過程是遞歸的,與MVC有點不同。 |
Action的執行與結果的響應 | 通過HttpActionInvoker的InvokerActionAsync方法激活Action,通過ActionResultConverter將Action的返回值轉換為HttpResponseMessage,轉換器包括: ResponseMessageResultConverter ValueResultConverter<T> VoidResultConverter 3個內置Filter篩選器的作用與MVC中的類似 |
補上IOC實現的代碼和HttpParameterBinding的流程圖:

1 public class NinjectDependencyResolver : IDependencyResolver 2 { 3 private List<IDisposable> disposableServices = new List<IDisposable>(); 4 public IKernel Kernel { get; private set; } 5 public NinjectDependencyResolver(NinjectDependencyResolver parent) 6 { 7 this.Kernel = parent.Kernel; 8 } 9 public NinjectDependencyResolver() 10 { 11 this.Kernel = new StandardKernel(); 12 } 13 public void Register<TFrom, TTo>() where TTo : TFrom 14 { 15 this.Kernel.Bind<TFrom>().To<TTo>(); 16 } 17 public IDependencyScope BeginScope() 18 { 19 return new NinjectDependencyResolver(this); 20 } 21 public object GetService(Type serviceType) 22 { 23 var service = this.Kernel.TryGet(serviceType); 24 this.AddDisposableService(service); 25 return service; 26 } 27 public IEnumerable<object> GetServices(Type serviceType) 28 { 29 foreach (var service in this.Kernel.GetAll(serviceType)) 30 { 31 this.AddDisposableService(service); 32 yield return service; 33 } 34 } 35 public void Dispose() 36 { 37 foreach (var disposable in disposableServices) 38 { 39 disposable.Dispose(); 40 } 41 } 42 private void AddDisposableService(object service) 43 { 44 IDisposable disposable = service as IDisposable; 45 if (null != disposable && !disposableServices.Contains(disposable)) 46 { 47 disposableServices.Add(disposable); 48 } 49 } 50 } 51 public class WebApiApplication : System.Web.HttpApplication 52 { 53 protected void Application_Start() 54 { 55 //自定義操作 56 NinjectDependencyResolver dependencyResolver = new NinjectDependencyResolver(); 57 dependencyResolver.Register<IContactRepository, DefaultContactRepository>(); 58 GlobalConfiguration.Configuration.DependencyResolver = dependencyResolver; 59 } 60 }
HttpParameterBinding流程圖:
最后介紹與WebAPI客戶端調用相關的內容,提到調用大家第一反應就是在Web頁面中通過javascript進行Ajax調用,獲取數據並呈現,服務的消費者是前端頁面,這只是調用的主要方式之一。另外一種就是通過HttpClient來進行調用,這和Web Service調用很相似,服務的消費者是一般應用程序。HttpClient類繼承之抽象類HttpMessageInvoker,核心方法SendAsync包括HttpRequestMessage的參數和HttpResponseMessage的返回類型,和之前服務器端的HttpMessageHandler類型一樣,實際上HttpClient就是一個該類的封裝。HttpCompletionOption用於設置響應完成的標志,包括讀完消息頭和讀完消息體。屬性BaseAddress用於指定WebAPI基地址,DefaultRequestHeader用於添加任意的報頭,MaxResponseContentBufferSize表示讀取緩存區的大小,默認2G,Timeout表示超時時限,默認100s。GetAsync, GetByteArrayAsync, GetStreamAsync, GetStringAsync用於HTTP-GET請求,其他方法也有相似定義。下面通過一個服務器端自我寄宿,客戶端一般調用的例子完成學習,需要注意通過Nuget添加SelfHost和Client的庫,代碼如下所示:

1 //服務器端 2 class Program 3 { 4 static void Main(string[] args) 5 { 6 var config = new HttpSelfHostConfiguration("http://127.0.0.1:3721"); 7 config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }); 8 using (var httpServer = new HttpSelfHostServer(config)) 9 { 10 httpServer.OpenAsync().Wait(); 11 Console.WriteLine("按任意鍵關閉WebAPI"); 12 Console.Read(); 13 } 14 } 15 } 16 //客戶端 17 class Program 18 { 19 static void Main(string[] args) 20 { 21 Uri baseAddress = new Uri("http://127.0.0.1:3721"); 22 var httpClient = new HttpClient { BaseAddress = baseAddress }; 23 IEnumerable<Contact> contacts = httpClient.GetAsync("api/contacts").Result.Content.ReadAsAsync<IEnumerable<Contact>>().Result; 24 Console.WriteLine("當前聯系人列表:"); 25 ListContacts(contacts); 26 var contact = new Contact { Id = "003", Name = "qiuzi", EmailAddress = "qiuqiu@gmail.com", PhoneNo = "95580" }; 27 Console.WriteLine("\n添加聯系人003: "); 28 httpClient.PutAsync<Contact>("/api/contacts", contact, new JsonMediaTypeFormatter()).Wait(); 29 contacts = httpClient.GetAsync("api/contacts").Result.Content.ReadAsAsync<IEnumerable<Contact>>().Result; 30 ListContacts(contacts); 31 contact = new Contact { Id = "003", Name = "qiuzi", EmailAddress = "zhaoyun@outlook.com", PhoneNo = "123" }; 32 Console.WriteLine("\n修改聯系人003: "); 33 httpClient.PostAsync<Contact>("/api/contacts", contact, new XmlMediaTypeFormatter()).Wait(); 34 contacts = httpClient.GetAsync("api/contacts").Result.Content.ReadAsAsync<IEnumerable<Contact>>().Result; 35 ListContacts(contacts); 36 Console.WriteLine("\n刪除聯系人003: "); 37 httpClient.DeleteAsync("/api/contacts/003").Wait(); 38 contacts = httpClient.GetAsync("api/contacts").Result.Content.ReadAsAsync<IEnumerable<Contact>>().Result; 39 ListContacts(contacts); 40 Console.Read(); 41 } 42 43 private static void ListContacts(IEnumerable<Contact> contacts) 44 { 45 foreach (var contact in contacts) 46 { 47 Console.WriteLine("{0, -6}{1, -6}{2, -20}{3, -10}", contact.Id, contact.Name, contact.EmailAddress, contact.PhoneNo); 48 } 49 } 50 }
此外,WebAPI學習系列目錄如下,歡迎您的閱讀!
快速入門系列--WebAPI--04在老版本MVC4下的調整
注:本文主要供自己學習,不妥之處望見諒。
參考資料:
[1]蔣金楠. ASP.NET MVC4框架揭秘[M]. 上海:電子工業出版社, 2012. 445-526