使用默认Model Binding支持集合类

Form【http://weblogs.asp.net/nmarun/archive/2010/03/13/asp-net-mvc-2-model-binding-for-a-collection.aspx】

Yes, my yet another post on Model Binding (previous one is here), but this one uses features presented in MVC 2.

How I got to writing this blog? Well, I’m on a project where we’re doing some MVC things for a shopping cart. Let me show you what I was working with. Below are my model classes:

1 public class Product
2 {
3 public int Id { get; set; }
4 public string Name { get; set; }
5 public int Quantity { get; set; }
6 public decimal UnitPrice { get; set; }
7 }
8
9 public class Totals
10 {
11 public decimal SubTotal { get; set; }
12 public decimal Tax { get; set; }
13 public decimal Total { get; set; }
14 }
15
16 public class Basket
17 {
18 public List<Product> Products { get; set; }
19 public Totals Totals { get; set;}
20 }
 
The view looks as below:
1 <h2>Shopping Cart</h2>
2
3 <% using(Html.BeginForm()) { %>
4
5 <h3>Products</h3>
6 <% for (int i = 0; i < Model.Products.Count; i++)
7 { %>
8 <div style="width: 100px;float:left;">Id</div>
9 <div style="width: 100px;float:left;">
10 <%= Html.TextBox("ID", Model.Products[i].Id) %>
11 </div>
12 <div style="clear:both;"></div>
13 <div style="width: 100px;float:left;">Name</div>
14 <div style="width: 100px;float:left;">
15 <%= Html.TextBox("Name", Model.Products[i].Name) %>
16 </div>
17 <div style="clear:both;"></div>
18 <div style="width: 100px;float:left;">Quantity</div>
19 <div style="width: 100px;float:left;">
20 <%= Html.TextBox("Quantity", Model.Products[i].Quantity)%>
21 </div>
22 <div style="clear:both;"></div>
23 <div style="width: 100px;float:left;">Unit Price</div>
24 <div style="width: 100px;float:left;">
25 <%= Html.TextBox("UnitPrice", Model.Products[i].UnitPrice)%>
26 </div>
27 <div style="clear:both;"><hr /></div>
28 <% } %>
29
30 <h3>Totals</h3>
31 <div style="width: 100px;float:left;">Sub Total</div>
32 <div style="width: 100px;float:left;">
33 <%= Html.TextBox("SubTotal", Model.Totals.SubTotal)%>
34 </div>
35 <div style="clear:both;"></div>
36 <div style="width: 100px;float:left;">Tax</div>
37 <div style="width: 100px;float:left;">
38 <%= Html.TextBox("Tax", Model.Totals.Tax)%>
39 </div>
40 <div style="clear:both;"></div>
41 <div style="width: 100px;float:left;">Total</div>
42 <div style="width: 100px;float:left;">
43 <%= Html.TextBox("Total", Model.Totals.Total)%>
44 </div>
45 <div style="clear:both;"></div>
46 <p />
47 <input type="submit" name="Submit" value="Submit" />
48 <% } %>

 

Nothing fancy, just a bunch of div’s containing textboxes and a submit button. Just make note that the textboxes have the same name as the property they are going to display. Yea, yea, I know. I’m displaying unit price as a textbox instead of a label, but that’s beside the point (and trust me, this will not be how it’ll look on the production site!!).

The way my controller works is that initially two dummy products are added to the basked object and the Totals are calculated based on what products were added in what quantities and their respective unit price. So when the page loads in edit mode, where the user can change the quantity and hit the submit button. In the ‘post’ version of the action method, the Totals get recalculated and the new total will be displayed on the screen. Here’s the code:

1 public ActionResult Index()
2 {
3 Product product1 = new Product
4 {
5 Id = 1,
6 Name = "Product 1",
7 Quantity = 2,
8 UnitPrice = 200m
9 };
10
11 Product product2 = new Product
12 {
13 Id = 2,
14 Name = "Product 2",
15 Quantity = 1,
16 UnitPrice = 150m
17 };
18
19 List<Product> products = new List<Product> { product1, product2 };
20
21 Basket basket = new Basket
22 {
23 Products = products,
24 Totals = ComputeTotals(products)
25 };
26 return View(basket);
27 }
28
29 [HttpPost]
30 public ActionResult Index(Basket basket)
31 {
32 basket.Totals = ComputeTotals(basket.Products);
33 return View(basket);
34 }

That’s that. Now I run the app, I see two products with the totals section below them. I look at the view source and I see that the input controls have the right ID, the right name and the right value as well.

1 <input id="ID" name="ID" type="text" value="1" />
2  <input id="Name" name="Name" type="text" value="Product 1" />
3 ...
4 <input id="ID" name="ID" type="text" value="2" />
5 <input id="Name" name="Name" type="text" value="Product 2" />
 

So just as a regular user would do, I change the quantity value of one of the products and hit the submit button. The ‘post’ version of the Index method gets called and I had put a break-point on line 32 in the above snippet. When I hovered my mouse on the ‘basked’ object, happily assuming that the object would be all bound and ready for use, I was surprised to see both basket.Products and basket.Totals were null. Huh?

A little research and I found out that the reason the DefaultModelBinder could not do its job is because of a naming mismatch on the input controls. What I mean is that when you have to bind to a custom .net type, you need more than just the property name. You need to pass a qualified name to the name property of the input control.

I modified my view and the emitted code looked as below:

1 <input id="Product_Name" name="Product.Name" type="text" value="Product 1" />
2 ...
3 <input id="Product_Name" name="Product.Name" type="text" value="Product 2" />
4 ...
5 <input id="Totals_SubTotal" name="Totals.SubTotal" type="text" value="550" />

Now, I update the quantity and hit the submit button and I see that the Totals object is populated, but the Products list is still null. Once again I went: ‘Hmm.. time for more research’. I found out that the way to do this is to provide the name as:

1 <%= Html.TextBox(string.Format("Products[{0}].ID", i), Model.Products[i].Id) %>
2 <!-- this will be rendered as -->
3 <input id="Products_0__ID" name="Products[0].ID" type="text" value="1" />
 

It was only now that I was able to see both the products and the totals being properly bound in the ‘post’ action method. Somehow, I feel this is kinda ‘clunky’ way of doing things. Seems like people at MS felt in a similar way and offered us a much cleaner way to solve this issue.

The simple solution is that instead of using a Textbox, we can either use a TextboxFor or an EditorFor helper method. This one directly spits out the name of the input property as ‘Products[0].ID and so on. Cool right? I totally fell for this and changed my UI to contain EditorFor helper method.

At this point, I ran the application, changed the quantity field and pressed the submit button. Of course my basket object parameter in my action method was correctly bound after these changes. I let the app complete the rest of the lines in the action method. When the page finally rendered, I did see that the quantity was changed to what I entered before the post. But, wait a minute, the totals section did not reflect the changes and showed the old values.

My status: COMPLETELY PUZZLED! Just to recap, this is what my ‘post’ Index method looked like:

1 [HttpPost]
2 public ActionResult Index(Basket basket)
3 {
4 basket.Totals = ComputeTotals(basket.Products);
5 return View(basket);
6 }

A careful debug confirmed that the basked.Products[0].Quantity showed the updated value and the ComputeTotals() method also returns the correct totals. But still when I passed this basket object, it ended up showing the old totals values only. I began playing a bit with the code and my first guess was that the input controls got their values from the ModelState object.

For those who don’t know, the ModelState is a temporary storage area that ASP.NET MVC uses to retain incoming attempted values plus binding and validation errors. Also, the fact that input controls populate the values using data taken from:

  • Previously attempted values recorded in the ModelState["name"].Value.AttemptedValue
  • Explicitly provided value (<%= Html.TextBox("name", "Some value") %>)
  • ViewData, by calling ViewData.Eval("name") FYI: ViewData dictionary takes precedence over ViewData's Model properties – read more here.

These two indicators led to my guess. It took me quite some time, but finally I hit this post where Brad brilliantly explains why this is the preferred behavior. My guess was right and I, accordingly modified my code to reflect the following way:

1 [HttpPost]
2 public ActionResult Index(Basket basket)
3 {
4 // read the following posts to see why the ModelState
5 // needs to be cleared before passing it the view
6 // http://forums.asp.net/t/1535846.aspx
7 // http://forums.asp.net/p/1527149/3687407.aspx
8 if (ModelState.IsValid)
9 {
10 ModelState.Clear();
11 }
12
13 basket.Totals = ComputeTotals(basket.Products);
14 return View(basket);
15 }


What this does is that in the case where your ModelState IS valid, it clears the dictionary. This enables the values to be read from the model directly and not from the ModelState.

So the verdict is this: If you need to pass other parameters (like html attributes and the like) to your input control, use

1 <%= Html.TextBox(string.Format("Products[{0}].ID", i), Model.Products[i].Id) %>

Since, in EditorFor, there is no direct and simple way of passing this information to the input control. If you don’t have to pass any such ‘extra’ piece of information to the control, then go the EditorFor way.

The code used in the post can be found here.

Published Saturday, March 13, 2010 11:32 PM by nmarun
Filed under: C#, ASP.NET MVC

转载于:https://www.cnblogs.com/jacobz/archive/2011/06/26/2090711.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422840.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows 下cmd命令行的替换工具cmder

1 简介 与windows自带的cmd相比&#xff0c;cmder具有更加友好的界面 2 安装与配置 安装 下载&#xff1a;http://cmder.net/ 下载之后&#xff0c;解压到指定目录即可 双击 cmder.exe 运行 或 windows键 R 后输入 cmder 来打开 配置 注册到右键菜单: 在命令行揭秘额&#x…

重新设定mysql密码~,网上方法都是,这里选一个。

新开一个终端&#xff0c;对&#xff0c;就是要输入jobs后没有输出的终端。1终止MYSQL服务。sudo killall mysqld2特殊运行MYSQLmysqld_safe --skip-grant-tables &登录mysql -u root设密码mysql> use mysql;Reading table information for completion of table and col…

【C语言进阶深度学习记录】四 C语言中的类型转换

今天学习C语言中的类型转换&#xff0c;包括隐式类型转换和显示类型转换 文章目录1 C语言中的数据类型转换1.1 强制类型转换1.11 强制类型转换代码分析1.&#xff12; 隐式类型转换1.21 隐式类型转换代码分析2 总结1 C语言中的数据类型转换 C语言中&#xff0c;可以进行数据类…

【C语言进阶深度学习记录】五 C语言中变量的属性

上一篇文章学习了C语言中的类型转换&#xff0c;点击链接查看&#xff1a;【C语言进阶深度学习记录】四 C语言中的类型转换. 文章目录1 C语言的变量属性1.1 auto关键字1.2 register关键字1.3 static 关键字1.4 代码案例分析1.5 extern 关键字1.6 代码案例分析2 总结1 C语言的变…

hash编码

常用的字符串Hash函数还有ELFHash&#xff0c;APHash等等&#xff0c;都是十分简单有效的方法。这些函数使用位运算使得每一个字符都对最后的函数值产生影响。另外还有以MD5和SHA1为代表的杂凑函数&#xff0c;这些函数几乎不可能找到碰撞。 常用字符串哈希函数有 BKDRHash&…

【C语言进阶深度学习记录】六 C语言中的分支语句

文章目录1 if 语句的分析1.1 if 语句中零值比较的注意点2 switch 语句的分析3 if 与switch语句使用代码案例分析4 if语句与switch语句的互换5 总结1 if 语句的分析 if 语句根据条件选择执行语句else 不能独立存在&#xff0c;且总是与距离它最近的if匹配else 语句可以连接其他…

【C语言进阶深度学习记录】七 C语言中的循环语句

文章目录1 循环语句分析1.1 do...while循环1.2 while循环1.3 for循环1.4 三种循环语句使用对比2 break和continue的区别3 总结1 循环语句分析 C语言中的循环语句主要有for循环&#xff0c;while循环和do…while循环。 循环语句的基本工作方式&#xff1a; 通过条件表达式判断…

overload和override的区别

override&#xff08;重写&#xff09; 1、方法名、参数、返回值相同。2、子类方法不能缩小父类方法的访问权限。3、子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。4、存在于父类和子类之间。5、方法被定义为final不能被重写。overload&#xff08;重载&am…

【C语言进阶深度学习记录】八 C语言中void的分析

文章目录1 void的意义1.1 不存在void变量1.2 C标准1.3 void指针的意义1.4 通过void* 实现memset函数2 总结1 void的意义 void修饰函数的参数和返回值的时候&#xff1a; 如果函数没有返回值应该将其返回值声明为void如果函数没有参数&#xff0c;应该将函数的参数声明为void如…

【C语言进阶深度学习记录】九 C语言中const的详细分析

文章目录1 const的分析2 const本质的分析实验2.1 代码案例分析3 const修饰函数参数和返回值时的情况3.1 代码案例分析4 总结1 const的分析 不管是C语言还是C语言&#xff0c;const都是一个非常重要的关键字。今天这篇文章着重学习记录C语言中的const。C语言中稍有不同。 在C语…

[转载] 源代码

新浪影音娱乐&#xff1a;http://data.ent.sina.com.cn/movie/11552.html 转载于:https://www.cnblogs.com/6DAN_HUST/archive/2011/07/19/2110161.html

【C语言进阶深度学习记录】十 C语言中:struct的柔性数组和union分析

本文并不讲C语言的基础 文章目录1 空struct的大小2 结构体与柔性数组2.1 柔性数组的使用方法2.2 柔性数组使用代码案例分析3 C语言中的union分析3.1 使用union判断系统大小端4 总结1 空struct的大小 C语言中的struct可以看成是变量的集合 如果一个struct里面什么都没有&#…

jQuery学习教程(一):入门

题外话&#xff1a;从今天起正式学习jQuery&#xff08;实际严格讲已经用了几个月的jQuery的一丁点东西&#xff09;&#xff0c;边学边做边记教程&#xff1b;阅读书籍《锋利的jQuery》jQuery API Doc。 基础知识&#xff1a; 想要结构与行为分离当然不能使用<button οncl…

【C语言进阶深度学习记录】十一 C语言中enum,sizeof,typedef分析

文章目录1 enum 枚举类型的使用方法1.1 enum枚举类型的特殊意义1.2 代码分析&#xff1a;enum的使用2 sizeof 关键字的用法2.1 代码案例分析&#xff1a;sizeof的本质3 typedef的意义3.1 代码案例&#xff1a;typedef 的使用案例4 总结1 enum 枚举类型的使用方法 enum是C语言中…

三全食品:信息化建设狂飙突进的六年

李健说&#xff0c;目前三全完成了SAP系统中的销售、物流、财务、生产等核心模块的建设&#xff0c;今后的重点工作就是外部电子商务门户建设&#xff0c;实现与经销商的网络交易&#xff0c;实现外部系统与企业ERP系统的集成。中国第一颗速冻汤圆&#xff0c;第一只速冻粽子都…

【C语言进阶深度学习记录】十二 C语言中的:字符和字符串

文章目录1 C语言中的单引号和双引号1.1 双引号带来的BUG2 总结1 C语言中的单引号和双引号 C语言中的单引号用来表示字符字面量C语言中的双引号用来表示字符串字面量&#xff0c;存储于全局的只读存储区 注意上面的字符与字符串的区别 下面的程序片段是否合法&#xff1f; 上面…

PetShop的系统架构设计[转]

前言&#xff1a;PetShop是一个范例&#xff0c;微软用它来展示.Net企业系统开发的能力。业界有许多.Net与J2EE之争&#xff0c;许多数据是从微软的PetShop和Sun的PetStore而来。这种争论不可避免带有浓厚的商业色彩&#xff0c;对于我们开发人员而言&#xff0c;没有必要过多关…

【C语言进阶深度学习记录】十三 C语言中 ++和--操作符

学习交流加&#xff08;可免费帮忙下载CSDN资源&#xff09;&#xff1a;个人微信&#xff1a; liu1126137994学习交流资源分享qq群1&#xff08;已满&#xff09;&#xff1a; 962535112学习交流资源分享qq群2&#xff1a; 780902027 文章目录1 和--操作符的本质2 总结1 和–操…

使用 Eclipse C/C++ Development Toolkit 开发应用程序

点击打开链接转载于:https://www.cnblogs.com/Podevor/archive/2011/07/25/2788071.html