Значения MVD DropDownList, опубликованные в модели, не привязаны - PullRequest
1 голос
/ 14 апреля 2011

DropDownLists, вероятно, моя наименее любимая часть работы с инфраструктурой 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 });
}

Все должно быть хорошо, за исключением того, что значения из выпадающих списков выставляются как нулевые. Когда я изучаю ошибки ModelState, выясняется, что причина:

Параметр преобразования из типа 'System.String' для ввода 'PeerCodeReview.Models.OutcomeStatus' не удалось, потому что преобразователь типов не может конвертировать между этими типами.

Это не должно быть так сложно, но это так. Итак, вопрос в том, Что мне нужно сделать, чтобы правильно связать свойства моей модели?

Кроме того, я знаю, что могу передать объект FormCollection, но это означает изменение значительных частей моих модульных тестов, которые в настоящее время ожидают строго типизированный параметр модели.

Ответы [ 2 ]

3 голосов
/ 14 апреля 2011

Вам необходимо создать и зарегистрировать пользовательский механизм связывания для двух свойств, которые связаны с раскрывающимися списками.

Вот мой код для подшивки модели, которую я построил именно для этой цели:

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 объекта в базе данных, поэтому средство связывания модели берет этот идентификатор, получает правильный объект из базы данных, а затем возвращает этот объект. У вас может быть другой способ преобразовать строку, заданную раскрывающимся списком, в правильный объект для модели.

Вам также необходимо зарегистрировать подшивку модели в Global.asax.

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

Предполагается, что имя элемента управления раскрывающегося списка совпадает с именем типа. Если нет, вы можете передать ключ в папку для моделей.

binders[typeof(EmploymentType)] = new LookupModelBinder<EmploymentType>("ControlName");
2 голосов
/ 14 апреля 2011

Попробуйте вместо этого:

<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>
...