模型绑定接口 属性
Modelbinding a interface property
我正在使用 MVC.NET 5.2.3 并尝试 post 模型到控制器,其中模型包含多个接口。这会导致 .NET 抛出此异常:
Cannot create an instance of an interface
我知道这是因为我在我的模型(ITelephone)中使用了接口。我的模型是这样的:
public class AddContactPersonForm
{
public ExternalContactDto ExternalContact { get; set; }
public OrganizationType OrganizationType { get; set; }
}
public class ExternalContactDto
{
public int? Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public IList<ITelephone> TelephoneNumbers { get; set; }
}
public interface ITelephone
{
string TelephoneNumber { get; set; }
}
public class TelephoneDto : ITelephone
{
public string TelephoneNumber { get; set; }
}
如果我使用 TelephoneDto class 而不是 ITelephone 界面,它工作正常。
我知道我需要使用 ModelBinder,这很好。但我真的只是想说modelbinder应该创建什么样的实例,而不是手动映射整个模型。
这个问题@jonathanconway 的回答和我想做的很接近
Custom model binder for a property
但我真的很想将它与简单地告诉 defaultbinder 将什么类型用于特定接口的简单性结合起来。与使用 KnownType 属性的方式类似。 defaultbinder 显然知道如何映射模型,只要它知道应该创建哪个class。
如何告诉 DefaultModelBinder 它应该使用什么 class 来反序列化接口然后绑定它?它当前崩溃是因为 posted (AddContactPersonForm) 的模型包含一个 "complex" 模型 (ExternalContactDto),它具有接口 ITelephone。
这是我目前得到的。
public class ContactPersonController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddContactPerson([ModelBinder(typeof(InterfaceModelBinder))] AddContactPersonForm addContactPersonForm)
{
// Do something with the model.
return View(addContactPersonForm);
}
}
public class InterfaceModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor);
if (propertyBinderAttribute != null)
{
// Never occurs since the model is nested.
var type = propertyBinderAttribute.ActualType;
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
return;
}
// Crashed here since because:
// Cannot create an instance of an interface. Object type 'NR.Delivery.Contract.Models.ITelephone'.
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
private InterfaceBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor)
{
return propertyDescriptor.Attributes
.OfType<InterfaceBinderAttribute>()
.FirstOrDefault();
}
}
public class ExternalContactDto
{
public int? Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
[InterfaceBinder(typeof(List<TelephoneDto>))]
public IList<ITelephone> TelephoneNumbers { get; set; }
}
public class InterfaceBinderAttribute : Attribute
{
public Type ActualType { get; private set; }
public InterfaceBinderAttribute(Type actualType)
{
ActualType = actualType;
}
}
我认为您需要覆盖模型联编程序的 CreateModel 方法,以便您可以创建具有正确实例化属性的模型。您将必须使用一些反射才能根据 InterfaceBinderAttribute.ActualType
属性 动态创建 属性 值。所以类似的东西应该可以工作:
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var model = Activator.CreateInstance(modelType);
var properties = modelType.GetProperties(BindingFlags.Public|BindingFlags.Instance);
foreach(var property in properties)
{
var attribute = property.Attributes.OfType<InterfaceBinderAttribute>().FirstOrDefault();
if(attribute != null)
{
var requiredType = (attribute as InterfaceBinderAttribute).ActualType;
property.SetValue(model, Activator.CreateInstance(requiredType ), null);
}
}
return model;
}
我想你错过了什么:
ViewModel 是一个 non-visual class
所有域类型问题都应映射到 ViewModel classes。您的 ViewModel 中应该不需要接口,因为它们都应该特定于您正在渲染的 page/view。
如果您计划在多个页面上使用相同的数据 - 您仍然可以使用 classes - 只需使用部分视图或子操作。
如果您需要更复杂的模型,请使用继承或组合技术。
只需使用 classes 即可完全避免此问题。
我正在使用 MVC.NET 5.2.3 并尝试 post 模型到控制器,其中模型包含多个接口。这会导致 .NET 抛出此异常:
Cannot create an instance of an interface
我知道这是因为我在我的模型(ITelephone)中使用了接口。我的模型是这样的:
public class AddContactPersonForm
{
public ExternalContactDto ExternalContact { get; set; }
public OrganizationType OrganizationType { get; set; }
}
public class ExternalContactDto
{
public int? Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public IList<ITelephone> TelephoneNumbers { get; set; }
}
public interface ITelephone
{
string TelephoneNumber { get; set; }
}
public class TelephoneDto : ITelephone
{
public string TelephoneNumber { get; set; }
}
如果我使用 TelephoneDto class 而不是 ITelephone 界面,它工作正常。
我知道我需要使用 ModelBinder,这很好。但我真的只是想说modelbinder应该创建什么样的实例,而不是手动映射整个模型。
这个问题@jonathanconway 的回答和我想做的很接近
Custom model binder for a property
但我真的很想将它与简单地告诉 defaultbinder 将什么类型用于特定接口的简单性结合起来。与使用 KnownType 属性的方式类似。 defaultbinder 显然知道如何映射模型,只要它知道应该创建哪个class。
如何告诉 DefaultModelBinder 它应该使用什么 class 来反序列化接口然后绑定它?它当前崩溃是因为 posted (AddContactPersonForm) 的模型包含一个 "complex" 模型 (ExternalContactDto),它具有接口 ITelephone。
这是我目前得到的。
public class ContactPersonController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddContactPerson([ModelBinder(typeof(InterfaceModelBinder))] AddContactPersonForm addContactPersonForm)
{
// Do something with the model.
return View(addContactPersonForm);
}
}
public class InterfaceModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor);
if (propertyBinderAttribute != null)
{
// Never occurs since the model is nested.
var type = propertyBinderAttribute.ActualType;
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
return;
}
// Crashed here since because:
// Cannot create an instance of an interface. Object type 'NR.Delivery.Contract.Models.ITelephone'.
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
private InterfaceBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor)
{
return propertyDescriptor.Attributes
.OfType<InterfaceBinderAttribute>()
.FirstOrDefault();
}
}
public class ExternalContactDto
{
public int? Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
[InterfaceBinder(typeof(List<TelephoneDto>))]
public IList<ITelephone> TelephoneNumbers { get; set; }
}
public class InterfaceBinderAttribute : Attribute
{
public Type ActualType { get; private set; }
public InterfaceBinderAttribute(Type actualType)
{
ActualType = actualType;
}
}
我认为您需要覆盖模型联编程序的 CreateModel 方法,以便您可以创建具有正确实例化属性的模型。您将必须使用一些反射才能根据 InterfaceBinderAttribute.ActualType
属性 动态创建 属性 值。所以类似的东西应该可以工作:
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var model = Activator.CreateInstance(modelType);
var properties = modelType.GetProperties(BindingFlags.Public|BindingFlags.Instance);
foreach(var property in properties)
{
var attribute = property.Attributes.OfType<InterfaceBinderAttribute>().FirstOrDefault();
if(attribute != null)
{
var requiredType = (attribute as InterfaceBinderAttribute).ActualType;
property.SetValue(model, Activator.CreateInstance(requiredType ), null);
}
}
return model;
}
我想你错过了什么:
ViewModel 是一个 non-visual class
所有域类型问题都应映射到 ViewModel classes。您的 ViewModel 中应该不需要接口,因为它们都应该特定于您正在渲染的 page/view。
如果您计划在多个页面上使用相同的数据 - 您仍然可以使用 classes - 只需使用部分视图或子操作。
如果您需要更复杂的模型,请使用继承或组合技术。
只需使用 classes 即可完全避免此问题。