3

我在 Question 类上有一个导航属性(Category),我在 Question 的 Create 视图中为其手动创建 DropDownList ,并且在发布 Create 操作时,Category 导航属性未填充到模型上,因此给了我一个无效的模型状态。

这是我的模型:

 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; }
    }

这是我对 Create 的 GET 和 POST 操作的问题控制器:

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

这是问题的创建视图

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

当我在 POST 操作上快速查看 Question 对象时,Category 属性为空,但该属性上的 CategoryId 字段设置为视图上的选定类别。

我知道我可以使用从视图中获得的 CategoryId 值轻松添加代码以手动获取具有 EF 的类别。我也认为我可以创建一个自定义活页夹来做到这一点,但我希望这可以通过数据注释来完成。

我错过了什么吗?

有没有更好的方法来为导航属性生成下拉列表?

有没有办法让 MVC 知道如何填充导航属性而无需我手动执行?

- 编辑:

如果有任何区别,我不需要在创建/保存问题时加载实际的导航属性,我只需要将 CategoryId 正确保存到数据库中,这不会发生。

谢谢

4

2 回答 2

4

代替

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

编辑:

没有自动方法可以从表单发布的 id 填充 Navigation 属性。因为,应该发出数据库查询来获取数据,并且它不应该是透明的。它应该明确地完成。此外,在自定义活页夹中执行此操作可能不是最好的方法。此链接中有一个很好的解释:Inject a dependency into a custom model binder and using InRequestScope using Ninject

于 2013-03-12T17:34:33.017 回答
1

我知道这个问题已经回答了,但它让我思考。

所以我想我找到了一种通过一些约定来做到这一点的方法。

首先,我让实体继承自这样的基类:

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; }
}

因此,我还将 Question 模型更改为没有名为 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 where T : Entity { 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 使用传递的 Id 加载数据。

这里我使用 Windsor 来注入依赖项,但您可以使用任何其他 IoC 容器。

就是这样。您有一种方法可以自动进行绑定。

于 2013-03-13T18:10:42.300 回答