ASP.NET 设计模式 MVP

前言

ASP.NET webform 使用后置代码页面隔离业务逻辑。但使用ASP.NET webform 做应用开发会存在以下不足:

  1. 后置代码页中混合了表现层、业务逻辑层、数据访问层的代码。出现这种情况的原因是后置代码负责事件触发,流程控制,业务规则和表现逻辑,业务逻辑和数据访问的协调者等多种角色。
  2. 后置代码页测试不方便。

Model-View-Presenter模式

Model-View-Presenter(模型-视图-呈现器,MVP)模式的重点是让呈现器控制整个表示层的逻辑流。从层次上来讲,MVP属于表现层的设计模式。MVP模式由如下三个不同的部分组成:

  1. Model:view展示或修改的业务数据。Model是对应用状态和业务功能的封装,可以看成是同时包含数据和行为的领域模型(Domain Model)
  2. View:实现可视化界面的呈现,将用户的输入委托给Presenter 。一般地,View会实现一个相应的接口。
  3. Presenter:Model和View的枢纽

MVP具有很多变体,其中最为常用的一种变体为Passive View(被动视图)。Passive View,Model、View和Presenter之间的关系如下图所示

clip_image002[4]_thumb

图片来源

View和Model之间不能直接交互,之间的交互是通过Presenter进行的

Presenter接受View的UI请求,Presenter与Model进行通信,检索View的业务数据,完成后,Presenter更新View

View直接依赖于Presenter,Presenter间接依赖View,Presenter直接依赖的是View实现的接口

Passive View模式的基本特征总结

以下引用自Artech的博文谈谈关于MVP模式中V-P交互问题

Passive View,顾名思义,View是被动的。那么主动是谁呢?答案是Presenter。对于Presenter的主动性,我个人是这么理解的:

  • Presenter是整个MVP体系的控制中心,而不是单纯的处理View请求的人;
  • View仅仅是用户交互请求的汇报者,对于响应用户交互相关的逻辑和流程,View不参与决策,真正的决策者是Presenter;
  • View向Presenter发送用户交互请求应该采用这样的口吻:“我现在将用户交互请求发送给你,你看着办,需要我的时候我会协助你”,不应该是这样:“我现在处理用户交互请求了,我知道该怎么办,但是我需要你的支持,因为实现业务逻辑的Model只信任你”;
  • 对于绑定到View上的数据,不应该是View从Presenter上“拉”回来的,应该是Presenter主动“推”给View的;
  • View尽可能不维护数据状态,因为其本身仅仅实现单纯的、独立的UI操作;Presenter才是整个体系的协调者,它根据处理用于交互的逻辑给View和Model安排工作。

实现一个完整的demo(代码有点多,后面给出源程序压缩包)

1、建立领域模型层

建立Model类库项目

model

Post类

   1:  [ActiveRecord("Posts")]
   2:  public class Post:ActiveRecordBase<Post>
   3:  {
   4:      [PrimaryKey("PostId")]
   5:      public int Id { get; set; }
   6:   
   7:      [Property]
   8:      public string Subject { get; set; }
   9:   
  10:      [Property]
  11:      public string Text { get; set; }
  12:   
  13:      [Property]
  14:      public DateTime DateAdded { get; set; }
  15:   
  16:      [BelongsTo("CategoryId")]
  17:      public Category Category { get; set; }
  18:   
  19:      [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Inverse = true)]
  20:      public IList<Comment> Comments { get; set; }
  21:   
  22:  }

Category类

   1:  [ActiveRecord("Categories")]
   2:  public class Category:ActiveRecordBase<Category>
   3:  {
   4:      [PrimaryKey("CategoryId")]
   5:      public int Id { get; set; }
   6:   
   7:      [Property(Column = "Category")]
   8:      public string CategoryName { get; set; }
   9:   
  10:      [HasMany]
  11:      public IList<Post> Posts { get; set; }
  12:  }

Comment类

   1:  [ActiveRecord("Comments")]
   2:  public class Comment : ActiveRecordBase<Comment>
   3:  {
   4:      [PrimaryKey("CommentId")]
   5:      public int Id { get; set; }
   6:   
   7:      [Property]
   8:      public string Text { get; set; }
   9:   
  10:      [Property]
  11:      public string Author { get; set; }
  12:   
  13:      [Property]
  14:      public DateTime DateAdded { get; set; }
  15:   
  16:      [BelongsTo("PostId")]
  17:      public Post Post { get; set; }
  18:   
  19:  }

添加资源库的契约接口,该接口为业务实体持久化提供了标准方法

IRepository接口

   1:  public interface IRepository<T> where T:class
   2:  {
   3:      T Find(int id);
   4:      void Add(T entity);
   5:      void Save(T entity);
   6:      void Remove(T entity);
   7:  }

ICategoryRepository接口

   1:  public interface ICategoryRepository : IRepository<Category>
   2:  {
   3:      IEnumerable<Category> FindAll();
   4:  }

IPostRepository接口

   1:  public interface IPostRepository:IRepository<Post>
   2:  {
   3:      IEnumerable<Post> FindAll();
   4:      IEnumerable<Post> FindAll(int index, int count);
   5:      int GetAllPostCount();
   6:      //IEnumerable<Post> FindAll();
   7:      //IEnumerable<Post> FindAll(int index, int count);
   8:      //IEnumerable<Post> FindBy(Query query);
   9:      //IEnumerable<Post> FindBy(Query query, int index, int count);
  10:  }

创建领域服务类PostService

   1:  public class PostService
   2:  {
   3:      private ICategoryRepository _categoryRepository;
   4:      private IPostRepository _postRepository;
   5:      public PostService(ICategoryRepository categoryRepository,IPostRepository postRepository)
   6:      {
   7:          _categoryRepository = categoryRepository;
   8:          _postRepository = postRepository;
   9:      }
  10:      public IList<Post> GetAllPostsIn(int categoryId)
  11:      {
  12:          return _categoryRepository.Find(categoryId).Posts;
  13:      }
  14:      public IList<Comment> GetPostAllComments(int postId)
  15:      {
  16:          return _postRepository.Find(postId).Comments;
  17:      }
  18:   
  19:      public int GetAllPostCount()
  20:      {
  21:   
  22:          return _postRepository.GetAllPostCount();
  23:      }
  24:  }

2、建立资源库(repository)

repository

   1:  /// <summary>
   2:  /// CategoryRepository
   3:  /// </summary>
   4:   
   5:  public class CategoryRepository : ICategoryRepository
   6:  {
   7:      public IEnumerable<Category> FindAll()
   8:      {
   9:          return Category.FindAll();
  10:      }
  11:   
  12:      public Category Find(int id)
  13:      {
  14:          return Category.Find(id);
  15:      }
  16:   
  17:      public void Add(Category entity)
  18:      {
  19:          entity.Create();
  20:      }
  21:   
  22:      public void Save(Category entity)
  23:      {
  24:          entity.Save();
  25:      }
  26:   
  27:      public void Remove(Category entity)
  28:      {
  29:          entity.Delete();
  30:      }
  31:  }
  32:   
  33:   
  34:  /// <summary>
  35:  /// CommentRepository
  36:  /// </summary>
  37:  public class CommentRepository : ICommentRepository
  38:  {
  39:      public Comment Find(int id)
  40:      {
  41:          return Comment.Find(id);
  42:      }
  43:   
  44:      public void Add(Comment entity)
  45:      {
  46:          entity.Create();
  47:      }
  48:   
  49:      public void Save(Comment entity)
  50:      {
  51:          entity.Save();
  52:      }
  53:   
  54:      public void Remove(Comment entity)
  55:      {
  56:          entity.Delete();
  57:      }
  58:  }
  59:   
  60:   
  61:  /// <summary>
  62:  /// PostRepository
  63:  /// </summary>
  64:  public class PostRepository : IPostRepository
  65:  {
  66:      public IEnumerable<Post> FindAll()
  67:      {
  68:          return Post.FindAll();
  69:      }
  70:   
  71:      public IEnumerable<Post> FindAll(int index, int count)
  72:      {
  73:          return Post.FindAll().Skip(index * count).Take(count);
  74:      }
  75:   
  76:      public Post Find(int id)
  77:      {
  78:          return Post.Find(id);
  79:      }
  80:   
  81:      public void Add(Post entity)
  82:      {
  83:          entity.Create();
  84:      }
  85:   
  86:      public void Save(Post entity)
  87:      {
  88:          entity.Save();
  89:      }
  90:   
  91:      public void Remove(Post entity)
  92:      {
  93:          entity.Delete();
  94:      }
  95:   
  96:      public int GetAllPostCount()
  97:      {
  98:          ScalarQuery query = new ScalarQuery(typeof(Post), @"select count(post.Category) from Post post)");
  99:          
 100:          return int.Parse(Post.ExecuteQuery(query).ToString());
 101:      }
 102:  }
 103:   

建立service层

   1:  public class BlogService
   2:  {
   3:      private IPostRepository _postRepository;
   4:      private ICommentRepository _commentRepository;
   5:      private ICategoryRepository _categoryRepository;
   6:      private PostService _service;
   7:   
   8:      public BlogService(ICategoryRepository categoryRepository, IPostRepository postRepository, ICommentRepository commentRepository)
   9:      {
  10:          _service = new PostService(categoryRepository, postRepository);
  11:          _categoryRepository = categoryRepository;
  12:          _postRepository = postRepository;
  13:          _commentRepository = commentRepository;
  14:      }
  15:      #region Post
  16:          public IEnumerable<Post> FindAllPost()
  17:          {
  18:              return _postRepository.FindAll();
  19:          }
  20:   
  21:          public IEnumerable<Post> FindAllPost(int index, int count)
  22:          {
  23:              return _postRepository.FindAll().Skip(index * count).Take(count);
  24:          }
  25:   
  26:          public Post FindPostBy(int id)
  27:          {
  28:              return _postRepository.Find(id);
  29:          }
  30:   
  31:          public void AddPost(Post entity)
  32:          {
  33:              _postRepository.Add(entity);
  34:          }
  35:   
  36:          public void SavePost(Post entity)
  37:          {
  38:              _postRepository.Save(entity);
  39:          }
  40:   
  41:          public void RemovePost(Post entity)
  42:          {
  43:              _postRepository.Remove(entity);
  44:          }
  45:   
  46:          public int GetAllPostCount()
  47:          {
  48:              return _postRepository.GetAllPostCount();
  49:          }
  50:   
  51:          public IList<Post> GetAllPostsIn(int categoryId)
  52:          {
  53:              return _service.GetAllPostsIn(categoryId);
  54:          }
  55:   
  56:          #endregion
  57:   
  58:      #region Category
  59:   
  60:          public IEnumerable<Category> FindAllCategories()
  61:          {
  62:              return _categoryRepository.FindAll();
  63:          }
  64:   
  65:          public Category FindCategoryBy(int id)
  66:          {
  67:              return _categoryRepository.Find(id);
  68:          }
  69:   
  70:          public void AddCategory(Category entity)
  71:          {
  72:              _categoryRepository.Add(entity);
  73:          }
  74:   
  75:          public void SaveCategory(Category entity)
  76:          {
  77:              _categoryRepository.Save(entity);
  78:          }
  79:   
  80:          public void RemoveCategory(Category entity)
  81:          {
  82:              _categoryRepository.Remove(entity);
  83:          }
  84:   
  85:          #endregion
  86:   
  87:      #region Comment
  88:   
  89:          public Comment FindComment(int id)
  90:          {
  91:              return _commentRepository.Find(id);
  92:          }
  93:   
  94:          public void AddComment(Comment entity)
  95:          {
  96:              _commentRepository.Add(entity);
  97:          }
  98:   
  99:          public void SaveComment(Comment entity)
 100:          {
 101:              _commentRepository.Save(entity);
 102:          }
 103:   
 104:          public void RemoveComment(Comment entity)
 105:          {
 106:              _commentRepository.Remove(entity);
 107:          }
 108:   
 109:          #endregion
 110:  }

创建Presenter层

添加IHomeView、IPostDetailView、ICategoryPostView接口(View接口解耦了Presenter与View之间的依赖)

   1:  /// <summary>
   2:  /// IHomeView
   3:  /// </summary>
   4:  public interface IHomeView
   5:  {
   6:      IEnumerable<Category> CategoryList { set; }
   7:   
   8:      IEnumerable<Post> Post { set; }
   9:   
  10:      int PostCount { set; }
  11:  }
  12:   
  13:  /// <summary>
  14:  /// IPostDetailView
  15:  /// </summary>
  16:  public interface IPostDetailView
  17:  {
  18:      IEnumerable<Category> CategoryList { set; }
  19:      Post Post { set; }
  20:      IList<Comment> Comments { set; }
  21:      event EventHandler<CommentEventArgs> NewComment;
  22:  }
  23:   
  24:   
  25:  /// <summary>
  26:  /// ICategoryPostView
  27:  /// </summary>
  28:  public interface ICategoryPostView
  29:  {
  30:      IEnumerable<Category> CategoryList { set; }
  31:   
  32:      IEnumerable<Post> Post { set; }
  33:  }
  34:   
  35:   
  36:   

添加相应的呈现器HomePresenter、PostDetailPresenter、CategoryPostPresenter

   1:  /// <summary>
   2:  /// HomePresenter
   3:  /// </summary>
   4:  public class HomePresenter:IPresenter
   5:  {
   6:      private IHomeView _homeView;
   7:      private BlogService _service;
   8:      public HomePresenter(IHomeView homeView,BlogService service)
   9:      {
  10:          _homeView = homeView;
  11:          _service = service;
  12:      }
  13:      public void Display()
  14:      {
  15:          _homeView.CategoryList = _service.FindAllCategories();
  16:          _homeView.Post = _service.FindAllPost(0, 10);
  17:          _homeView.PostCount = _service.GetAllPostCount();
  18:      }
  19:  }
  20:   
  21:   
  22:  /// <summary>
  23:  /// PostDetailPresenter
  24:  /// </summary>
  25:  public class PostDetailPresenter : IPresenter
  26:  {
  27:      private IPostDetailView _postDetailView;
  28:      private BlogService _service;
  29:      private int _postId;
  30:      public PostDetailPresenter(IPostDetailView postDetailView, BlogService service, int postId)
  31:      {
  32:          _postDetailView = postDetailView;
  33:          _service = service;
  34:          _postId = postId;
  35:          _postDetailView.NewComment += new EventHandler<Event.CommentEventArgs>(_postDetailView_NewComment);
  36:      }
  37:   
  38:      void _postDetailView_NewComment(object sender, Event.CommentEventArgs e)
  39:      {
  40:          Comment comment=new Comment ();
  41:          comment.Author=e.CommentAuthor;
  42:          comment.Text=e.CommentText;
  43:          comment.DateAdded = DateTime.Now;
  44:          comment.Post = _service.FindPostBy(_postId);
  45:          _service.AddComment(comment);
  46:      }
  47:      public void Display()
  48:      {
  49:          _postDetailView.CategoryList = _service.FindAllCategories();
  50:          _postDetailView.Post = _service.FindPostBy(_postId);
  51:          _postDetailView.Comments = _service.FindPostBy(_postId).Comments;
  52:      }
  53:  }
  54:   
  55:   
  56:  /// <summary>
  57:  /// CategoryPostPresenter
  58:  /// </summary>
  59:  public class CategoryPostPresenter : IPresenter
  60:  {
  61:      private ICategoryPostView _categoryPostView;
  62:      private BlogService _service;
  63:      private int _categoryId;
  64:      public CategoryPostPresenter(ICategoryPostView categoryPostView, BlogService service, int categoryId)
  65:      {
  66:          _categoryPostView = categoryPostView;
  67:          _service = service;
  68:          _categoryId = categoryId;
  69:      }
  70:      public void Display()
  71:      {
  72:          _categoryPostView.CategoryList = _service.FindAllCategories();
  73:          _categoryPostView.Post = _service.GetAllPostsIn(_categoryId);
  74:      }
  75:  }

三个呈现器实现共同的呈现接口IPresenter

   1:  public interface IPresenter
   2:  {
   3:      void Display();
   4:  }

创建web应用程序 作为view

index.aspx.cs

   1:  public partial class Index : System.Web.UI.Page, IHomeView
   2:  {
   3:      IPresenter _presenter;
   4:      int _pageCount;
   5:      protected override void OnPreInit(EventArgs e)
   6:      {
   7:   
   8:          _presenter = new HomePresenter(this, ObjectFactory.GetInstance<BlogService>());
   9:          base.OnPreInit(e);
  10:      }
  11:      protected void Page_Load(object sender, EventArgs e)
  12:      {
  13:          _presenter.Display();
  14:      }
  15:   
  16:      public IEnumerable<Model.Category> CategoryList
  17:      {
  18:          set
  19:          {
  20:              
  21:              rptCategory.DataSource = value;
  22:              rptCategory.DataBind();
  23:          }
  24:      }
  25:   
  26:      public IEnumerable<Model.Post> Post
  27:      {
  28:          set
  29:          {
  30:              rptPostList.DataSource = value;
  31:              rptPostList.DataBind();
  32:          }
  33:      }
  34:   
  35:      public int PostCount
  36:      {
  37:          set { _pageCount = value; }
  38:      }
  39:  }

CategoryPost.aspx.cs

   1:  public partial class CategoryPost : System.Web.UI.Page, ICategoryPostView
   2:  {
   3:      IPresenter _presenter;
   4:      protected override void OnPreInit(EventArgs e)
   5:      {
   6:          int categoryId;
   7:          int.TryParse(Request.QueryString["categoryid"], out categoryId);
   8:   
   9:          _presenter = new CategoryPostPresenter(this, ObjectFactory.GetInstance<BlogService>(), categoryId);
  10:          base.OnPreInit(e);
  11:      }
  12:      protected void Page_Load(object sender, EventArgs e)
  13:      {
  14:          _presenter.Display();
  15:      }
  16:   
  17:      public IEnumerable<Model.Category> CategoryList
  18:      {
  19:          set
  20:          {
  21:              rptCategory.DataSource = value;
  22:              rptCategory.DataBind();
  23:          }
  24:      }
  25:   
  26:      public IEnumerable<Model.Post> Post
  27:      {
  28:          set
  29:          {
  30:              rptPostList.DataSource = value;
  31:              rptPostList.DataBind();
  32:          }
  33:      }
  34:  }

PostDetail.aspx.cs

   1:  public partial class PostDetail : System.Web.UI.Page,IPostDetailView
   2:  {
   3:      IPresenter _presenter;
   4:      public event EventHandler<CommentEventArgs> NewComment;
   5:      protected virtual void OnNewComment(CommentEventArgs e)
   6:      {
   7:          if (NewComment != null)
   8:          {
   9:              NewComment(this, e);
  10:          }
  11:      }   
  12:   
  13:      protected override void OnPreInit(EventArgs e)
  14:      {
  15:          int postId;
  16:          int.TryParse(Request.QueryString["postId"], out postId);
  17:   
  18:          _presenter = new PostDetailPresenter(this, ObjectFactory.GetInstance<BlogService>(), postId);
  19:          base.OnPreInit(e);
  20:      }
  21:      protected void Page_Load(object sender, EventArgs e)
  22:      {
  23:          _presenter.Display();
  24:      }
  25:   
  26:      public IEnumerable<Model.Category> CategoryList
  27:      {
  28:          set
  29:          {
  30:              rptCategory.DataSource = value;
  31:              rptCategory.DataBind();
  32:          }
  33:      }
  34:   
  35:      public Model.Post Post
  36:      {
  37:          set {
  38:              lbltitle.Text = value.Subject;
  39:              lblDateAdded.Text = value.DateAdded.ToShortDateString();
  40:              litContent.Text = value.Text;
  41:          }
  42:      }
  43:   
  44:      public IList<Model.Comment> Comments
  45:      {
  46:          set
  47:          {
  48:              rptCommentList.DataSource = value;
  49:              rptCommentList.DataBind();
  50:          }
  51:      }
  52:   
  53:      protected void btnCommit_Click(object sender, EventArgs e)
  54:      {
  55:          int postId;
  56:          int.TryParse(Request.QueryString["postid"], out postId);
  57:          
  58:          CommentEventArgs eventArgs = new CommentEventArgs(txtText.Text, txtAuthor.Text,postId);
  59:          OnNewComment(eventArgs);
  60:      }    
  61:  }

MVP模式中的顺序图

sequencediagram

下载地址:源代码下载

参考资料:谈谈关于MVP模式中V-P交互问题

     Professional ASP.NET Design Patterns

     在ASP.NET中实现Model-View-Presenter(MVP)

     使用ASP.NET实现Model View Presenter(MVP)

原文地址:https://www.cnblogs.com/whx1973/p/2806480.html