這部分內容的學習,已經放了大半年時間了,果斷補充上,盡早將過去遺留的老技術坑都補上。首先將介紹服務冪等性的概念和相關解決方案,這部分也將是本文的理解難點,由於WebAPI是一種Restful風格服務的實現方式,其遵循HTTP標准方法,因此理解好這部分概念,對於提供良好的業務服務顯得非常重要。之后則將介紹SignalR這一長連接通訊的集成解決方案的概念和實踐,這部分在交互式的Web場景中非常有效。最后將補充Owin、IOC、EnterpriseLibrary等相關知識,這些也都是.NET程序員比較容易忽視的知識點,這些知識在快速搭建Solution上有很大的幫助。
最早接觸這個概念還是在一次面試的過程中,當時記得自己只能是通過賣萌將這一概念一筆帶過,由於當時的項目實踐相對較少,且被微軟便捷的服務搭建所欺騙,以為搭建一個webService只用在IDE中添加有一個.asmx就萬事大吉了,其他項目通過一個服務引用就算是SOA實現了(WCF額外需要配置一下終結點),其實對於整個服務的概念完全是個門外漢。
那么在實際中,搭建一個服務需要注意那些問題呢?接下來通過一個簡單的表格來描述。
關注因素 | 詮釋 |
通訊協議的選取 | 例如常見的TCP、HTTP、SOAP等,實際上任何協議都可以作為服務的載體,只要適合相應的場景即可 |
URL | 服務的地址,服務的消費者可以通過這個地址請求服務 |
安全性 | 只有通過認證的請求才能獲得服務,不同的服務方法需要不同權限控制 |
冪等性 | 對於同一個服務方法來說,相同的請求參數無論請求幾次,都將獲得相同的結果(簡化版的解釋,不太完備) |
其他 | 與服務相關的概念非常多,比如:在架構層次流行的微服務,用於解耦應用;服務的監控、限流;分布式服務的治理、擴容等。 |
接下來將詳細介紹服務冪等性的概念,相關解決方案和基於HTTP協議的服務冪等性等知識。
基礎概念:從抽象代數的角度,冪等Idempotence就是f(f(x)) = f(x)。也就是說對同一個服務的1次或多次調用,返回的結果相同,且對服務系統的影響相同,接下來通過一個非常簡單的圖來描述該概念在服務請求場景下的意義。
在上圖中,第一次扣款請求成功,但返回丟失,這是Client重發扣款請求,之后成功。在這樣的場景下,如果不控制服務的冪等性,就會出現重復扣款的情況出現。
解決方案:上例標准的解決方案是,客戶端的這個操作需要兩個請求,首先需要向服務端申請一個ticket進行扣款操作,之后將該ticket作為參數的一部分發送給Server請求扣款。服務端首先檢驗該ticket是否已經被使用,若被使用,直接返回成功;若未被使用,則進行相應扣款操作。邏輯上很簡單,不過在實踐時有幾點需要注意:
注意事項 | 詮釋 |
冪等的時效性 | 在實際項目中,冪等是具有時效性的,不同的業務需求會有不同的時效性要求。一般來說,對於重要業務操作,通過是與Money有關的操作,要求持久的服務冪等性,這是就需要選用數據庫來實現冪等控制,將ticket(流水號,GUID等)保存起來,其特點是安全、低效;對於一般的數據,可以選用緩存來控制,其特點是高效、不穩定。 |
數據庫實現冪等 | 比如SQL SERVER, 在Read Committed隔離級別下,,建立一張專門的冪等表,通過重復insert的異常來實現冪等,比較規范;也可以通過NoLock讀的方式,更搞笑,但存在隱患,推薦前者。Tip:通過SqlException, Number = 2601 |
緩存實現冪等 | 比如Redis,通過與key相關的操作,Exists Key |
CRUD操作的冪等級別 | SELECT最高,為只讀級別;UPDATE、DELETE次之,為冪等級別;INSERT為不能冪等級別。也就是說,SELECT操作不管是一次操作還是多次操作,均不改變目標的狀態;UPDATE、DELETE只在第一次調用時會改變狀態,之后不會;而INSERT則每次均改變狀態。 |
CAS操作 | 之前一直有個疑惑,就是關於UPDATE操作,比如a++的情況下,這個操作不是冪等的,但實際上,這不是一個原子操作,其涉及一次查詢和一次修改,在很多語言中,都支持稱為CAS(CompareAndSet)的原子操作。 |
Ticket的生成 | 根據不同的場景,可以是客戶端生成,也可以是服務端生成,當然,最好的方案是設置好指定規則,然后由客戶端生成ticket,比如GUID組合客戶端的標識的方式,因為這樣可以減少服務端的壓力,無論是CPU還是網絡。 |
基於HTTP協議的服務冪等性:在之前的表格中,已介紹過CRUD操作所對應的冪等級別,那么對應到HTTP的操作呢?很簡單,GET表示查詢操作,PUT和DELETE表示更新和刪除操作,POST表示插入操作,因此POST操作需要添加冪等控制的。當然了,在WebAPI的實際設計中,接口的URL格式和http報文中Body的參數值會需要進一步的思考。
此外,大家也可以查閱博主Todd Wei的博文http的冪等性http://www.cnblogs.com/weidagang2046/archive/2011/06/04/idempotence.html
SignalR這個名字,咋一看還挺高大上的,實際上和WCF、AJAX類似,並不是什么新技術,而是對已有技術的一種整合,集成了客戶端和服務端的庫。不知道大家還記不記的,大學時學習的Windows網絡編程,當時通過WinSocket搭建了一個聊天室,其實這里的SignalR也一樣,最常見的應用仍然是聊天室場景,不過變成瀏覽器和服務器之間,而不是過去的Client與服務器之間。那么它與H5的WebSocket有什么區別么?准確來說,SignalR整合了WebSocket,在瀏覽器支持H5的情況下就使用WebSocket,若不支持,就通過長輪訓的方式,算是一種兼容性的整體解決方案。
簡單來說,記住一點就好,SignalR支持雙向通信的長連接,其是對http請求-響應模式的有力補充。其提供一個簡單的API用於創建服務端到客戶端的遠程過程調用(RPC),以便從服務器端.NET代碼中調用客戶端瀏覽器中的js代碼。
SignalR的API包含兩種客戶端和服務器之間進行通信的模型:永久連接和Hubs。。接下來通過來通過一段代碼,走進SignalR的世界。

1 前端頁面 2 @{ 3 ViewBag.Title = "Chat"; 4 } 5 6 <h2>Chat</h2> 7 <div class="container"> 8 <input type="text" id="message" /> 9 <input type="button" id="sendmessage" value="Send" /> 10 <input type="hidden" id="displayname" /> 11 <ul id="discussion"></ul> 12 </div> 13 @section scripts{ 14 <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script> 15 <script src="~/signalr/hubs"></script> 16 <script> 17 $(function () { 18 //reference the auto-generated proxy for the hub 19 var chat = $.connection.chatHub; 20 chat.client.addNewMessageToPage = function (name, message) { 21 //add the message to the page 22 $('#discussion').append('<li><strong>' + htmlEncode(name) + '</strong>: ' + htmlEncode(message) + '</li>'); 23 }; 24 //get the user name and store it to prepend to messages. 25 $('#displayname').val(prompt('Enter your name:', '')); 26 //set initial focus to message input box 27 $('#message').focus(); 28 //start the connection 29 $.connection.hub.start().done(function () { 30 $('#sendmessage').click(function () { 31 chat.server.send($('#displayname').val(), $('#message').val()); 32 $('#message').val('').focus(); 33 }); 34 }); 35 }); 36 37 //this optional function html-encodes messages for display in the page 38 function htmlEncode(value) { 39 var encodedValue = $('<div/>').text(value).html(); 40 return encodedValue; 41 } 42 </script> 43 } 44 45 Startup文件 46 [assembly: OwinStartup(typeof(Sory.Framework.SignalRDemo.Startup))] 47 namespace Sory.Framework.SignalRDemo 48 { 49 public class Startup 50 { 51 public void Configuration(IAppBuilder app) 52 { 53 //Any connection or hub wire up and configuration should go here 54 app.MapSignalR(); 55 } 56 } 57 } 58 59 Hub文件 60 public class ChatHub : Hub 61 { 62 public void Send(string name, string message) 63 { 64 Clients.All.addNewMessageToPage(name, message); 65 } 66 }
相關學習可以參見張善友大神的4年前的博文SingalR QuickStart, http://www.cnblogs.com/shanyou/archive/2012/07/28/2613693.html
另外還有博主will_曉檸的博文http://www.cnblogs.com/vance/p/SignalR.html,相對版本更新一些,此外其翻譯的Signal入門非常的贊,必須頂http://files.cnblogs.com/files/wanliwang01/SignalR2.0.pdf
簡單來說,類似J2EE中javaWeb的相關標准(servlet),之后各種不同的容器廠商均可以針對該接口提供自己的實現,比如Tomcat、Weblogic等。隨着微軟慢慢走向開發,也提出了相應的接口標准,這個標准就是Owin,我們常見的IIS其實就是該標准的一個官方實現。記得身邊的一位大牛說過,JAVA就是先自己開發,其他的廠商也模仿着開發,之后為了統一就建立標准,之后的版本大家都按照這個標准來,類似於實踐推導出理論,理論再來指導實踐的過程。
在Owin中,將不再使用ASP.NET管道處理請求,而是使用Owin管道來處理請求,其通過一個Dictionary來傳遞上下文信息,其信息如下表所示。
Key Name | 類型 | 描述 |
Owin.RequestBody | Stream | Http請求體 |
Owin.RequestHeaders | IDictionary<string, string[]> | Http請求頭 |
Owin.RequestMethod | String | 請求方法, get, post等 |
Owin.RequestPathBase | String | URL根 |
Owin.RequestPath | String | URL路徑,根后面的部分 |
Owin.RequestProtocol | String | 協議名稱和版本, http/1.1 |
Owin.RequestQueryString | String | 查詢字符串 |
Owin.RequestSchema | String | http或者https |
Owin接口微軟的官方實現叫做Katana(武士刀)的組件包括:Application, Middleware, Server和Host。在vs2013中的MVC5模板中,添加如下DLL,和在nuget中package添加如下配置。

1 <package id="Owin" version="1.0" targetFramework="net451"/> 2 <package id="Microsoft.Owin" version="2.0.0" targetFramework="net451"/> 3 <package id="Microsoft.Owin.Host.SystemWeb" version="1.0" targetFramework="net451"/> 4 <package id="Microsoft.Owin.Security" version="1.0" targetFramework="net451"/> 5 <package id="Microsoft.Owin.Security.Cookies" version="1.0" targetFramework="net451"/>
需要提及的是,其通過一個稱為claims-based認證方式進行用戶的認證,與原有Form認證方式有一定區別,簡單來講,類似於windows的token認證(單登SSO)。
詳細信息,可以參考Jesse博主的博文http://www.cnblogs.com/jesse2013/p/aspnet-identity-claims-based-authentication-and-owin.html#what-is-owin
IOC框架用於解耦系統不同層次間的依賴關系,便於系統的擴展。當然其也會在一定程度上增加系統的復雜性,影響系統的效率,那么選擇一個高效可靠IOC顯得非常重要。IOC控制反轉的相關組件非常的多,包括微軟的Unity,第三方的Autofac,CastleWindsor, Spring.NET, StructureMap, Ninject等,其中Unity表現的中規中矩,在大量迭代情況下(>1000000),Autofac和StructureMap效率最高。就我個人而言,由於公司的組件庫支持Unity,那么就不用選了,哈哈。
詳情請見Leepy大神的博文:http://www.cnblogs.com/liping13599168/archive/2011/07/17/2108734.html
微軟的企業庫包含的模塊非常多,比如緩存、數據存取、日志、IOC、AOP、異常處理等,由於現代企業往往都提供統一的日志管理系統和DAL系統,因此微軟企業庫仍然有價值的部分為AOP和IOC等部分,其實都是面向切面的應用(我們常用的WCF、MVC、WebAPI項目由於攔截器的存在,已經實現了AOP),當然在一部分做的最好的應該是Spring。
接下來通過一個示例,來對AOP的應用,有一個基本的了解(通過Unity的Interception擴展)。

1 配置: 2 <configuration> 3 <configSections> 4 <section name="unityInterception" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/> 5 </configSections> 6 <unityInterception> 7 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension 8 , Microsoft.Practices.Unity.InterceptionExtension.Configuration"> 9 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension 10 , Microsoft.Practices.Unity.Interception.Configuration" /> 11 <container> 12 <extension type="Interception"/> 13 <register type="LipinInvoice.BL.AOPDemo, LipinInvoice.BL"> 14 <interceptor type="TransparentProxyInterceptor" /> 15 <policyInjection /> 16 </register> 17 </container> 18 </sectionExtension> 19 </unityInterception> 20 </configuration> 21 22 攔截類 23 public class ExceptionHandler : ICallHandler 24 { 25 public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) 26 { 27 IMethodReturn methodReturn = getNext()(input, getNext); 28 return null; 29 } 30 public int Order { get; set; } 31 } 32 33 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Class)] 34 public class ExceptionHandlerAttribute : HandlerAttribute 35 { 36 public override ICallHandler CreateHandler(IUnityContainer container) 37 { 38 return new ExceptionHandler() { Order = this.Order }; 39 } 40 }
EL6.0官方文檔:http://files.cnblogs.com/files/wanliwang01/EL5.0.pdf
EL5.0的詳細配置,可以參考博主黃聰的系列教程:http://www.cnblogs.com/huangcong/archive/2010/06/08/1753988.html
還可以參考倉儲大叔的博文:http://www.cnblogs.com/lori/p/4088889.html
WebAPI學習系列目錄如下,歡迎您的閱讀!
快速入門系列--WebAPI--04在老版本MVC4下的調整
參考資料:
-
蔣金楠. ASP.NET Web API 2框架揭秘[M]. 北京:電子工業出版社, 2014.
-
(美)加洛韋. ASP.NET MVC 5高級編程(第5版)[M]. 北京:清華大學出版社, 2015.