Membership 三步曲之進階篇 - 深入剖析Provider Model
本文的目標是讓每一個人都知道Provider Model 是什么,并且能靈活的在自己的項目中使用它。
- Membership三步曲之入門篇 - Membership 基礎示例
- Membership三步曲之進階篇 - 深入剖析Provider Model
- Membership三步曲之高級篇 - 從Membership 到 .NET 4.5 之 AspNet.Identity
在入門篇中我們已經從0開始將Membership集成到一個空的MVC站點中,并且與ASP.NET的權限管理體系相結合。本篇(進階篇)將剖析Membership的設計理念以及它的結構。別忘了我們還有高級篇我們將會擴展自己的MembershipProvider和RolesProvider,目地是直接利用我們老系統中已經存在的用戶表和角色表,也就是用我們已經存在的數據庫去集成Membership。
我們了解到Membership的重要組成部分是MembershipProviders。 要理解Membership的架構設計,我們首先要理解Provider。Provider 的全稱是 Provider Model (中文是提供程序模型),它早就已經不是什么新鮮事了,它是在ASP.NET 1.1的時候被 Rob Howard 設計出來的,從ASP.NET 2.0時代,它就已經開始大行其道了。到現在的MVC中,各種Provider已經隨處可見了, 貌似微軟對它特別青睞。下面我們就來好好看一看Provider到底是個神馬東西,Membership是如何利用它的!
目錄
隨處可見的Provider
各位Provider們,先來和大家混個眼熟吧,相信下面的幾種Provider大家并不會覺得陌生:
前面兩種Provider我們在Membership入門篇里面已經接觸到了,后面兩種我想也不用說了吧? 而在MVC中的下面幾個兄弟,你們都見識過吧?
那么Provider到底是起了一個什么樣的作用呢?為什么微軟要如此廣泛的使用它?又或者我們是不是可以在自己的項目中使用這種設計呢?
首先,一個Provider可以是一個類,或者好幾個類共同組成的一個模塊,它們提供了一些特定的功能,這些特定的功能要么用接口中的方法或者抽象類的抽象方法暴露出來(.NET框架中一般使用抽象類)。這就是ProvderModel的第一要素:具要良好定義的公有API。比如說我們的MembershipProvider提供了GetUser, CreateUser, ChangePassword等與用戶管理息息相關的功能,這些API并不是越全越好,而是越精越好,只定義最核心的功能,其它的可以添加自己的擴展去實現,正是小巧又精悍呀。
其次,要有一種配置機制。這種配置機制能夠將具體的Provider與我們定義的功能集綁定起來。比如說我們有SqlMembershipProvider,還有ActiveDirectoryMemberProvider,他們都是抽象類MembershipProvider的子類。我還可以自己去添加其它的子類以不同的方式實現同樣的功能,那么我就需要一種靈活的方法將我的子類加入到程序中。ASP.NET是通過web.config里面的配置結點實現的。
最后,我們還需要有一種加載機制,通過這種加載機制(我們下面會探討這種加載機制),我們可以讀取配置并創建Provider的具體實例傳遞給我們的功能API。.NET已經為我們提供好了一些類來幫助我們達到簡單配置和初始化Providers的目地,我們馬上就會說到。
那么Provider model能給我們帶來哪些好處呢?
- 提高靈活性和可擴展性
- 隔絕功能代碼和具體的數據訪問代碼
- 讓擴展變得簡單。既可以從抽象類繼承,也可以從其它具體Provider繼承,只實現不一樣的地方,最后只需要輕松配置就可以搞定。
- ?
拿Membership來舉例,核心功能通過Membership調用完成。而Membership并不負責具體的實現,它定義了公開良好的接口在MembershipProvider中。 所以Membership實際上并不處理業務代碼,大量的功能是調用具體的Provider來實現的。這就起到了隔絕的作用,用戶信息可以保存在SQL數據庫中,還可以保存在域控制器中,這些Membership不用關心,只需要交給具體的Provider就可以了。同理,如果我們想讓用戶保存到其它地方,只需要添加自己的Provider來實現就可以了。
Membership.GetUser的代碼
public static MembershipUser GetUser(string username, bool userIsOnline) { SecUtility.CheckParameter(ref username, true, false, true, 0, "username"); return Provider.GetUser(username, userIsOnline); }
所以最后怎么樣去實現,實現上還是看我們具體配置的Provider如何去做。
Provider并不是一種設計模式
Provider在最開始(ASP.NET 1.1)的時候其實是一種設計模式,全稱是 Provider Model Design Pattern,但是到了ASP.NET2.0 的時候其實就直接叫Provider Model 了(具體叫不叫設計模式,是不明確的,還有的地方叫Provider Pattern,在這里我們就不做爭辯了)。 但是實際上我們會在Provider Model身上發現好幾種設計模式的影子,比如說:
- 策略模式
- 工廠方法模式
- 單一模式
- 外觀模式
本文不會詳細討論這四種設計模式,但是為了讓大家更好的理解Provider Model,我們來簡單的結合Provider Model的結構提一下每個設計模式的定義。
2.1 策略模式
關于策略模式有一個故事,話說老王有時候開車特別快,開的快有時候就會被警察逮住。但是有時候遇到的是比較好說話的警察可能不會扣他的分,只是口頭警告一下,有時候會遇到比較嚴格又正直的警察一定要扣分。但是在沒有被逮住之前,我們都不知道那個會是一個好說話的警察還是一個嚴格又正直的警察。
有沒有發現這個類圖和我們上面的Membership的類圖很像? 簡單一句話概括策略模式:將一個特性的主要功能抽象出來,允許不同的實現方式,在運行時可以任意切換到不同的實現。MembershipProvider抽象類為我們定義了一組具體的API, 而Membership類則負責調用這些API,但是Membership并不在乎現在是哪一個實現。
2.2 工廠方法模式
工廠方法(如果想詳細了解工廠方法的同學,可以參考Terry Lee的這篇博客)的關鍵在于: 使用者不需要知道也不用知道哪一個具體對象會被創建。對于Membership類來說,它會調用具體的Provider相關的方法而不用知道當前是哪一個Provider在工作。 在Membership內部有一個名叫Provider的屬性,它的類型是基類- 抽象類MembershipProvider。而它的初始化是借助于.NET為我們提供的一套配置和初始化的基礎框架來實現的,這兩套框架我們馬上就會說到了。
2.3 單件模式
單件模式簡單的說即在整個應用程序的生命周期內,某個類的實例至始至終都是同一個。在Membership的內部,維護著一個Provider的屬性,這就是singleton的實現。
2.4 外觀模式
對外觀模式淺顯易懂的定義是“為一組分布在不同的子系統或者不同地方的接口提供一個統一的入口點”。大家可以對比一下,所有的和Membership相關的功能都是通過調用Membership完成的,即使里面有一些功能是由MembershipUser和其它的類來實現,但是沒有關系,Membership給這些所有的功能作了一個統一。這就是傳說中的外觀模式。
Provider Model的配置及初始化框架
Provider Model之所以能在.NET的世界里面大放光彩,不僅僅是因為它提供了我們上述的功能及特性,而是它還提供了這樣的一套配置及初始化框架,從而使得我們在.NET中使用Provider Model中使用Provider Model是那么的容易。下面我們就來看看它是如何做到的。
我們在上一篇中列舉了Membership中使用到的一些類型,但是用到起到讀取配置作用的實際上是MembershipSection這個類,它會幫助我們把配置文件中membership結點信息加載到一個MembershipSection的實例中。我們可以來看看Membership中的Initialize方法:
private static MembershipProvider s_Provider; private static MembershipProviderCollection s_Providers; private static void Initialize() { MembershipSection membershipSection = null; if (!_initialized) { // 這里是加載配置信息 membershipSection = (MembershipSection)ConfigurationManager.GetSection("membership"); if (membershipSection == null) throw new Exception("沒有找到Membership的配置"); s_Providers = new MembershipProviderCollection(); // 這里是創建Provider實例 ProvidersHelper.InstantiateProviders( membershipSection.Providers, s_Providers, typeof(MembershipProvider)); // 將當前Provider指向默認的Provider s_Provider = s_Providers[membershipSection.DefaultProvider]; _initialized = true; } } public static MembershipProvider Provider { get { Initialize(); return s_Provider; } }
而初始化是由System.Web.Configuration.ProvidersHelper來完成的,它里面只包含兩個公有的靜態方法: InstantiateProviders和InstantiateProvider。大家可以看到前者只不過是一個包裝方法,其實是通過遍歷調用后者來加載所有的provider實例的。后者的代碼我就不貼了,其實就是用了反射去創建對象而已,還記得我們已經將具體類型配置到config文件去了么?
public static void InstantiateProviders( ProviderSettingsCollection configProviders, ProviderCollection providers, Type providerType) { foreach (ProviderSettings settings in configProviders) { providers.Add(InstantiateProvider(settings, providerType)); } }
有了這一套配置和初始化框架,才有了我們在config文件中的簡單操作。
利用Provider Model實現記日志組件
記日志組件也已經是被炒了很多次的話題,既然都炒了那么多次了,也不在乎我再炒一次吧? 其實.NET本身提供的一些事件記錄器也是基于Provider Model來實現的,既然我們學習了Provider Model,那就來自己動手實現一個簡單的日志組件小小實戰一下吧。
我們要實現的功能很簡單,只有能夠寫入消息到一個文件就可以了,但是我們要求可以按不同的格式寫入,可以是純文本的,可以是XML的,當然你還可以擴展成任意其它你想要的格式。 對于調用者來說,它只需要輕松調用就可以了。并且我們要實現可以在config文件中靈活配置使用哪一種格式來記錄我們的日志。
public ActionResult Index() { MyLog.LogManager.Log(MyLog.LogType.Info, "我要開始記日志了"); return View(); }
我們的LogManager是對外暴露的唯一對象,里面的方法和屬性都是靜態的,和Membership類一樣,它不負責具體的實現,所有的實現都是轉交給具體的Provider的。
LogManager.Log的代碼
public static void Log(LogType logType, string Message) { Provider.WriteLog(logType, Message); } public static LogProvider Provider { get { Initialize(); return _provider; } }
初始化Provider的代碼
private static LogProvider _provider; private static LogProviderCollection _providers; // 是否已經初始化 private static bool _initialized = false; private static void Initialize() { LogProviderConfigurationSection myLogSection = null; if (!_initialized) { // 讀取myLog結點的配置 myLogSection = (LogProviderConfigurationSection)ConfigurationManager.GetSection("myLog"); if (myLogSection == null) throw new Exception("沒有找到myLog的配置"); // 初始化providers _providers = new LogProviderCollection(); ProvidersHelper.InstantiateProviders(myLogSection.Providers, _providers, typeof(LogProvider)); _provider = _providers[myLogSection.DefaultProvider]; _initialized = true; } }
TextLogProvider.WriteLog的代碼
public override void WriteLog(LogType logType, string message) { var dir = Path.GetDirectoryName(FilePath); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); using (var sw = new StreamWriter(FilePath, true)) { string s = string.Format("{0}, {1}, {2}", DateTime.Now, logType.ToString(), message); sw.WriteLine(s); } }
XmlLogProvider我就不實現了,如果大家有興趣可以自己動手嘗試一下,還是老樣子附上整個項目的源碼。Membership三步曲最后一篇,我們將實現自己的MembershipProvider利用EF來完成的用戶的管理。下周見!
下載源碼:http://pan.baidu.com/s/1jGI9dhs (覺得有用的同學就幫助點個推薦吧,您的滿意,我的動力!)
參考引用
- http://msdn.microsoft.com/en-us/library/aa479046.aspx
- http://msdn.microsoft.com/en-us/library/ms972319.aspx
- http://msdn.microsoft.com/en-us/library/aa479020.aspx
文章列表