3

質問の作成ビューでDropDownListを手動で作成している質問クラスにナビゲーションプロパティ(カテゴリ)があり、作成アクションを投稿すると、カテゴリナビゲーションプロパティがモデルに入力されないため、無効な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; }
    }

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プロパティはnullになりますが、プロパティのCategoryIdフィールドはビューで選択したCategoryに設定されます。

ビューから取得した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プロパティに自動的に入力する方法はありません。なぜなら、データを取得するためにデータベースクエリを発行する必要があり、透過的であってはならないからです。明示的に行う必要があります。さらに、カスタムバインダーでこの操作を行うことは、おそらく最善の方法ではありません。このリンクには良い説明があります:依存性をカスタムモデルバインダーに注入し、Ninjectを使用してInRequestScopeを使用します

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

そのため、質問モデルも変更して、CategoryId という追加のプロパティを持たないようにしました。

フォームの場合、私がしたことは次のとおりです。

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

これが 2 番目の規則です。プロパティ フィールドに Id suffix を付けて名前を付ける必要があります

最後に、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;
        }
    }

public class 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 に答える