ASP.NET виртуальное свойство навигации MVC 4 не заполняется при выполнении Post Action



У меня есть свойство навигации (Category) в классе вопросов, для которого я вручную создаю раскрывающийся список в представлении Create вопроса, и при отправке действия Create свойство навигации по категориям не заполняется в модели, поэтому дает мне недопустимое состояние ModelState.



Вот моя модель:



 public class Category
{
[Key]
[Required]
public int CategoryId { get; set; }

[Required]
public string CategoryName { get; set; }

public virtual List<Question> Questions { get; set; }
}

public class Question
{
[Required]
public int QuestionId { get; set; }

[Required]
public string QuestionText { get; set; }

[Required]
public string Answer { get; set; }

[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }

public int CategoryId { get; set; }
}


Вот мой контроллер вопросов для действий GET и POST Create:



public ActionResult Create(int? id)
{
ViewBag.Categories = Categories.Select(option => new SelectListItem {
Text = option.CategoryName,
Value = option.CategoryId.ToString(),
Selected = (id == option.CategoryId)
});
return View();
}


[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Question question)
{
if (ModelState.IsValid)
{
db.Questions.Add(question);
db.SaveChanges();
return RedirectToAction("Index");
}

return View(question);
}


А вот представление Create для вопроса



@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)

<fieldset>
<legend>Question</legend>

<div class="editor-label">
@Html.LabelFor(model => model.Category)
</div>
<div class="editor-field">
@Html.DropDownListFor(model => model.Category.CategoryId, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
</div>
<div class="editor-label">
@Html.LabelFor(model => model.QuestionText)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.QuestionText)
@Html.ValidationMessageFor(model => model.QuestionText)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Answer)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Answer)
@Html.ValidationMessageFor(model => model.Answer)
</div>

<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}


У меня есть попробовал следующие варианты генерации выпадающего списка в представлении:



@Html.DropDownListFor(model => model.Category.CategoryId, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
@Html.DropDownListFor(model => model.Category, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
@Html.DropDownList("Category", (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
@Html.DropDownList("CategoryId", (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")


Когда я быстро просматриваю объект Question в действии POST, свойство Category имеет значение null, но поле CategoryId в этом свойстве имеет значение выбранной категории в представлении.



Я знаю, что могу легко добавить код для ручной выборки категории С помощью EF, используя значение CategoryId, которое я получаю из представления. Я также думаю, что я мог бы создать пользовательский связующий файл, чтобы сделать это, но я надеялся, что это может быть сделано с аннотациями данных.



Я что-то упустил?



Есть ли лучший способ создать раскрывающийся список для свойства навигации?



Есть ли способ дать MVC знать, как заполнить свойство навигации без необходимости делать это вручную?



-- EDIT:



Если это имеет какое-либо значение, мне не нужно фактическое свойство навигации, загруженное при создании/сохранении вопроса, мне просто нужно, чтобы CategoryId был правильно сохранен в базе данных, которая не является событие.



Спасибо

608   2  

2 ответов:

Вместо

        @Html.DropDownListFor(model => model.Category.CategoryId, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")

Попробуйте

        @Html.DropDownListFor(model => model.CategoryId, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")

Правка:

Не существует автоматического способа заполнения свойства навигации по идентификатору, опубликованному в форме. Потому что для получения данных должен быть выдан запрос к базе данных, и он не должен быть прозрачным. Это должно быть сделано явно. Более того, выполнение этой операции в пользовательской папке, вероятно, не самый лучший способ. Существует хорошее объяснение в этой ссылке: введите зависимость в пользовательскую модель binder и с помощью InRequestScope с использованием Ninject

Я знаю, что на этот вопрос уже дан ответ, но он заставил меня задуматься.

Поэтому я думаю, что нашел способ сделать это с помощью некоторых конвенций.

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

public abstract class Entity
{
}
public class Question : Entity
{
    [Required]
    public int QuestionId { get; set; }

    [Required]
    public string QuestionText { get; set; }

    [Required]
    public string Answer { get; set; }

    public virtual Category Category { get; set; }
}
public class Category : Entity
{
    [Key]
    [Required]
    public int CategoryId { get; set; }

    [Required]
    public string CategoryName { get; set; }

    public virtual List<Question> Questions { get; set; }
}

Итак, я также изменил модель вопроса, чтобы не иметь дополнительного свойства, называемого CategoryId.

Для формы все, что я сделал, было:

@Html.DropDownList("CategoryId", (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")

Итак, вот второе соглашение, вы должны были бы иметь поле свойства с именем Id суффикс .

Наконец, CustomModelBinder и CustomModelBinderProvider

public class CustomModelBinderProvider : IModelBinderProvider
    {
        private readonly IKernel _kernel;

        public CustomModelBinderProvider(IKernel kernel)
        {
            _kernel = kernel;
        }

        public IModelBinder GetBinder(Type modelType)
        {
            if (!typeof(Entity).IsAssignableFrom(modelType))
                return null;

            Type modelBinderType = typeof (CustomModelBinder<>)
                .MakeGenericType(modelType);

            // I registered the CustomModelBinder using Windsor
            return _kernel.Resolve(modelBinderType) as IModelBinder;
        }
    }

Открытый класс CustomModelBinder: DefaultModelBinder, где T: сущность { private readonly QuestionsContext _db;

public CustomModelBinder(QuestionsContext db)
{    
    _db = db;
}

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    var model = base.BindModel(controllerContext, bindingContext) as T;

    foreach (var property in typeof(T).GetProperties())
    {
        if (property.PropertyType.BaseType == typeof(Entity))
        {
            var result = bindingContext.ValueProvider.GetValue(string.Format("{0}Id", property.Name));
            if(result != null)
            {
                var rawIdValue = result.AttemptedValue;
                int id;
                if (int.TryParse(rawIdValue, out id))
                {
                    if (id != 0)
                    {
                        var value = _db.Set(property.PropertyType).Find(id);
                        property.SetValue(model, value, null);
                    }
                }
            }
        }
    }
    return model;
}

}

CustomModelBinder будет искать свойства типа Entity и загружать данные с переданным идентификатором, используя EF.

Здесь я использую Windsor для введения зависимостей,но вы можете использовать любой другой контейнер IoC.

И это все. У тебя есть способ сделать эту привязку автоматической.

Comments

    Ничего не найдено.