zoukankan      html  css  js  c++  java
  • 探索MVP(Model-View-Presenter)设计模式在SharePoint平台下的实现

    探索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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    [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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    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
    • 关键点来了,在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等。相信构建高效清晰整洁的代码是每个程序员所追求的,你不得不佩服国外大神们总结的设计模式是多么的精妙,或许怀着敬畏的心才能慢慢体会其中的奥秘。点击此处下载源代码

  • 相关阅读:
    jquery 回车切换 tab功能
    Jtemplates 基本语法
    Rdlc报表出现空白页解决方法
    动软代码生成与 EntityFramework 实体生成模板
    windows 无法启动asp.net 状态服务 错误 0x8007277a
    导出Excel Gridview
    错误提示:类型“GridView”的控件“GridView1”必须放在具有 runat=server 的窗体标记内 .
    c#与vb.net在App_Code里面编译要通过,需要以下web.config的配置
    個人最近做的最多的重複工作就是excel导出
    Js/Jquery获取iframe中的元素 在Iframe中获取父窗体的元素方法
  • 原文地址:https://www.cnblogs.com/ningang/p/4320594.html
Copyright © 2011-2022 走看看