代码改变世界

使用Model Binder绑定Action参数字段时的取舍问题

2009-09-28 13:57  Jeffrey Zhao  阅读(12986)  评论(26编辑  收藏  举报

刚才在看代码的时候忽然发现了一件可能会成为问题的情况,而这个情况还挺隐蔽的。因此,我原本写到一半的东西就暂时停下,顺延至明天,而现在先来谈谈这个问题。这个问题就是在使用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也会忽略这些数据了。