ASP.NET 设计模式 MVP
前言
ASP.NET webform 使用后置代码页面隔离业务逻辑。但使用ASP.NET webform 做应用开发会存在以下不足:
- 后置代码页中混合了表现层、业务逻辑层、数据访问层的代码。出现这种情况的原因是后置代码负责事件触发,流程控制,业务规则和表现逻辑,业务逻辑和数据访问的协调者等多种角色。
- 后置代码页测试不方便。
Model-View-Presenter模式
Model-View-Presenter(模型-视图-呈现器,MVP)模式的重点是让呈现器控制整个表示层的逻辑流。从层次上来讲,MVP属于表现层的设计模式。MVP模式由如下三个不同的部分组成:
- Model:view展示或修改的业务数据。Model是对应用状态和业务功能的封装,可以看成是同时包含数据和行为的领域模型(Domain Model)
- View:实现可视化界面的呈现,将用户的输入委托给Presenter 。一般地,View会实现一个相应的接口。
- Presenter:Model和View的枢纽
MVP具有很多变体,其中最为常用的一种变体为Passive View(被动视图)。Passive View,Model、View和Presenter之间的关系如下图所示
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类库项目
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)
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模式中的顺序图
下载地址:源代码下载
参考资料:谈谈关于MVP模式中V-P交互问题
Professional ASP.NET Design Patterns
在ASP.NET中实现Model-View-Presenter(MVP)
使用ASP.NET实现Model View Presenter(MVP)