您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關自定義的ControllerFactory接口如何實現支持Area,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
幾個星期之前,有個朋友對我說,他的項目中需要將前后臺區分開來,也就是類似分Area的功能。不過Area只在MVC 2中出現,因此現在想在1.0版本中先實現類似的功能了。他打算,根據Route中捕獲的內容(如“area”),然后去找對應命名空間下的Controller。這樣看來不難,似乎只要在Route上做點配置,而默認的DefaultControllerFactory已經對命名空間的查詢提供支持了(可惜有線程安全的問題)。
不過他說,發現似乎這塊功能不是他想象的那樣,因此希望我可以看看到底是什么問題。由于當時沒有擴展ASP.NET MVC的需求,后來我事情一多就忘了,現在先說聲抱歉。最近開始對ASP.NET MVC動手動腳了,發現這樣一個Area的功能非常有用,而且巧合的是,我也打算把Area和命名空間對應起來。
只是我選擇的路和那位兄弟不一樣,我打算自己寫一個簡單的ControllerFactory來替換掉默認的DefaultControllerFactory。這么做的主要原因是:我不知道DefaultControllerFactory已經提供對命名空間的支持了,微軟默默地實現了卻沒有對外公開過,我也是后來閱讀代碼時才發現的。同時又意識到線程安全的問題,于是就還是打算自己寫了。
好在ASP.NET MVC從設計之初就提供了擴展的能力,每個組件粒度都很小,大部分組件都是可以獨立拔插的(Controller類除外,如果你使用自己的IController實現,就會發現大部分功能,如各Filter都失效了)。而要實現一個Controller Factory,只要實現一個簡單的IControllerFactory就可以了(我喜歡接口):
public interface IControllerFactory { IController CreateController(RequestContext requestContext, string controllerName); void ReleaseController(IController controller); }
于是構建一個AreaControllerFactory也大致只需要以下一些代碼:
public class AreaControllerFactory : IControllerFactory { public IController CreateController(RequestContext requestContext, string controllerName) { ... } public void ReleaseController(IController controller) { IDisposable disposable = controller as IDisposable; if (disposable != null) { disposable.Dispose(); } } }
然后按照慣例,還是一步步談起。首先是構造函數,我們的策略是根據不同的Area加載不同命名空間下的Controller類型。方便起見,我選擇“基礎命名空間”和“擴展部分”兩塊,它們從構造函數中傳入:
private Dictionary<string, string> m_areaPartMapping = new Dictionary<string, string>(); public string NamespaceBase { get; private set; } public AreaControllerFactory(string namespaceBase) : this(namespaceBase, null) { } public AreaControllerFactory(string namespaceBase, IDictionary<string, string> areaPartMapping) { this.NamespaceBase = namespaceBase.EndsWith(".") ? namespaceBase : namespaceBase + "."; if (areaPartMapping != null) { foreach (var pair in areaPartMapping) { this.m_areaPartMapping.Add(pair.Key.ToLowerInvariant(), pair.Value); } } }
于是我們就可以這樣使用:
var controllerFactory = new AreaControllerFactory("MyApp.Controllers"); ControllerBuilder.Current.SetControllerFactory(controllerFactory);
如果在需要的時候,還可以指定Area與特定命名空間“部分”的映射關系。因此,我們需要從Area來獲取這個“Part”:
private string GetNamespacePart(string area) { if (String.IsNullOrEmpty(area)) return ""; string part; if (this.m_areaPartMapping.TryGetValue(area.ToLowerInvariant(), out part)) { return part; } return area; }
這里我選擇“配置”和“約定”相結合的方式。得到一個Area之后,我們會在映射表里進行查找Part,如果沒有,則Area本身便是Part。根據Part和Controller名稱,我們便可以獲得Controller的類型:
private ReaderWriterLockSlim m_rwLock = new ReaderWriterLockSlim(); private Dictionary<string, Type> m_controllerTypes = new Dictionary<string, Type>(); private Type GetControllerType(string area, string controllerName) { string part = this.GetNamespacePart(area); string typeName = String.IsNullOrEmpty(part) ? this.NamespaceBase + controllerName.ToLowerInvariant() + "Controller" : this.NamespaceBase + part + "." + controllerName.ToLowerInvariant() + "Controller"; Type type; this.m_rwLock.EnterReadLock(); try { if (this.m_controllerTypes.TryGetValue(typeName, out type)) { return type; } } finally { this.m_rwLock.ExitReadLock(); } type = Type.GetType(typeName, false, true); if (type != null) { this.m_rwLock.EnterWriteLock(); try { this.m_controllerTypes[typeName] = type; } finally { this.m_rwLock.ExitWriteLock(); } } return type; }
由于我選擇在應用程序中使用同一個AreaControllerFactory對象,因此線程安全是一定要有保證的。這里我們用到了讀寫鎖,不過請注意,紅色那句話并不保證對于每個相同的typeName只執行一次,也不保證相同的typeName對于m_controllerTypes字典只會進行一次寫操作(所以我沒有Add,而是使用了下標操作)。不過,由于這些“重復”不會造成問題,因此就沒有去涉及太多這方面的考慮。
***,便是那CreateControlle方法:
public IController CreateController(RequestContext requestContext, string controllerName) { Type controllerType; object area; if (requestContext.RouteData.Values.TryGetValue("area", out area)) { controllerType = this.GetControllerType(area.ToString(), controllerName); } else { controllerType = this.GetControllerType(null, controllerName); } if (controllerType == null) { throw new HttpException(404, String.Format( "Controller of path {0} not found.", requestContext.HttpContext.Request.Path)); } try { return (IController)Activator.CreateInstance(controllerType); } catch (Exception ex) { string message = String.Format("Error creating controller {0}" + controllerType); throw new InvalidOperationException(message, ex); } }
似乎沒有什么可談的:我們從RouteData中獲取出area對應的值,并且調用GetControllerType方法獲得Controller的類型,并使用Activator.CreateInstance創建對象。在不合法的情況下,拋出合適的異常即可。
至此,AreaControllerFactory就完成了,很容易,不是嗎?很顯然,這個組件的功能非常有限,例如為什么所有的Controller一定要在同一個命名空間下?沒錯,它其實只是符合“我要求”的一個東西。但是,在項目中很多東西都是如此,我只實現我夠用的功能。例如,我可能不會向對外公開的API那樣,嚴格檢查每個問題,拋出嚴謹的異常。我可能傾向于在項目中使用接口,而不是使用抽象類。因為是我的項目,我可以快速反饋,需要修改的時候就修改吧。
同樣的,如果DefaultControllerFactory真在某些特別情況下有問題,或者支持的有些復雜。那么不如我們就自己動手吧。一次性投入,而且這樣的小組件也花不了多少時間。
關于自定義的ControllerFactory接口如何實現支持Area就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。