在软件开发过程中,经常需要在领域模型中使用继承。例如,可能需要对一本书进行建模,其中包含一系列参考文献。每条参考文献可能指向杂志文章、另一本书、网页等。因此,创建了一个名为Reference的抽象类,以及几个用于不同类型引用的子类。也可能想要为模型创建一个并行的继承链。
假设有一个参考文献列表。每一行都有一个链接到可以编辑相应参考文献对象的页面。虽然不同类型的参考文献有不同的字段需要编辑,但不想为每种类型创建一个单独的页面。显示参考文献很简单:只需在表单中调用Html.EditorFor(),它就会为特定类型的参考文献生成必要的字段。问题是,如何获取新的值。
假设有以下操作方法:
[HttpPost]
public ActionResult Update(ReferenceModel model) {
// ...
}
默认的绑定器会尝试创建一个ReferenceModel的实例,但会失败,因为这是一个抽象类。因此,需要使用一个不同的绑定器。这个绑定器足够智能,能够创建具体类型的实例。
为了实现这一点,需要提供模型类型的名称。将通过一个名为ModelType的隐藏字段来实现:
<input type="hidden" name="ModelType" value="<%= this.Model.GetType() %>" />
有人会倾向于重写CreateModel方法,但这还不够。模型会被创建,但它不会被填充为子类特定的属性。绑定器仍然会使用基抽象类的元数据,因此特定于具体类的属性将不会被获取。
因此,将重写BindModel方法并“纠正”模型类型,然后让绑定器为创建并绑定请求类型的模型。以下是代码:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
if (bindingContext.ValueProvider.ContainsPrefix("ModelType")) {
var typeName = (string)bindingContext.ValueProvider.GetValue("ModelType").ConvertTo(typeof(string));
var modelType = Type.GetType(typeName);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, modelType);
}
return base.BindModel(controllerContext, bindingContext);
}
[Test]
public void BinderCreatesArticleModelWithValues() {
var response = new TestSession().Post("/Sample/UpdateReference", new {
ModelType = typeof(ArticleModel).ToString(),
ArticleName = NEW_ARTICLE_NAME
});
Assert.IsInstanceOf(response.ActionMethodParameters["model"]);
var model = (ArticleModel)response.ActionMethodParameters["model"];
Assert.AreEqual(NEW_ARTICLE_NAME, model.ArticleName);
}