创建视图模型时,您可以将选项(例如,在下拉列表中使用)填充到视图模型的setter属性中。 问题是,当该视图模型稍后作为参数(通过框架!)传递到动作方法时,这些属性值不会自动变为 重新填充,因此如果由于验证错误需要重新显示表单,则需要重新填充这些选项。
我在这个问题中特别要求的一个可能的解决方案是如何使MVC框架使用构造函数注入实例化视图模型,这将为视图模型构造函数提供某种数据访问对象的实现(例如,存储库)可用于在视图请求选项时检索选项(例如,在辅助方法“DropDownListFor”中)?
我认为该解决方案可能与IModelBinderProvider或IModelBinder的实现有关,但是在网络上的示例代码片段中对这些内容进行了实验后,我仍在寻找一个完全可行的示例,可下载的可执行代码没有任何缺失如何将所有事情放在一起。
如果您正在寻找有关如何填充选择列表的替代讨论,例如使用“Dependecy Lookup”而不是“Dependecy Injection”,您可能需要查看以下讨论: 在GET / POST上为ViewModel填充SelectList的最佳方法 Best way to populate SelectList for ViewModel on GET/POST
前几天我在该帖子中写了关于“Dependecy Injection”的以下后续问题,我现在正在寻找这个帖子: https://stackoverflow.com/a/8674525/310457 (它提供了一个关于我正在寻找解决方案的问题的代码示例)
但是我并没有希望有人会找到那个标题较少的旧帖子,而是用一个更具体的主题来创建这个新问题,关于我在寻找什么。 对于想要跟进我正在寻找的这个特定解决方案的任何人,我还将提供从该线程到这个新问题的链接。
答案 0 :(得分:8)
我假设您希望自己的ViewModel通过其构造函数自动注入某些内容 - 例如View将用于确定要显示的内容的某种配置对象。我还假设当MVC尝试从Controller Action的参数自动创建和绑定模型实例时,此方法导致“为此对象定义无参数构造函数”错误。然后我们假设我们将使用DI框架在运行时自动将SiteConfig对象注入到我们的控制器中。
这意味着我们必须解决的唯一问题是如何在自动绑定时将Controller中的注入对象引入其Actions的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;
}
}
}
现在我们定义我们的实际控制器。
<强>的HomeController 强>
public HomeController: BaseController
{
public HomeController(ISiteConfig siteConfig): base(siteConfig) {}
}
假设我们将Ninject用于DI,Ninject将被配置为自动创建Controller并在运行时将具体的ISiteConfig
对象传递给其构造函数。
现在我们将Action添加到Controller。
索引操作
public ActionResult Index(IndexViewModel model)
{
return View(model);
}
所以这就是没有做任何其他事情的地方,如果你试图调用Index Action,MVC将会出现“Parameterless Constructor”错误,因为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)。