web-dev-qa-db-ja.com

List <BaseClass>とエディターテンプレートを含むViewModel

フロアプランに追加されているテーブルを一覧表示するビューがあります。テーブルはTableInputModelから派生し、RectangleTableInputModelCircleTableInputModelなどを考慮します。

ViewModelには、すべて派生型の1つであるTableInputModelのリストがあります。

派生型ごとに部分的なビューがあり、混合派生型のListが与えられると、フレームワークはそれらをレンダリングする方法を知っています。

ただし、フォームを送信すると、タイプ情報は失われます。カスタムモデルバインダーを試してみましたが、送信時にタイプ情報が失われるため、機能しません...

誰かがこれを以前に試したことがありますか?

37
user156888

次のモデルがあると仮定します。

public abstract class TableInputModel 
{ 

}

public class RectangleTableInputModel : TableInputModel 
{
    public string Foo { get; set; }
}

public class CircleTableInputModel : TableInputModel 
{
    public string Bar { get; set; }
}

そして、次のコントローラー:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new TableInputModel[]
        {
            new RectangleTableInputModel(),
            new CircleTableInputModel()
        };
        return View(model);
    }

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

これで、ビューを作成できます。

メインビューIndex.cshtml

@model TableInputModel[]
@using (Html.BeginForm())
{
    @Html.EditorForModel()
    <input type="submit" value="OK" />
}

および対応するエディターテンプレート。

~/Views/Home/EditorTemplates/RectangleTableInputModel.cshtml

@model RectangleTableInputModel
<h3>Rectangle</h3>
@Html.Hidden("ModelType", Model.GetType())
@Html.EditorFor(x => x.Foo)

~/Views/Home/EditorTemplates/CircleTableInputModel.cshtml

@model CircleTableInputModel
<h3>Circle</h3>
@Html.Hidden("ModelType", Model.GetType())
@Html.EditorFor(x => x.Bar)

パズルの最後の欠けている平和は、TableInputModel型のカスタムモデルバインダーです。これは、投稿された非表示フィールド値を使用して型をフェッチし、適切な実装をインスタンス化します。

public class TableInputModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".ModelType");
        var type = Type.GetType(
            (string)typeValue.ConvertTo(typeof(string)), 
            true
        );
        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
        return model;
    }
}

Application_Startに登録されます:

ModelBinders.Binders.Add(typeof(TableInputModel), new TableInputModelBinder());

そしてそれはほとんどすべてです。これで、Index Postアクション内で、モデル配列が正しい型で適切に初期化されます。

69
Darin Dimitrov

Mvccontribに "Derived Type Model Binder" がありました。しかし、残念ながら、mvccontribバージョン3にはそのようなバインダーはありません。

2