[Asp.Net] 使用Enterprise Library Unity Application Block打造輕量級的MVP框架

  • 7117
  • 0

[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,主要的精神我們可以用簡單的示意圖來理解

Asp.Net Mvp

與原型的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鍵也立即更新

45

46

如果今天,我們想在TimeService加入快取的話只需要在Class上加上一行

(當然需要Reference需要的dll)

    [CachingCallHandler]
    public class TimeService:ITimeService
    {        
        public DateTime GetCurrentTime()
        {
            return DateTime.Now;
        }        
    }

 

 

再次執行程式,我們可以發現時間被快取了

47

47
 

結語


透過Enterprise Library Unity Application Block建立了輕量級的Asp.Net Mvp 框架,

不但讓我們的網頁的邏輯具有可測試性,也把IoC以及Policy Injection的優點帶了進來,

或許在整體網站的架構顯得複雜了一些,但卻大大的增加程式的彈性以及擴充性,

歡迎大家多多的提出問題以及分享心得,也請大家多多指教 ^_^

(本文所使用的Enterprise Library版本為4.1)

 

參考文章:

1. 談談關於MVP模式中V-P交互問題

2. Asp.Net開發架構設計(二)

3. Enterprise Library深入解析与?活?用(3):倘若?Unity、PIAB、Exception Handling引入MVP模式

4. Model View Presenter with ASP.NET