发布到模型的 MVC DropDownList 值未绑定

发布于 2024-11-01 08:24:00 字数 1244 浏览 1 评论 0原文

DropDownList 可能是我在使用 MVC 框架时最不喜欢的部分。我的表单中有几个下拉菜单,我需要将其选定的值传递给接受模型作为参数的 ActionResult。

标记看起来像这样:

<div class="editor-label">
    @Html.LabelFor(model => model.FileType)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.FileType.Variety, (SelectList)ViewBag.FileTypes)
</div>

<div class="editor-label">
    @Html.LabelFor(model => model.Status)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Status.Status, (SelectList)ViewBag.Status)
</div>

我的控制器操作看起来像这样:

[HttpPost]
public ActionResult Create(int reviewid, ReviewedFile file)
{
    if (ModelState.IsValid)
    {
        UpdateModel(file);
    }

    //repository.Add(file);

    return RedirectToAction("Files", "Reviews", new { reviewid = reviewid, id = file.ReviewedFileId });
}

这应该一切都很好,除了下拉列表中的值被发布为 null 之外。当我进一步查看 ModelState 错误时,发现原因是:

参数类型转换 要输入的“System.String” 'PeerCodeReview.Models.OutcomeStatus' 失败,因为没有类型转换器可以 这些类型之间进行转换。

这不应该这么难,但事实就是如此。所以问题是;我需要做什么才能正确绑定我的模型属性?

顺便说一句,我知道我可以传入 FormCollection 对象,但这意味着更改当前需要强类型模型参数的单元测试的重要部分。

DropDownLists are probably my least favourite part of working with the MVC framework. I have a couple of drop-downs in my form whose selected values I need to pass to an ActionResult that accepts a model as its parameter.

The markup looks like this:

<div class="editor-label">
    @Html.LabelFor(model => model.FileType)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.FileType.Variety, (SelectList)ViewBag.FileTypes)
</div>

<div class="editor-label">
    @Html.LabelFor(model => model.Status)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Status.Status, (SelectList)ViewBag.Status)
</div>

And my controller action looks like this:

[HttpPost]
public ActionResult Create(int reviewid, ReviewedFile file)
{
    if (ModelState.IsValid)
    {
        UpdateModel(file);
    }

    //repository.Add(file);

    return RedirectToAction("Files", "Reviews", new { reviewid = reviewid, id = file.ReviewedFileId });
}

This should be all well and good except the values from the drop downs are being posted as null. When I look further into the ModelState errors, the cause is found to be:

The parameter conversion from type
'System.String' to type
'PeerCodeReview.Models.OutcomeStatus'
failed because no type converter can
convert between these types.

It shouldn't be this hard, but it is. So the question is; what do I need to do in order to get my model properties bound correctly?

As an aside, I know I could pass in a FormCollection object, but that means changing significant parts of my unit tests that currently expect a strongly-typed model parameter.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

飘逸的'云 2024-11-08 08:24:00

您需要为绑定到下拉列表的两个属性创建并注册自定义模型绑定器。

以下是我为此目的构建的模型绑定程序的代码:

public class LookupModelBinder<TModel> : DefaultModelBinder
    where TModel : class
{
    private string _key;

    public LookupModelBinder(string key = null)
    {
        _key = key ?? typeof(TModel).Name;
    }

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var dbSession = ((IControllerWithSession)controllerContext.Controller).DbSession;

        var modelName = bindingContext.ModelName;
        TModel model = null;
        ValueProviderResult vpResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (vpResult != null)
        {
            bindingContext.ModelState.SetModelValue(modelName, vpResult);
            var id = (int?)vpResult.ConvertTo(typeof(int));
            model = id == null ? null : dbSession.Get<TModel>(id.Value);
        }
        if (model == null)
        {
            ModelValidator requiredValidator = ModelValidatorProviders.Providers.GetValidators(bindingContext.ModelMetadata, controllerContext).Where(v => v.IsRequired).FirstOrDefault();
            if (requiredValidator != null)
            {
                foreach (ModelValidationResult validationResult in requiredValidator.Validate(bindingContext.Model))
                {
                    bindingContext.ModelState.AddModelError(modelName, validationResult.Message);
                }
            }
        }
        return model;
    }
}

TModel 是下拉框应绑定到的属性的类型。在我的应用程序中,下拉框提供了数据库中对象的 ID,因此该模型绑定器采用该 ID,从数据库中检索正确的实体,然后返回该实体。您可能有不同的方法将下拉列表给出的字符串转换为模型的正确实体。

您还需要在 Global.asax 中注册模型绑定器。

binders[typeof(EmploymentType)] = new LookupModelBinder<EmploymentType>();

这假设下拉列表控件的名称与类型名称相同。如果没有,您可以将密钥传递给模型绑定器。

binders[typeof(EmploymentType)] = new LookupModelBinder<EmploymentType>("ControlName");

You need to create and register a custom model binder for the two properties that are bound to the drop-down lists.

Here's my code for a model binder I built for exactly this purpose:

public class LookupModelBinder<TModel> : DefaultModelBinder
    where TModel : class
{
    private string _key;

    public LookupModelBinder(string key = null)
    {
        _key = key ?? typeof(TModel).Name;
    }

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var dbSession = ((IControllerWithSession)controllerContext.Controller).DbSession;

        var modelName = bindingContext.ModelName;
        TModel model = null;
        ValueProviderResult vpResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (vpResult != null)
        {
            bindingContext.ModelState.SetModelValue(modelName, vpResult);
            var id = (int?)vpResult.ConvertTo(typeof(int));
            model = id == null ? null : dbSession.Get<TModel>(id.Value);
        }
        if (model == null)
        {
            ModelValidator requiredValidator = ModelValidatorProviders.Providers.GetValidators(bindingContext.ModelMetadata, controllerContext).Where(v => v.IsRequired).FirstOrDefault();
            if (requiredValidator != null)
            {
                foreach (ModelValidationResult validationResult in requiredValidator.Validate(bindingContext.Model))
                {
                    bindingContext.ModelState.AddModelError(modelName, validationResult.Message);
                }
            }
        }
        return model;
    }
}

TModel is the type of the property that the drop-down box should bind to. In my app the drop-down box gives the Id of the object in the database, so this model binder takes that id, retrieves the correct entity from the database, then returns that entity. You may have a different way to convert the string given by the drop-down to the correct entity for the model.

You also need to register the model binder in Global.asax.

binders[typeof(EmploymentType)] = new LookupModelBinder<EmploymentType>();

This assumes that the name of the drop-down list control is the same as the type name. If not, you can pass a key to the model binder.

binders[typeof(EmploymentType)] = new LookupModelBinder<EmploymentType>("ControlName");
终难愈 2024-11-08 08:24:00

试试这个:

<div class="editor-label">
    @Html.LabelFor(model => model.FileType)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.FileType.Id, (SelectList)ViewBag.FileTypes)
</div>

<div class="editor-label">
    @Html.LabelFor(model => model.Status)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Status.Id, (SelectList)ViewBag.Status)
</div>

Try this instead:

<div class="editor-label">
    @Html.LabelFor(model => model.FileType)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.FileType.Id, (SelectList)ViewBag.FileTypes)
</div>

<div class="editor-label">
    @Html.LabelFor(model => model.Status)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Status.Id, (SelectList)ViewBag.Status)
</div>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文