如何创建Asp.net MVC ViewModel
Since MVC has been released I have observed much confusion about how best to construct view models. Sometimes this confusion is not without good reason since there does not seem to be a ton of information out there on best practice recommendations. Additionally, there is not a “one size fits all” solution that acts as the silver bullet. In this post, I’ll describe a few of the main patterns that have emerged and the pros/cons of each. It is important to note that many of these patterns have emerged from people solving real-world issues.
Another key point to recognize is that the question of how best to construct view models is *not* unique to the MVC framework. The fact is that even in traditional ASP.NET web forms you have the same issues. The difference is that historically developers haven’t always dealt with it directly in web forms – instead what often happens is that the code-behind files end up as monolithic dumping grounds for code that has no separation of concerns whatsoever and is wiring up view models, performing presentation logic, performing business logic, data access code, and who knows what else. MVC at least facilitates the developer taking a closer look at how to more elegantly implement Separation of Concerns.
Pattern 1 – Domain model object used directly as the view model
Consider a domain model that looks like this:
1: public class Motorcycle
2: {
3: public string Make { get; set; }
4: public string Model { get; set; }
5: public int Year { get; set; }
6: public string VIN { get; set; }
7: }
When we pass this into the view, it of course allows us to write simple HTML helpers in the style of our choosing:
1: <%=Html.TextBox("Make") %>
2: <%=Html.TextBoxFor(m => m.Make) %>
And of course with default model binding we are able to pass that back to the controller when the form is posted:
1: public ActionResult Save(Motorcycle motorcycle)
While this first pattern is simple and clean and elegant, it breaks down fairly quickly for anything but the most trivial views. We are binding directly to our domain model in this instance – this often is not sufficient for fully displaying a view.
Pattern 2 – Dedicated view model that *contains* the domain model object
Staying with the Motorcycle example above, a much more real-world example is that our view needs more than just a Motorcycle object to display properly. For example, the Make and Model will probably be populated from drop down lists. Therefore, a common pattern is to introduce a view model that acts as a container for all objects that our view requires in order to render properly:
1: public class MotorcycleViewModel
2: {
3: public Motorcycle Motorcycle { get; set; }
4: public SelectList MakeList { get; set; }
5: public SelectList ModelList { get; set; }
6: }
In this instance, the controller is typically responsible for making sure MotorcycleViewModel is correctly populated from the appropriate data in the repositories (e.g., getting the Motorcycle from the database, getting the collections of Makes/Models from the database). Our Html Helpers change slightly because they refer to Motorcycle.Make rather than Make directly:
1: <%=Html.DropDownListFor(m => m.Motorcycle.Make, Model.MakeList) %>
When the form is posted, we are still able to have a strongly-typed Save() method:
1: public ActionResult Save([Bind(Prefix = "Motorcycle")]Motorcycle motorcycle)
Note that in this instance we had to use the Bind attribute designating “Motorcycle” as the prefix to the HTML elements we were interested in (i.e., the ones that made up the Motorcycle object).
This pattern is simple and elegant and appropriate in many situations. However, as views become more complicated, it also starts to break down since there is often an impedance mismatch between domain model objects and view model objects.
Pattern 3 – Dedicated view model that contains a custom view model entity
As views get more complicated it is often difficult to keep the domain model object in sync with concerns of the views. In keeping with the example above, suppose we had requirements where we need to present the user a checkbox at the end of the screen if they want to add another motorcycle. When the form is posted, the controller needs to make a determination based on this value to determine which view to show next. The last thing we want to do is to add this property to our domain model since this is strictly a presentation concern. Instead we can create a custom “view model entity” instead of passing the actual Motorcycle domain model object into the view. We’ll call it MotorcycleData:
1: public class MotorcycleData
2: {
3: public string Make { get; set; }
4: public string Model { get; set; }
5: public int Year { get; set; }
6: public string VIN { get; set; }
7: public bool AddAdditionalCycle { get; set; }
8: }
This pattern requires more work and it also requires a “mapping” translation layer to map back and forth between the Motorcycle and MotorcycleData objects but it is often well worth the effort as views get more complex. This pattern is strongly advocated by the authors of MVC in Action (a book a highly recommend). These ideas are further expanded in a post byJimmy Bogard (one of the co-authors) in his post How we do MVC – View Models. I strongly recommended reading Bogard’s post (there are many interesting comments on that post as well). In it he discusses approaches to handling this pattern including using MVC Action filters and AutoMapper (I also recommend checking out AutoMapper).
Let’s continue to build out this pattern without the use of Action filters as an alternative. In real-world scenarios, these view models can get complex fast. Not only do we need to map the data from Motorcycle to MotorcycleData, but we also might have numerous collections that need to be populated for dropdown lists, etc. If we put all of this code in the controller, then the controller will quickly end up with a lot of code dedicated just to building the view model which is not desirable as we want to keep our controllers thin. Therefore, we can introduce a “builder” class that is concerned with building the view model.
1: public class MotorcycleViewModelBuilder
2: {
3: private IMotorcycleRepository motorcycleRepository;
4:
5: public MotorcycleViewModelBuilder(IMotorcycleRepository repository)
6: {
7: this.motorcycleRepository = repository;
8: }
9:
10: public MotorcycleViewModel Build()
11: {
12: // code here to fully build the view model
13: // with methods in the repository
14: }
15: }
This allows our controller code to look something like this:
1: public ActionResult Edit(int id)
2: {
3: var viewModelBuilder = new MotorcycleViewModelBuilder(this.motorcycleRepository);
4: var motorcycleViewModel = viewModelBuilder.Build();
5: return this.View();
6: }
Our views can look pretty much the same as pattern #2 but now we have the comfort of knowing that we’re only passing in the data to the view that we need – no more, no less. When the form is posted back, our controller’s Save() method can now look something like this:
1: public ActionResult Save([Bind(Prefix = "Motorcycle")]MotorcycleData motorcycleData)
2: {
3: var mapper = new MotorcycleMapper(motorcycleData);
4: Motorcycle motorcycle = mapper.Map();
5: this.motorcycleRepository.Save(motorcycle);
6: return this.RedirectToAction("Index");
7: }
Conceptually, this implementation is very similar to Bogard’s post but without the AutoMap attribute. The AutoMap attribute allows us to keep some of this code out of the controller which can be quite nice. One advantage to not using it is that the code inside the controller class is more obvious and explicit. Additionally, our builder and mapper classes might need to build the objects from multiple sources and repositories. Internally in our mapper classes, you can still make great use of tools like AutoMapper.
In many complex real-world cases, some variation of pattern #3 is the best choice as it affords the most flexibility to the developer.
Considerations
How do you determine the best approach to take? Here are some considerations to keep in mind:
Code Re-use – Certainly patterns #1 and #2 lend themselves best to code re-use as you are binding your views directly to your domain model objects. This leads to increased code brevity as mapping layers are not required. However, if your view concerns differ from your domain model (which they often will) options #1 and #2 begin to break down.
Impedance mismatch – Often there is an impedance mismatch between your domain model and the concerns of your view. In these cases, option #3 gives the most flexibility.
Mapping Layer – If custom view entities are used as in option #3, you must ensure you establish a pattern for a mapping layer. Although this means more code that must be written, it gives the most flexibility and there are libraries available such as AutoMapper that make this easier to implement.
Validation – Although there are many ways to perform validation, one of the most common is to use libraries like Data Annotations. Although typical validations (e.g., required fields, etc.) will probably be the same between your domain models and your views, not all validation will always match. Additionally, you may not always be in control of your domain models (e.g., in some enterprises the domain models are exposed via services that UI developers simply consume). So there is a limit to how you can associate validations with those classes. Yes, you can use a separate “meta data” class to designate validations but this duplicates some code similar to how a view model entity from option #3 would anyway. Therefore, option #3 gives you the absolute most control over UI validation.
Conclusion
The following has been a summary of several of the patterns that have emerged in dealing with view models. Although these have all been in the context of ASP.NET MVC, the problem with how best to deal with view models is also an issue with other frameworks like web forms as well. If you are able to bind directly to domain model in simple cases, that is the simplest and easiest solution. However, as your complexity grows, having distinct view models gives you the most overall flexibility.
如何创建Asp.net MVC ViewModel的更多相关文章
- ASP.NET MVC使用Bootstrap系列(5)——创建ASP.NET MVC Bootstrap Helpers
阅读目录 序言 内置的HTML Helpers 创建自定义的Helpers 使用静态方法创建Helpers 使用扩展方法创建Helpers 创建Fluent Helpers 创建自动闭合的Helper ...
- asp.net -mvc框架复习(2)-创建ASP.NET MVC 第一个程序以及MVC项目文件夹说明
建议vs2013或2013以上版本的vs,要是跨平台的话最好用vs2015或vs2017的asp.net mvc core . 1.创建ASP.NET MVC 第一个程序 打开vs2013->文 ...
- 抛开visual studio,纯手工创建asp.net mvc遇到的问题
脱离Visual Studio,只用文本编辑器..NET Framework.IIS Express创建ASP.NET MVC应用时,需要精简~/View目录下web.config文件内容,之前创建的 ...
- 在Windows Azure上创建ASP.NET MVC网站
本篇体验在Windows Azure上创建ASP.NET MVC网站. →登录到Windows Azure管理门户 →点击左下方的"新建" →点击"自定义创建" ...
- 2.2 利用项目模板创建ASP.NET MVC项目
1.启动VS2012,点击“文件|新建|项目”. 2.在新建项目的窗口中,选择ASP.NET MVC 4应用程序. 3.在新ASP.NET MVC 4项目窗口中的“选择模板”列表中选择“基本”模板,在 ...
- Visual Studio 2015创建ASP.NET MVC流程
本文链接:https://blog.csdn.net/begindong/article/details/68059437本人这两天第一次对c#进行系统学习,写出来做个学习记录和大家分享,以下有什么错 ...
- 实战CENTOS6.5安装docker并创建asp.net mvc 5 镜像,运行MVC 网站
Docker,容器,让研发.测试.生产同一环境,可在linux平台上混合使用JAVA与net 程序 Centos6.5安装docker 参考http://my.oschina.net/kcw/blog ...
- VS2013 创建ASP.NET MVC 4.0 未指定的错误(异常来自HRESULT: 0x80004005(e_fail))
这个错误是真的头疼,尝试各种办法都没用,最后解决用的方法是: 找到 vs_ultimate.exe 修复文件,我的文件位置在 C:\ProgramData\Package Cache\{4d78654 ...
- ASP.NET MVC 5 03 - 安装MVC5并创建第一个应用程序
不知不觉 又逢年底, 穷的钞票 所剩无几. 朋友圈里 各种装逼, 抹抹眼泪 MVC 继续走起.. 本系列纯属学习笔记,如果哪里有错误或遗漏的地方,希望大家高调指出,当然,我肯定不会低调改正的.(开个小 ...
随机推荐
- Eclipse插件 —— Maven的安装
1.下载插件 下载一(CSDN 网站下载) CSDN上提供的下载内容是笔者在SOURCEFORGE网站上下载下来的. 由于SOURCEFORGE网站上有多个版本,且没有集中打包,需逐个下 ...
- 利用SOLR搭建企业搜索平台 之——solr配置solrconfig.xml
来源:http://blog.csdn.net/zx13525079024/article/details/25310781 solrconfig.xml配置文件主要定义了SOLR的一些处理规则,包括 ...
- Web Servers in Visual Studio for ASP.NET Web Projects
https://msdn.microsoft.com/en-us/library/58wxa9w5(v=vs.120).aspx When you develop web projects in Vi ...
- 纠结和郁闷的存在感-关于DirectX与HLSL的矩阵存储方式---转载好文章
我常常这么大胆的认为,搞科学的人总是喜欢用各种让常人难以理解的复杂方式去处理某些其实可能很简单的事情,这种情况在他自身擅长的.有着诸多对手竞争的领域上极为常见.比如吧,搞DirectX的人用了左手坐标 ...
- 线程——委托InvokeRequired和Invoke
C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它.此时它将会在内部调用ne ...
- PS太大GIMP可用
图片处理中Photoshop用的非常多,但是该软件过于臃肿,使用起来也非常复杂,对于一般性的图片处理,有没有其他可以选择的工具呢? GIMP是GNU Image Manipulation Progra ...
- LA 4255 (拓扑排序 并查集) Guess
设这个序列的前缀和为Si(0 <= i <= n),S0 = 0 每一个符号对应两个前缀和的大小关系,然后根据这个关系拓扑排序一下. 还要注意一下前缀和相等的情况,所以用一个并查集来查询. ...
- 每个极客都应该知道的Linux技巧
每个极客都应该知道的Linux技巧 2014/03/07 | 分类: IT技术 | 0 条评论 | 标签: LINUX 分享到:18 本文由 伯乐在线 - 欣仔 翻译自 TuxRadar Linux. ...
- ubuntu 12.04上安装OpenERP 7的一次记录
登陆ssh, 先更新系统: sudo apt-get update && sudo apt-get dist-upgrade 接着再为openerp运行创建一个系统用户,用户名就叫op ...
- 使用MySQL Proxy解决MySQL主从同步延迟
MySQL的主从同步机制非常方便的解决了高并发读的应用需求,给Web方面开发带来了极大的便利.但这种方式有个比较大的缺陷在于MySQL的同步机制 是依赖Slave主动向Master发请求来获取数据的, ...