多態的モデルバインディング 質問する

多態的モデルバインディング 質問する

この質問は前に尋ねたMVCの以前のバージョンでは、このブログ記事この問題を回避する方法について。MVC3 で何か役立つものが導入されているかどうか、または他のオプションがあるかどうか知りたいです。

簡単に言うと、状況は次のとおりです。抽象基本モデルと 2 つの具体的なサブクラスがあります。モデルをレンダリングする厳密に型指定されたビューがありますEditorForModel()。次に、各具体的な型をレンダリングするためのカスタム テンプレートがあります。

問題は投稿時に発生します。投稿アクション メソッドが基本クラスをパラメーターとして受け取るようにすると、MVC はその抽象バージョンを作成できなくなります (いずれにしても、これは必要ありません。実際の具体的な型を作成したいのです)。パラメーター シグネチャのみが異なる複数の投稿アクション メソッドを作成すると、MVC はあいまいであるとエラーを出します。

私の知る限り、この問題を解決するにはいくつかの選択肢があります。さまざまな理由からどれも気に入りませんが、ここでリストします。

  1. 私がリンクした最初の投稿で Darin が提案しているように、カスタム モデル バインダーを作成します。
  2. リンクした 2 番目の投稿で提案されているように、識別子属性を作成します。
  3. タイプに応じて異なるアクションメソッドに投稿する
  4. ???

1 は基本的に隠された構成なので好きではありません。コードに取り組んでいる他の開発者がそれを知らない可能性があり、変更時になぜ問題が発生するのかを解明しようとして多くの時間を無駄にする可能性があります。

2 はちょっとハッキーな感じがするので好きではありません。でも、私はこのアプローチに傾いています。

3 は DRY に違反することになるので好きではありません。

他に何か提案はありますか?

編集:

私は Darin の方法を採用することに決めましたが、少し変更を加えました。抽象モデルに以下を追加しました:

[HiddenInput(DisplayValue = false)]
public string ConcreteModelType { get { return this.GetType().ToString(); }}

すると、隠し が に自動的に生成されますDisplayForModel()。 覚えておかなければならない唯一のことはDisplayForModel()、 を使用していない場合は自分で追加する必要があるということです。

ベストアンサー1

私は明らかにオプション1を選択しているので(:-))、もう少し詳しく説明して、壊れやすい具体的なインスタンスをモデル バインダーにハードコーディングしないようにします。具体的な型を非表示フィールドに渡し、リフレクションを使用して具体的な型をインスタンス化するという考え方です。

次のようなビュー モデルがあるとします。

public abstract class BaseViewModel
{
    public int Id { get; set; }
}

public class FooViewModel : BaseViewModel
{
    public string Foo { get; set; }
}

次のコントローラー:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new FooViewModel { Id = 1, Foo = "foo" };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(BaseViewModel model)
    {
        return View(model);
    }
}

対応するIndexビュー:

@model BaseViewModel
@using (Html.BeginForm())
{
    @Html.Hidden("ModelType", Model.GetType())    
    @Html.EditorForModel()
    <input type="submit" value="OK" />
}

エディター~/Views/Home/EditorTemplates/FooViewModel.cshtmlテンプレート:

@model FooViewModel
@Html.EditorFor(x => x.Id)
@Html.EditorFor(x => x.Foo)

これで、次のカスタム モデル バインダーが作成されます。

public class BaseViewModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
        var type = Type.GetType(
            (string)typeValue.ConvertTo(typeof(string)),
            true
        );
        if (!typeof(BaseViewModel).IsAssignableFrom(type))
        {
            throw new InvalidOperationException("Bad Type");
        }
        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
        return model;
    }
}

実際の型は、非表示フィールドの値から推測されますModelType。ハードコードされていないため、このモデル バインダーに触れることなく、後で他の子型を追加できます。

この同じ技術は簡単に適用できる基本ビューモデルのコレクション。

おすすめ記事