问题
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