[Asp.Net] 使用Enterprise Library Unity Application Block打造輕量級的MVP框架
前言
相信大家對於Asp.Net Mvc框架都不陌生,也已經使用的十分上手了,
它讓開發者能夠以SoC(關注點分離)的方式開發網頁之外,也讓網頁的程式具有可測試性,
但畢竟Asp.Net Mvp屬於比較新的網頁框架,而許多公司現存的許多Asp.Net商業元件都是Web Form版本的,
既想要使用Web Form現成的元件,又希望網頁的程式具有可測試性,那該怎麼辦呢?
因此就嘗試使用Mvp Patterns來發開我們的Asp.Net吧!
(當然Asp.Net Mvc也有技巧可以使用Web Form元件,可參考保哥的與Web Form共舞)
關於Unity Container以及Mvp部分在本文不會深入說明,
還請大家上網瀏覽相關的資訊
實際演練
Mvp Patterns其實就是Mvc的一種變形,通常我們可以在開發Win Form上看到它,
Patterns & Practice團隊也提供了十分完整的Web Client Software Factory框架來讓使用者開發大型網站,
而這邊所使用的Mvp Patterns,主要的精神我們可以用簡單的示意圖來理解
與原型的Mvp比較不一樣的是Presenter透過Interface來對View進行操作,
但View是透過觸發Event的方式來讓Presenter執行對應的動作,
這也避免了在View中直接操作Presenter,降低了View和Presenter之間的耦合性,
MVP三個角色之間都是透過Interface進行交互動作,而實體的部分則交給Unity Container來解決。
首先,我們先建立一個UnityContainerFactory Class,用來當作我們PageBase以及UserControlBase取得UnityContainer的途徑
public class UnityContainerFactory
{
private static IUnityContainer mUnityContainer;
private static readonly object mLocker = new object();
public IUnityContainer GetContainer()
{
if (mUnityContainer == null)
{
lock (mLocker)
{
if (mUnityContainer == null)
{
mUnityContainer = new UnityContainer();
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(mUnityContainer);
ExtendedInterception interception = new ExtendedInterception();
interception.Interceptor = new TransparentProxyInterceptor();
mUnityContainer.AddExtension(interception);
}
}
}
return mUnityContainer;
}
}
ExtendedInterception是參考Artech大大的文章Enterprise Library深入解析與靈活應用(7):再談PIAB與Unity之間的集成
再來替我們的View和Presenter建立Interface
public interface IPresenterBase<IView>
{
IView View { get; set; }
}
public interface IViewBase
{
event EventHandler<IsPostBackEventArgs> ViewLoad;
}
與Winform不同的是,我們會多需要判斷頁面是否Postback過,
所以在這邊使用了一個內含bool的EventArgs
public class IsPostBackEventArgs : EventArgs
{
public bool IsPostBack { get; private set; }
public IsPostBackEventArgs(bool isPostBack)
{
this.IsPostBack = isPostBack;
}
}
接著替我們的Presenter建立一個共同的抽象類別,當Presenter的View被設定時,
呼叫一個OnViewSet的抽象方法,繼承之後的Class可以在這邊掛載View的Event
public abstract class Presenter<IView> : IPresenterBase<IView>
{
private IView mView;
public IView View
{
get
{
return mView;
}
set
{
mView = value;
this.OnViewSet();
}
}
protected abstract void OnViewSet();
}
我們也幫所有的Page和UserControl建立Base Class,而該頁所擁有的Presenter也是在這邊進行派發
public abstract class PageBase<TView, TPresenter> : Page
where TPresenter : IPresenterBase<TView>
where TView : class
{
protected TPresenter Presenter { get; private set; }
protected override void OnPreInit(EventArgs e)
{
InjectDependencies();
base.OnPreInit(e);
}
protected virtual void InjectDependencies()
{
UnityContainerFactory factory = new UnityContainerFactory();
var container = factory.GetContainer();
Presenter = container.Resolve<TPresenter>();
Presenter.View = this as TView;
}
}
public abstract class UserControlBase<TView, TPresenter> : UserControl
where TPresenter : IPresenterBase<TView>
where TView : class
{
protected TPresenter Presenter { get; private set; }
protected override void OnInit(EventArgs e)
{
InjectDependencies();
base.OnInit(e);
}
protected virtual void InjectDependencies()
{
UnityContainerFactory factory = new UnityContainerFactory();
var container = factory.GetContainer();
Presenter = container.Resolve<TPresenter>();
Presenter.View = this as TView;
}
}
到這邊為止,我們就初步的完成我們的框架囉!
接下來,讓我們實際練習一下如何使用這個框架,
首先開發我們的Service
public interface ITimeService
{
DateTime GetCurrentTime();
}
public class TimeService:ITimeService
{
public DateTime GetCurrentTime()
{
return DateTime.Now;
}
}
再來定義我們View的Interface
public interface IDisplayTimeView : IViewBase
{
DateTime DisplayTime { set; }
}
有了IView之後,我們就可以來撰寫Presenter的邏輯
public class DisplayTimeViewPresenter : PresenterBase<IDisplayTimeView>
{
[Dependency]
public ITimeService Service { get; set; }
protected override void OnViewSet()
{
this.View.ViewLoad +=
(sender, args) =>
{
this.View.DisplayTime = this.Service.GetCurrentTime();
};
}
}
可以特別注意到ITimeService上有Dependency,到時候會使用UnityContainer自動注入對應的實體Class
接下來我們可以在網站中建立View如下
public partial class _Default : PageBase<IDisplayTimeView, IPresenterBase<IDisplayTimeView>>, IDisplayTimeView
{
protected void Page_Load(object sender, EventArgs e)
{
if (ViewLoad != null)
{
this.ViewLoad(sender, new IsPostBackEventArgs(IsPostBack));
}
}
public DateTime DisplayTime
{
set { Label_DisplayTime.Text = value.ToLongTimeString() ; }
}
public event EventHandler<IsPostBackEventArgs> ViewLoad;
}
然後我們必須要在config中定義Interface和實體class的關係
首先在<configSections>區段加入這行
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
接著加入下列設定,需注意Namespace是否正確
<unity>
<typeAliases>
<typeAlias alias="string" type="System.String, mscorlib" />
<typeAlias alias="ITimeService" type="Bll.ITimeService,Bll" />
<typeAlias alias="TimeService" type="Bll.TimeService,Bll" />
<typeAlias alias="IDisplayTimeViewPresenter" type="Presenter.IPresenterBase`1[[Presenter.IDisplayTimeView, Presenter]],Presenter" />
<typeAlias alias="DisplayTimeViewPresenter" type="Presenter.DisplayTimeViewPresenter,Presenter" />
</typeAliases>
<containers>
<container>
<types>
<type type="ITimeService" mapTo="TimeService" />
<type type="IDisplayTimeViewPresenter" mapTo="DisplayTimeViewPresenter" />
</types>
</container>
</containers>
</unity>
執行之後我們可以看到頁面正確的顯示現在時間,按下Refresh鍵也立即更新
如果今天,我們想在TimeService加入快取的話只需要在Class上加上一行
(當然需要Reference需要的dll)
[CachingCallHandler]
public class TimeService:ITimeService
{
public DateTime GetCurrentTime()
{
return DateTime.Now;
}
}
再次執行程式,我們可以發現時間被快取了
結語
透過Enterprise Library Unity Application Block建立了輕量級的Asp.Net Mvp 框架,
不但讓我們的網頁的邏輯具有可測試性,也把IoC以及Policy Injection的優點帶了進來,
或許在整體網站的架構顯得複雜了一些,但卻大大的增加程式的彈性以及擴充性,
歡迎大家多多的提出問題以及分享心得,也請大家多多指教 ^_^
(本文所使用的Enterprise Library版本為4.1)
參考文章:
3. Enterprise Library深入解析与?活?用(3):倘若?Unity、PIAB、Exception Handling引入MVP模式