关于asp.net mvc:MVC CheckBox 绑定回模型

MVC CheckBox binding back to model

所以我到处寻找这个特殊问题的答案,但我找不到确切的解决方案。很多类似的问题,但没有一个能解决我的确切问题,所以如果这已经得到解答,请道歉。

我有以下(简化的)模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class SimpleModel {
    public List<SimpleFieldModel> Fields{ get; set; }
}

public abstract class SimpleFieldModel {
    public List<ListItemModel> Items { get; set; }
}

public class ListItemModel {
     public string Text { get; set; }
     public string Value { get; set; }
     public bool Selected { get; set; }
}

使用以下 SimpleModel 视图:

1
2
3
@for(var i = 0; i < Model.Fields.Count; i++) {
    @Html.EditorFor(Model.Fields[i])
}

以及下面的 SimpleFieldModel 视图:

1
2
3
4
@for(var i = 0; i < Model.Items.Count; i++) {
    @Html.CheckBoxFor(m => Model.Items[i].Selected)
    @Html.HiddenFor(m => Model.Items[i].Text)
}

这会产生预期的标记:

1
2
3
<input name="Fields[5].Items[0].Selected" id="Fields_5__Items_0__Selected" type="checkbox" value="true" data_val="89">
<input name="Fields[5].Items[0].Selected" type="hidden" value="false">
<input name="Fields[5].Items[0].Text" id="Fields_5__Items_0__Text" type="hidden" value="Option 1">

当它被发布到控制器时,我会收到以下信息:

  • 表单数据:Fields[5].Items[0].Selected = "true,false"
  • 模型:字段[5].Items[0].Selected = false

我不得不编写一个方法来使用表单中的数据填充我的集合,但我觉得 Razor 应该为我处理这个问题,但它似乎不起作用。

编辑

看来根本原因可能是因为我希望将抽象模型接收到我的控制器中,如下所示:

1
2
3
4
5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(SimpleModel model)
{
}

为了克服这个问题,我有以下模型活页夹:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SimpleModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue("ConcreteModelType");
        var type = Type.GetType(
            (string)typeValue.ConvertTo(typeof(string)),
            true
        );
        if (!typeof(SimpleModel).IsAssignableFrom(type))
        {
            throw new InvalidOperationException("Bad Type");
        }
        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata =
        ModelMetadataProviders.Current.GetMetadataForType(() => model, type);

        return model;
    }
}

但奇怪的是,在退出上述活页夹时,模型具有正确数量的字段,并且每个字段都有其正确和预期的项目。但是,当我进入控制器方法时,我的模型似乎已被更改,因为它现在缺少字段中的项目。

对于最初没有包含所有代码,我深表歉意,但我无法提供对完整代码库的访问权限。

进一步更新

我认为提及我也有一个 SimpleFieldModelBinder 也可能是相关的。就是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SimpleFieldModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue(string.Format("{0}.{1}", bindingContext.ModelName,"ConcreteFieldModelType"));
        var type = Type.GetType(
            (string)typeValue.ConvertTo(typeof(string)),
            true
        );
        if (!typeof(SimpleFieldModel).IsAssignableFrom(type))
        {
            throw new InvalidOperationException("Bad Type");
        }
        var model = Activator.CreateInstance(type);

        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);

        return model;
    }
}

所以,当我退出 SimpleModelBinder 时,我拥有了所有字段,并且它们具有正确的项目(尽管不受最近的表单更改的约束)。然而,我的控制器似乎收到了 SimpleFieldModel 对象的列表,这些对象缺少相关的 Items 列表。

SimpleFieldModelBinder 肯定出了问题。如果我把它拿出来,我的 SimpleModel 包含一个抽象 SimpleFieldModel 对象的列表。但是,如果我把它留在里面,控制器会在其 Fields 列表中收到一个具有正确具体类型的 SimpleModel,但这些都被清除了它们的 Items。

另一个更新

我在 SimpleFieldModelBinder 中添加了一些代码,以便它从表单数据中填充 Items 集合。在退出 CreateModel 时,集合是完整的(并且具有适当的 Selected 值)。然而,当我到达我的控制器时(我必须假设这是绑定的下一步?)这些 Items 集合已被删除。有点可疑的一点是,第一个字段有它的项目,但所有其他字段都没有。我怀疑绑定仅适用于 Fields 集合中的第一项,即使在调试过程中,我为所有字段点击了 SimpleFieldModelBinder。

我可能缺少什么?

谢谢


请将此复制到您的 Visual Studio。这应该对您有所帮助。

控制器/模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class HomeController : Controller
{
    public class SimpleModel
    {
        public List<ListItemModel> Items { get; set; }
    }

    public class ListItemModel
    {
        public string Text { get; set; }
        public string Value { get; set; }
        public bool Selected { get; set; }
    }

    [HttpPost]
    public ActionResult Index29(SimpleModel sm)
    {
        //put breakpoint here to interrogate passed in model
        return View();
    }

    public ActionResult Index29()
    {
        ListItemModel lim = new ListItemModel { Selected = false, Text ="aText", Value ="aValue" };
        ListItemModel limTwo = new ListItemModel { Selected = true, Text ="bText", Value ="bValue" };
        SimpleModel sm = new SimpleModel();
        sm.Items = new List<ListItemModel>();
        sm.Items.Add(lim);
        sm.Items.Add(limTwo);

        return View(sm);
    }

视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@model Testy20161006.Controllers.HomeController.SimpleModel
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    Index29
</head>
<body>
   
        @using (Html.BeginForm())
        {

            for (var i = 0; i < Model.Items.Count; i++)
            {
                @Html.Label(Model.Items[i].Text)

                //modify the next line
                @Html.CheckBoxFor(
                    m => Model.Items[i].Selected)

                @Html.HiddenFor(m => Model.Items[i].Text)
                @Html.HiddenFor(m => Model.Items[i].Value)
            }

            <input type="submit" value="Post" />
        }
   
</body>
</html>