嵌套集合的ASP.NET MVC模型绑定问题

时间:2014-04-07 22:54:17

标签: asp.net-mvc data-annotations model-binding mvc-editor-templates

我有一个包含选项列表的问题列表。他们的问题有一个类型字段,允许问题是单选按钮或清单等...

我正在尝试以这样的方式创建视图,以便我将回答问题和选项。现在我不关心回应。

最好的我可以告诉我遵循ASP.NET的开箱即用模型绑定的规则,这些列表在以下两个地方发表了博客。

Scott Hanselman的博客:http://bit.ly/1fYAWCs
Phil Haack的博客:http://bit.ly/1fYBokd

是否可以使用开箱即用的模型绑定器执行我要执行的操作,还是需要实现自己的自定义模型绑定器?

如果我确实需要实现自定义模型绑定器,您能否指出一些简单的示例或博客文章,了解如何实现这一目标?

最终我想使用问题列表和响应列表在数据注释中编写自定义属性来验证模型(通过确保每个问题至少有一个答案),同时还在客户端上使用不显眼的验证

如果有更好的解决方案,我愿意接受各种想法。

以下是一个展示我问题的人为例子。

视图模型:

public class Questionnaire
{
    private List<QuestionResponse> _questions;

    [UIHint("QuestionResponse")]
    public List<QuestionResponse> Questions
    {
        get { return _questions ?? (_questions = new List<QuestionResponse>()); }
        set { _questions = value; }
    }

    public List<int> Responses;
}

型号:

public class QuestionResponse
{
    public int QuestionId;

    public string QuestionType;

    public string QuestionCode;

    public string QuestionName;

    public string OpenResponse;

    public List<QuestionOptions> Options;
}

public class QuestionOptions
{
    public int OptionId;

    public string OptionType;

    public string OptionName;

    public string OptionDescription;

    public string OptionResponse;

    public bool Selected;

}

控制器方法:

    [HttpGet]
    public ActionResult Questionnaire()
    {
        #region HardCodedDataz
        var questions = new List<QuestionResponse>
        {
            new QuestionResponse
            {
                QuestionId = 1,
                OpenResponse = null,
                QuestionCode = "1",
                QuestionName = "What kind of movies do you like?",
                QuestionType = "Checklist",
                Options = new List<QuestionOptions>
                {
                    new QuestionOptions
                    {
                        OptionDescription = "Horror",
                        OptionId = 1,
                        OptionName = "Horror",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false
                    },
                    new QuestionOptions()
                    {
                        OptionDescription = "Family",
                        OptionId = 2,
                        OptionName = "Family",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false
                    },
                    new QuestionOptions()
                    {
                        OptionDescription = "Comedy",
                        OptionId = 3,
                        OptionName = "Comedy",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false
                    },
                    new QuestionOptions()
                    {
                        OptionDescription = "Adventure",
                        OptionId = 4,
                        OptionName = "Adventure",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false
                    },
                    new QuestionOptions()
                    {
                        OptionDescription = "Drama",
                        OptionId = 5,
                        OptionName = "Drama",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false
                    }
                }
            },
            new QuestionResponse()
            {
                QuestionId = 2,
                OpenResponse = null,
                QuestionCode = "2",
                QuestionName = "Which university did you attend in your first year of college?", 
                QuestionType = "RadioButton",
                Options = new List<QuestionOptions>
                {
                    new QuestionOptions()
                    {
                        OptionDescription = "Cornell",
                        OptionId = 1,
                        OptionName = "Cornell",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false 
                    },
                    new QuestionOptions()
                    {
                        OptionDescription = "Columbia",
                        OptionId = 2,
                        OptionName = "Columbia",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false 
                    },
                    new QuestionOptions()
                    {
                        OptionDescription = "Harvard",
                        OptionId = 3,
                        OptionName = "Harvard",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false 
                    },
                    new QuestionOptions()
                    {
                        OptionDescription = "Yale",
                        OptionId = 4,
                        OptionName = "Yale",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false 
                    },
                    new QuestionOptions()
                    {
                        OptionDescription = "Princeton",
                        OptionId = 5,
                        OptionName = "Princeton",
                        OptionResponse = null,
                        OptionType = "boolean",
                        Selected = false 
                    }
                }
            }
        };
        #endregion

        var model = new Questionnaire()
        {
            Questions = questions,
            Responses = new List<int>()
        };

        return PartialView(model);
    }

    [HttpPost]
    public JsonResult Questionnaire(List<QuestionResponse> questions, List<int> responses)
    {
        return Json(new
        {
            success = "some success message",
            error = "some error message"
        });
    }

查看:

@using TestApplication.Models
@model  Questionnaire

@using (Html.BeginForm("Questionnaire", "Home", FormMethod.Post))
{
    <div>
        @Html.EditorFor(m => m.Questions)
    </div>
    <div>
        <button type="submit">Save</button>
    </div>
}

自定义编辑器模板:
注意:隐藏字段仅用于在post请求中填充模型。

@using System.Web.Mvc.Html
@using TestApplication.Models
@model  List<QuestionResponse>
@if (Model != null)
{
    for (int i = 0; i < Model.Count; i++)
    {
        <p><strong>@Model[i].QuestionName</strong></p>
        @Html.HiddenFor(m => m[i].QuestionName);
        @Html.HiddenFor(m => m[i].QuestionType)
        @Html.HiddenFor(m => m[i].QuestionCode)
        @Html.HiddenFor(m => m[i].QuestionId)

        if (Model[i].QuestionType == "Checklist")
        {
             for (int j = 0; j < Model[i].Options.Count; j++)
             {   
                 <div>
                     <label>@Model[i].Options[j].OptionDescription</label>
                     @Html.CheckBoxFor(m => m[i].Options[j].Selected)
                     @Html.HiddenFor(m => m[i].Options[j].OptionDescription)
                     @Html.HiddenFor(m => m[i].Options[j].OptionName)
                     @Html.HiddenFor(m => m[i].Options[j].OptionId)
                     @Html.HiddenFor(m => m[i].Options[j].OptionType)
                 </div>
             }
        }
        else if (Model[i].QuestionType == "RadioButton")
        {
             for (int j = 0; j < Model[i].Options.Count; j++)
             {
                 var name = "Questions[" + i + "].Options[" + j + "].Selected";
                 var id = Model[i].Options[j].OptionId;
                 <div>
                     <label>@Model[i].Options[j].OptionDescription</label>
                     <input id="@id" type="radio" name="@name" value="" />
                     @Html.HiddenFor(m => m[i].Options[j].OptionDescription)
                     @Html.HiddenFor(m => m[i].Options[j].OptionName)
                     @Html.HiddenFor(m => m[i].Options[j].OptionId)
                     @Html.HiddenFor(m => m[i].Options[j].OptionType)
                 </div>
             }
        }
    }
}

1 个答案:

答案 0 :(得分:1)

在查看asp.net源代码后,我发现框架期望您的模型由属性组成,而简单的字段不会起作用。

以下是源代码中的BindProperties方法。如果复杂模型没有属性,则属性集合将为空,并且将发出绑定发生的for循环。

    private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext);
        Predicate<string> propertyFilter = bindingContext.PropertyFilter;

        // Loop is a performance sensitive codepath so avoid using enumerators.
        for (int i = 0; i < properties.Count; i++)
        {
            PropertyDescriptor property = properties[i];
            if (ShouldUpdateProperty(property, propertyFilter))
            {
                BindProperty(controllerContext, bindingContext, property);
            }
        }
    }

如果您绝对必须使用简单字段,则自定义模型绑定器是合适的。

同样,如果您尝试在公共字段上使用DataAnnotations,那么您将遇到此问题 DataAnnotations on public fields vs properties in MVC