MVC DropDownList values posted to model aren't bound

此生再无相见时 提交于 2019-12-08 07:59:42

问题


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.


回答1:


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>



回答2:


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");


来源:https://stackoverflow.com/questions/5657826/mvc-dropdownlist-values-posted-to-model-arent-bound

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!