一、介绍

深入了解 ASP.NET MVC 数据绑定非常重要,特别是对于像富客户端越来越流行,复杂对象需求度也越来越高。我会写一些示例来帮助了解MVC数据绑定的是怎么回事,这对于工作也会事半功倍。

二、键值对

首先我们要明确MVC默认只支持键值对数据绑定(自定义绑定规则除外),了解这一点非常重要,因为对于MVC而言提交时是通过表单。而表单默认就以键值对形式(即 Content-Type:application/x-www-form-urlencoded);当然我们可能提交是带有文件二进制流的 Content-Type:multipart/form-data 形式,我们也可以理解为键值对,只不过编码不同而已。这两种对MVC而言都是一样的。

文本键值对的特点是请求参数都是会以 [name]=[value]& 的形式串连一个字符串。

看到这不必太着急,我相信很多人更想要了解的是通过AJAX来提交一个复杂对象是怎么一回事,但必须先了解键值对,否则脑子很难转过弯来。

三、示例

我们假如以下 Model 是我们遇到的相对复杂的结构,我相信已经足够应付绝大多数环境了。

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
    public bool IsCCP { get; set; }
    public byte Status { get; set; }
    public DateTime Created { get; set; }

    /// <summary>
    /// 家庭成员情况
    /// </summary>
    public List<Family> Families { get; set; }

    /// <summary>
    /// 年度分类情况,key:年份
    /// </summary>
    public Dictionary<int, Score> Scores { get; set; }
}

public class Family
{
    public string Name { get; set; }

    public string Mobile { get; set; }
}

public class Score
{
    public int Avg { get; set; }

    public int Highest { get; set; }

    public int Lowest { get; set; }
}

根据Model出现了基础类型、List、Dictionary几乎涵盖所有我们日常需要的类型了,以下相应的前端HTML代码如下:

@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { name = "nForm", id = "nForm", @class = "form-horizontal" }))
{
    <fieldset>
        <legend>基本信息</legend>
        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "col-sm-2 control-label" })
            <div class="col-sm-10">
                @Html.TextBoxFor(model => model.Name, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Age, new { @class = "col-sm-2 control-label" })
            <div class="col-sm-10">
                @Html.TextBoxFor(model => model.Age, new { type = "number", @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-10 col-sm-offset-2">
                @Html.CheckBoxFor(model => model.IsCCP)
                @Html.LabelFor(model => model.IsCCP, new { @class = "" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Status, new { @class = "col-sm-2 control-label" })
            <div class="col-sm-10">
                @Html.DropDownListFor(w => w.Status, new List<SelectListItem>() {
            new SelectListItem(){Text="请选择",Value="0"},
            new SelectListItem(){Text="正常",Value="1"},
            new SelectListItem(){Text="退学",Value="2"}
           }, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Created, new { @class = "col-sm-2 control-label" })
            <div class="col-sm-10">
                @Html.TextBoxFor(model => model.Created, new { type = "date", @class = "form-control" })
            </div>
        </div>
    </fieldset>
    <fieldset>
        <legend>家庭情况</legend>
        @for (var i = 0; i < Model.Families.Count; i++)
        {
            <div class="form-group">
                @Html.LabelFor(model => Model.Families[i].Name, new { @class = "col-sm-2 control-label" })
                <div class="col-sm-10">
                    @Html.TextBoxFor(model => Model.Families[i].Name, new { @class = "form-control" })
                </div>
            </div>
            <div class="form-group">
                @Html.LabelFor(model => Model.Families[i].Mobile, new { @class = "col-sm-2 control-label" })
                <div class="col-sm-10">
                    @Html.TextBoxFor(model => Model.Families[i].Mobile, new { type = "number", @class = "form-control" })
                </div>
            </div>
            <hr />
        }
    </fieldset>
    <fieldset>
        <legend>分数情况</legend>
        @{int scorePoint = 0;}
        @foreach (var item in Model.Scores)
        {
            <div class="form-group">
                <div class="col-sm-2">
                    <input name="Scores[@scorePoint].Key" value="@item.Key" type="number" class="form-control" />
                </div>
                <div class="col-sm-10">
                    <div class="form-group">
                        @Html.LabelFor(model => item.Value.Highest, new { @class = "col-sm-2 control-label" })
                        <div class="col-sm-10">
                            <input name="Scores[@scorePoint].Value.Highest" value="@item.Value.Highest" type="number" class="form-control" />
                        </div>
                    </div>
                    <div class="form-group">
                        @Html.LabelFor(model => item.Value.Lowest, new { @class = "col-sm-2 control-label" })
                        <div class="col-sm-10">
                            <input name="Scores[@scorePoint].Value.Lowest" value="@item.Value.Lowest" type="number" class="form-control" />
                        </div>
                    </div>
                    <div class="form-group">
                        @Html.LabelFor(model => item.Value.Avg, new { @class = "col-sm-2 control-label" })
                        <div class="col-sm-10">
                            <input name="Scores[@scorePoint].Value.Avg" value="@item.Value.Avg" type="number" class="form-control" />
                        </div>
                    </div>
                </div>
            </div>
            <hr />
            ++scorePoint;
        }
    </fieldset>
    <div class="form-group">
        <div class="col-sm-10 col-sm-offset-2">
            <button type="submit" class="btn btn-primary">Submit</button>
        </div>
    </div>
}

1、基础类型

基本信息都是基础类型,相应的@Html都是非常简单的,我们可以跟踪一下生成的HTML及请求时所提交的相应参数信息:

<input class="form-control" id="Name" name="Name" type="text" value="cipchk">
<input class="form-control" id="Age" name="Age" type="number" value="18">

非常简单的将Name和Age组装成:Name=cipchk&Age=18,我们可以通过 Request.Form 来确认这一点。

2、List类型

家庭情况以List对象存在,相应的@Html只需要通过 for 循环,最后生成的HTML就像:

<!-- 1 -->
<input class="form-control" name="Families[0].Name" type="text" value="父亲">
<input class="form-control" name="Families[0].Mobile" type="number" value="111">
<!-- 2 -->
<input class="form-control" name="Families[1].Name" type="text" value="母亲">
<input class="form-control" name="Families[1].Mobile" type="number" value="222">

和基本类型相比较,重点的name值,会以对象名+下标的形式出现,且这个下标必须是从0逐一递增。

注:原则上我们也可以用foreach来循环,但这出于@Html的运行机制中会以传递的 model => Model.Families[i].Name 做为name值,而这种方式跟我们所需要的键值对名称的约定有出入,所以采用 for 循环方式更符合实际。因为我们可以把以下这句话做为@Html的约束。

对于前端@Html的循环,永远都以 for 来完成。

而最后提交到会组装成数据如:Families[0].Name=父亲&Families[0].Mobile=111&Families[1].Name=母亲&Families[1].Mobile=222

2、Dictionary类型

分数以字典形式出现,其实他也是一个List对象,只不过看起来他只有 Key、Value 而已,所以我们依然可以用 List 的形式来表现,只不过他的表单 name 会更加奇怪一点,像:

<!-- 1 -->
<input name="Scores[0].Key" value="2014" type="number" class="form-control">
<input name="Scores[0].Value.Highest" value="100" type="number" class="form-control">
<input name="Scores[0].Value.Lowest" value="100" type="number" class="form-control">
<input name="Scores[0].Value.Avg" value="100" type="number" class="form-control">
<!-- 2 -->
<input name="Scores[1].Key" value="2015" type="number" class="form-control">
<input name="Scores[1].Value.Highest" value="100" type="number" class="form-control">
<input name="Scores[1].Value.Lowest" value="100" type="number" class="form-control">
<input name="Scores[1].Value.Avg" value="100" type="number" class="form-control">

所需要的表单name和List类型相象,我就不加与赘述。

四、回归HTML标准

查询w3对form描述,键值对这种形和.Net的数据类型之间的互转换原来对于基础类型来说非常简单,我们可以理解为form中的text、number、date等等(大部分是HTML5)这一些都是和我们编程语言的数据类型相之对应,而对于复杂类型对象、List、字典等等,本质也是采用键值对,只不过不同语言对于键的表现形式不同而已。

所以实际我们更应该清楚不同语言在键的表述形式上的理解,那么我相信对于数据绑定这个课题上不会再纠结。

五、AJAX问题

我相信如果了解问题本质以后,这个点就会不存在了,不相信我们改学以上示例,用AJAX提交,只需要这样:

$('form').submit(function () {
    $.ajax({
        url: $(this).attr('action'),
        type: $(this).attr('method'),
        data: $(this).serialize(),
        success: function (res) {

        }
    });
    return false;
});

资料