我假设你想让你的 ViewModels 通过它们的构造函数自动注入一些东西——例如某种配置对象,视图将用来确定要显示的内容。我还假设当 MVC 尝试从控制器操作的参数自动创建和绑定模型实例时,这种方法会导致“没有为此对象定义无参数构造函数”错误。然后假设我们将使用 DI 框架在运行时自动将 SiteConfig 对象注入到我们的控制器中。
这意味着我们唯一需要解决的问题是,当它们被自动绑定时,如何将注入的对象从我们的 Controller 中获取到其 Actions 的 ViewModel 中。
所以让我们定义一个基础模型供其他人继承。
基础视图模型
public class BaseViewModel
{
public ISiteConfig SiteConfig { get; set; }
public BaseViewModel(ISiteConfig siteConfig)
{
this.SiteConfig = siteConfig;
}
}
现在让我们创建一个继承自它的模型。
索引视图模型
public class IndexViewModel : BaseViewModel
{
public string SomeIndexProperty { get; set; }
public IndexViewModel (ISiteConfig siteConfig) : base(siteConfig) {}
}
现在让我们定义一个基础控制器,我们的控制器将从中继承。
基本控制器
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) {}
}
假设我们将 Ninject 用于 DI,Ninject 将被配置为自动创建控制器并ISiteConfig
在运行时将具体对象传递给它的构造函数。
现在我们将我们的动作添加到控制器中。
索引操作
public ActionResult Index(IndexViewModel model)
{
return View(model);
}
因此,如果您尝试调用索引操作,MVC 将在不执行任何其他操作的情况下爆炸并出现“无参数构造函数”错误,因为 MVC 找不到不带参数的 ViewModel 构造函数。
所以,答案。我们需要覆盖默认的 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();
}
就这样。如您所见,当您现在查看 Index Action 时,MVC 将使用我们自定义的模型绑定器。它将意识到 IndexViewModel 派生自 BaseViewModel,因此将尝试使用它可以在 Action 的 Controller 中找到的 ISiteConfig 启动 IndexViewModel 实例(因为 Controller 派生自 BaseController)。