使用Model Binder绑定Action参数字段时的取舍问题
2009-09-28 13:57 by 老赵, 12403 visits刚才在看代码的时候忽然发现了一件可能会成为问题的情况,而这个情况还挺隐蔽的。因此,我原本写到一半的东西就暂时停下,顺延至明天,而现在先来谈谈这个问题。这个问题就是在使用DefaultModelBinder在绑定字段时的取舍问题。而您在使用ASP.NET MVC的时候不妨也检查一下,看看有没有这方面的情况。
ASP.NET MVC的一个便利之处就在于,它可以将Route Value,Form或Query String里的数据自动构造成Actoin方法的参数。例如我们ProductController中有个叫做Create的Action方法:
[AcceptVerbs(HttpVerbs.Post)] public class ProductController : BaseController { public ActionResult Create(Product product) { var db = new DataContext(); db.InsertOnSubmit(product); db.SubmitChanges(); } }
这是一段使用LINQ to SQL向数据库中插入数据的方法,很简单,您一定也看得懂。ASP.NET MVC的DefaultModelBinder类会根据Product类型的字段,从Post过来的数据中获取一些值,然后构造一个Product对象。例如Product类型是这样定义的:
public class Product { public virtual int ProductID { get; set; } public virtual string Name { get; set; } public virtual float Price { get; set; } public virtual int ViewCount { get; set; } }
四个字段:ID、名称、价格以及浏览量。当用户访问/Product/Create页面时(不是刚才Post Only的Action,而是用于显示创建页的Action),客户端便会获得这样的form表单:
<form action="/Product/Action" method="post"> <input type="text" name="Name" /> <input type="text" name="Price" /> <input type="submit" /> </form>
当然,实际情况下这个表单是不会那么赤裸裸的,会有文字说明,会有校验逻辑等等。不过它的含义总归是向/Product/Action这个URL中Post两个字段:Name和Price,最后在服务器端执行得到的结果便是创建了一个指定了Name和Price的对象,它的ViewCount是0——为什么是0?因为客户端没有提供数据咯。
问题就在这里,既然客户端没有提供数据,于是ViewCount是0,但如果客户端提供了数据又该怎么办呢?例如,用户完全有能力在客户端多加一个字段,变成这样:
<form action="/Product/Action" method="post"> <input type="text" name="Name" /> <input type="text" name="Price" /> <input type="hidden" name="ViewCount" value="1000" /> <input type="submit" /> </form>
如此创建出的对象,它的ViewCount自然就直接是1000了。您可能会问,那么用户为什么会知道是ViewCount这个字段?我想,他应该可以从网站其他地方获取蛛丝马迹的,例如某个元素的id,例如某次请求所提交的参数等等。于是,他就可以这么一试了。
写到这儿,我忽然又想到,有些朋友在写form的时候可能会偷一下懒,省略了form的action属性,或者这样构造一个表单:
<% using (Html.BeginForm()) { %> ... <% }; %>
如此,form便会提交给当前URL。这样做的问题在于,如果用户通过/Product/Create?ViewCount=1000这样的URL来访问创建页面,这样提交后的数据也会自动填写上ViewCount。
当然这个问题很容易解决,例如在创建Product的Create方法中可以主动为product参数的属性设置默认值,或者使用ASP.NET MVC自带的机制:
[AcceptVerbs(HttpVerbs.Post)] public ActionResult Create([Bind(Exclude="ViewCount")]Product product) { ... }
您可以为product参数加上BindAttribute标记,指定哪些字段不需要绑定(或哪些需要),这样即使客户端提交了某些字段,DefaultModelBinder也会忽略这些数据了。
hihi