ViewModels に、Constructor を介して何かを自動的に注入する必要があると想定しています。たとえば、View が何を表示するかを決定するために使用するある種の構成オブジェクトです。また、MVC がコントローラー アクションの引数からモデル インスタンスを自動的に作成してバインドしようとすると、このアプローチが「このオブジェクトにパラメーターなしのコンストラクターが定義されていません」というエラーを引き起こしていると想定しています。また、DI フレームワークを使用して、SiteConfig オブジェクトを実行時にコントローラーに自動的に挿入するとします。
これは、解決しなければならない唯一の問題は、注入されたオブジェクトを Controller からアクションの ViewModel に自動的にバインドするときに取得する方法だけであることを意味します。
それでは、他のモデルが継承する基本モデルを定義しましょう。
BaseViewModel
public class BaseViewModel
{
public ISiteConfig SiteConfig { get; set; }
public BaseViewModel(ISiteConfig siteConfig)
{
this.SiteConfig = siteConfig;
}
}
そして、それを継承するモデルを作成しましょう。
IndexViewModel
public class IndexViewModel : BaseViewModel
{
public string SomeIndexProperty { get; set; }
public IndexViewModel (ISiteConfig siteConfig) : base(siteConfig) {}
}
次に、コントローラが継承するベース コントローラを定義しましょう。
BaseController
public abstract class BaseController : Controller
{
protected BaseController(ISiteConfig siteConfig)
{
_siteConfig = siteConfig;
}
private readonly ISiteConfig _siteConfig;
public ISiteConfig SiteConfig
{
get
{
return _siteConfig;
}
}
}
次に、実際のコントローラーを定義します。
ホームコントローラー
public HomeController: BaseController
{
public HomeController(ISiteConfig siteConfig): base(siteConfig) {}
}
DI に Ninject を使用していると仮定すると、Ninject は自動的にコントローラーを作成し、ISiteConfig
実行時に具体的なオブジェクトをそのコンストラクターに渡すように構成されます。
ここで、Action を Controller に追加します。
インデックス アクション
public ActionResult Index(IndexViewModel model)
{
return View(model);
}
MVC は引数を取らない ViewModel コンストラクターを見つけることができないため、インデックス アクションを呼び出そうとすると、他に何もしなくても MVC は「パラメーターなしのコンストラクター」エラーで爆発します。
そして、答え。デフォルトの ModelBinder をオーバーライドする必要があります。
BaseViewModelBinder
public class BaseViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
if (modelType == typeof(BaseViewModel) || modelType.IsSubclassOf(typeof(BaseViewModel)))
{
var baseControl = controllerContext.Controller as BaseController;
if (baseControl == null)
{
throw new Exception("The Controller must derive from BaseController");
}
var instance = Activator.CreateInstance(modelType, baseControl.SiteConfig);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, modelType);
return instance;
}
else
{
return base.CreateModel(controllerContext, bindingContext, modelType);
}
}
}
そして、これをデフォルトのモデル バインダーとして設定する必要がありますglobal.asax.cs
。
protected void Application_Start()
{
...
ModelBinders.Binders.DefaultBinder = new BaseViewModelBinder();
}
それで全部です。ご覧のとおり、インデックス アクションを表示すると、MVC はカスタム モデル バインダーを使用します。IndexViewModel が BaseViewModel から派生していることを認識し、アクションのコントローラーで見つけることができる ISiteConfig を使用して IndexViewModel インスタンスをスピンアップしようとします (コントローラーは BaseController から派生するため)。