探索MVP(Model-View-Presenter)設計模式在SharePoint平台下的實現


 

對於SharePoint Developers來說,往往會過多的去關注SharePoint平台和工具,而把設計模式和代碼的可測試性放在了一個較低的優先級。這並不是說SharePoint Developers對設計模式不感興趣,而是缺乏在SharePoint平台下使用設計模式的經驗。所以本篇Blog正如題目所示:探索MVP(Model-View-Presenter)設計模式在SharePoint平台下的實現。利用MVP設計模式,可以盡量讓我們的項目分離關注點、易測試、可重用。在實現MVP時,我也會加入Repository和Service Locator這兩種設計模式,Repository可以理解為一個倉儲,相當於數據訪問層(DAL),而Service Locator扮演了IoC角色,IoC類似一個工廠(容器),工廠內部注冊了很多依賴關系,IoC容器正式使用這種依賴關系從而動態的注入(又稱依賴注入)提供你所需要的實例,這樣可以有效的實現解耦,即分離關注點。

 

MVP模式

在SharePoint平台下,如開發SharePoint Farm Solution,如果不對代碼進行重構,往往會出現這樣的代碼:

很明顯這樣把所有的邏輯都雜揉在UI Logic,特別是在團隊開發時,即不利於測試,也不利於分工協作。而且對於SharePoint而言,開發機性能若低,調試是苦不堪言的,其耗時難以想象。所以前期如能通過單元測試解決Bug,將大大的節約時間。幸運的是,MVP設計模式的出現,對於Web Part的開發,是非常適合的。MVP的特點是很好的分離了關注點,各司其職。把上圖稍作更改如下所示:

可以看到的是UI Logic處理的業務邏輯交給了Presenter,而UI徹底解放了,只單純的做顯示層(View)。

 

Repository Design Pattern

從上圖可以看出,Presenter並不是直接去訪問SharePoint數據層( SharePoint List),而是通過了一個Repository 去間接訪問,而Repository Model 封裝了數據層。

到這一步,看似完美,但實則還是在原地踏步。因為Presenter和Repository還是緊耦合着,這就好像負責Presenter的 A程序員必須要等負責Repository 的B程序員完成才能工作。

誰叫他們緊耦合在一起呢?

在團隊開發中,我們需要的是互相獨立,所以需要讓負責Presenter的程序員可以使用MockRepository來做測試,這樣就不會影響進度了,幸運的是,基於接口的設計,可以讓我完成這個願景。具體的實現如下:

SharePoint Service Locator Design Pattern

仔細分析上圖,Presenter還是沒有解耦,因為這必須要在Presenter中把某個Repository的實例創建出來,所以Presenter還是依賴了Repository這個項目程序集。這對測試沒有好處,(正如前面所分析的那樣,開發Presenter 的A程序員必須可以在單元測試里使用MockRepository來測試,而在真實的項目里使用B 程序員開發的AnyRepository)。

那么有沒有一種方式能徹底將Presenter和Repository解耦呢?

當然有,如依賴注入,本篇博客介紹的是由Microsoft Patterns and Practices 專門為SharePoint開發的IoC容器:SharePoint Service Locator。

什么是IoC容器

傳統的控制流,從客戶端創建服務時,必須指定一個特定服務實現(並且對服務的程序集添加引用),IoC容器所做的就是完全將這種關系倒置過來(倒置給IoC容器),將服務注入到客戶端代碼中,這是一種推得方式(依賴注入)。術語"控制反轉",即客戶放棄代碼的控制,將其交給IoC容器,也就是將控制從客戶端代碼倒置給容器,所以又有人稱作好萊塢原則"不要打電話過來,我們打給你"。實際上,IoC就是使用IoC容器將傳統的控制流(客戶端創建服務)倒置過來,將服務注入到客戶端代碼中。

總之一句話,客戶端代碼能夠只依賴接口或者抽象類或基類或其他,而不關心運行時由誰來提供具體實現。

使用IoC容器如SharePoint Service Locator,首先配置依賴關系(即當向Ioc容器詢問特定的類型時將返回一個具體的實現),所以這又叫依賴注入。

MVP在項目中的實踐

有了上面的分析,那么就來設計漂亮的代碼:

  • 模塊化代碼
  • 松耦合,無依賴
  • 代碼重用
  • 獨立的單元測試
  •  首先創建IVew,單純的給UI界面"取"數據和"顯示"數據
  public interface IEmployeeView
    {
        string Country { get; }
        IEnumerable<EmployeeModel> EmplyeeList { set; }
        bool NotEmployeesFoundMessageVisible { set; }
    }
    • 接着WebPart實現IView
[ToolboxItemAttribute(false)]
    public partial class VisualWebPart1 : WebPart,IEmployeeView
    {
        // Uncomment the following SecurityPermission attribute only when doing Performance Profiling on a farm solution
        // using the Instrumentation method, and then remove the SecurityPermission attribute when the code is ready
        // for production. Because the SecurityPermission attribute bypasses the security check for callers of
        // your constructor, it's not recommended for production purposes.
        // [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Assert, UnmanagedCode = true)]
        private EmployeePresenter _presenter;
        public VisualWebPart1()
        {
            IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent(SPContext.Current.Site);
            IEmployeeRepository employeeRepository = serviceLocator.GetInstance<IEmployeeRepository>();
            _presenter = new EmployeePresenter(this, employeeRepository);
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            InitializeControl();
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            _presenter.GetEmployees();
        }

        public string Country
        {
            get { return HttpContext.Current.Request["country"] }
        }

        public IEnumerable<Model.EmployeeModel> EmplyeeList
        {
            set 
            {
                rptDataSource.DataSource = value;
                rptDataSource.DataBind();
            }
        }

        public bool NotEmployeesFoundMessageVisible
        {
            set { lblMessage.Visible = value; }
        }
    }
  • 接着對BaseRepository的設計
public abstract class BaseRepository<T>
    {
        protected SPWeb _web;
        public BaseRepository()
        {

        }
        public BaseRepository(SPWeb web)
        {
            _web = web;
        }
        protected IEnumerable<T> GetEntities(SPListItemCollection items)
        {
            List<T> list =null;
            if (items.Count>0)
            {
                list = new List<T>();
                foreach (SPListItem item in items)
                {
                    list.Add(GetEntity(item));
                }
            }
           
            return list;
        }
        protected abstract T GetEntity(SPListItem item);
    }
  • 正如前面分析的那樣,基於接口的設計能更好的做單元測試,所以創建IRepository
public interface IEmployeeRepository
    {
        IEnumerable<EmployeeModel> GetEmployeeByCountry(string country);
    }
  • 實現Repository
 public class EmployeeRepository:BaseRepository<EmployeeModel>,IEmployeeRepository
    {
        public EmployeeRepository():base()
        {

        }
        public EmployeeRepository(SPWeb web):base(web)
        {

        }
        public IEnumerable<EmployeeModel> GetEmployeeByCountry(string country)
        {
            SPWeb web = _web ?? SPContext.Current.Web;
            SPList list = web.Lists.TryGetList("Employee");
            IEnumerable<EmployeeModel> employeeEntitiesList = null;
            if (list!=null)
            {
                SPQuery query = new SPQuery();
                query.ViewFields = string.Concat("<FieldRef Name='Title'/>", "<FieldRef Name='CountryField'/>");
                query.ViewFieldsOnly = true;
                if (!string.IsNullOrEmpty(country))
                {
                    query.Query = @"<Where>
                                        <Eq>
                                            <FieldRef Name='CountryField'/>
                                            <Value Type='Lookup'>" + country + @"</Value>
                                        </Eq>
                                    </Where>";
                }
                else
                {
                    query.Query = "";
                }

                SPListItemCollection employeeListColl = list.GetItems(query);
                employeeEntitiesList = GetEntities(employeeListColl);

            }
            return employeeEntitiesList;
        }
        protected override EmployeeModel GetEntity(SPListItem item)
        {

            return new EmployeeModel() {
                Name = item["Title"].ToString(), 
                Country = item["CountryField"].ToString() 
            };
        }
    }
  • 因為Presenter與Repository徹底解耦,故在Presenter中,根據構造函數動態注入View和Repository
 public class EmployeePresenter
    {
        private IEmployeeView _view;
        private IEmployeeRepository _repository;
        public EmployeePresenter(IEmployeeView view,IEmployeeRepository repository) 
        {
            _view = view;
            _repository = repository;
        }
        public void GetEmployees()
        {
           string country= _view.Country;
           if (string.IsNullOrEmpty(country))
           {
               return;
           }

           var employees = _repository.GetEmployeeByCountry(country);
           if (HasEmployeeFound(employees))
           {
               ShowEmployees(employees);
           }
           else
           {
               ShowEmployeeNotFoundMessage();
           }

        }
        private void ShowEmployees(IEnumerable<EmployeeModel> employees)
        {
            _view.EmplyeeList = employees;
            _view.NotEmployeesFoundMessageVisible = false;
        }
        private void ShowEmployeeNotFoundMessage()
        {
            _view.NotEmployeesFoundMessageVisible = true;
        }
        private bool HasEmployeeFound(IEnumerable<EmployeeModel> employees)
        {
            if (employees!=null)
            {
                return employees.Count() > 0;
            }
            return false;
        }
    }
  • 關鍵點來了,在Feature中向SharePoint Service Locator依賴注冊(IRepositoy/Repositoy)
  public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSite site = properties.Feature.Parent as SPSite;
            IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent(site);
            IServiceLocatorConfig serviceLocatorConfig = serviceLocator.GetInstance<IServiceLocatorConfig>();
            serviceLocatorConfig.Site = site;
            serviceLocatorConfig.RegisterTypeMapping<IEmployeeRepository, EmployeeRepository>();   
        }


         //Uncomment the method below to handle the event raised before a feature is deactivated.

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPSite site = properties.Feature.Parent as SPSite;
            IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent(site);
            IServiceLocatorConfig serviceLocatorConfig = serviceLocator.GetInstance<IServiceLocatorConfig>();
            serviceLocatorConfig.Site = site;
            serviceLocatorConfig.RemoveTypeMappings<IEmployeeRepository>();
        }
  • 注意這個Feature 的Scope必須在在Site Level之上(建議在Farm),因為有可能用戶在有權限Deactivate Feature
  • 根據依賴關系動態獲取實例
 private EmployeePresenter _presenter;
        public VisualWebPart1()
        {
            IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent(SPContext.Current.Site);
            IEmployeeRepository employeeRepository = serviceLocator.GetInstance<IEmployeeRepository>();
            _presenter = new EmployeePresenter(this, employeeRepository);
        }

 總結

至此,探索MVP(Model-View-Presenter)設計模式在SharePoint平台下的實現,已經全部結束了,在這個基礎架構上還可以繼續優化,如DataMapper等。相信構建高效清晰整潔的代碼是每個程序員所追求的,你不得不佩服國外大神們總結的設計模式是多么的精妙,或許懷着敬畏的心才能慢慢體會其中的奧秘。點擊此處下載源代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM