C# 接口属性的模型绑定

C# 接口属性的模型绑定,c#,asp.net-mvc,model-binding,C#,Asp.net Mvc,Model Binding,我正在使用MVC.NET 5.2.3,并尝试将一个模型发布到一个控制器,其中该模型包含多个接口。这会导致.NET引发此异常: 无法创建接口的实例 我明白这是因为我在模型(ITelephone)中使用了接口。我的模型如下: public class AddContactPersonForm { public ExternalContactDto ExternalContact { get; set; } public OrganizationType OrganizationTyp

我正在使用MVC.NET 5.2.3,并尝试将一个模型发布到一个控制器,其中该模型包含多个接口。这会导致.NET引发此异常:

无法创建接口的实例

我明白这是因为我在模型(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; }
}
公共类AddContactPersonForm
{
public externalcontactd到ExternalContact{get;set;}
公共组织类型组织类型{get;set;}
}
公共类外部ContactdTo
{
公共int?Id{get;set;}
公共字符串名称{get;set;}
公共字符串标题{get;set;}
公共IList电话号码{get;set;}
}
公共接口ITelephone
{
字符串电话号码{get;set;}
}
公共类电话:ITelephone
{
公共字符串电话号码{get;set;}
}
如果我使用TelephoneDto类而不是ITelephone接口,它可以正常工作

我知道我需要使用ModelBinder,这很好。但我只想说modelbinder应该创建什么样的实例,而不是手动映射整个模型

“jonathanconway”在这个问题上给出的答案与我想做的很接近

但我真的希望将此与简单地告诉defaultbinder特定接口使用什么类型相结合。与使用KnownType属性的方式类似。defaultbinder显然知道如何映射模型,只要它知道应该创建哪个类

我如何告诉DefaultModelBinder应该使用什么类来反序列化接口,然后绑定它?它当前崩溃是因为发布的模型(AddContactPersonForm)包含一个“复杂”模型(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;
    }
}
公共类联系人控制器:控制器
{
[HttpPost]
[ValidateAntiForgeryToken]
公共操作结果AddContactPerson([ModelBinder(typeof(InterfaceModelBinder))]AddContactPersonForm AddContactPersonForm)
{
//对模型做点什么。
返回视图(addContactPersonForm);
}
}
公共类InterfaceModelBinder:DefaultModelBinder
{
受保护的覆盖无效BindProperty(ControllerContext ControllerContext、ModelBindingContext bindingContext、,
PropertyDescriptor PropertyDescriptor)
{
var propertyBinderAttribute=TryFindPropertyBinderAttribute(propertyDescriptor);
if(propertyBinderAttribute!=null)
{
//从未发生,因为模型是嵌套的。
变量类型=propertyBinderAttribute.ActualType;
var model=Activator.CreateInstance(类型);
bindingContext.ModelMetadata=ModelMetadataProviders.Current.GetMetadataForType(()=>model,type);
BindProperty(controllerContext、bindingContext、propertyDescriptor);
返回;
}
//在这里坠毁是因为:
//无法创建接口的实例。对象类型“NR.Delivery.Contract.Models.ITelephone”。
BindProperty(controllerContext、bindingContext、propertyDescriptor);
}
私有接口BinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor PropertyDescriptor)
{
返回propertyDescriptor.Attributes
第()类
.FirstOrDefault();
}
}
公共类外部ContactdTo
{
公共int?Id{get;set;}
公共字符串名称{get;set;}
公共字符串标题{get;set;}
[接口项(类型(列表))]
公共IList电话号码{get;set;}
}
公共类InterfaceBinderAttribute:属性
{
公共类型实际类型{get;private set;}
公共接口BinderAttribute(类型actualType)
{
实际类型=实际类型;
}
}

我认为您需要重写model binder的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;
}
受保护的重写对象CreateModel(ControllerContext ControllerContext,ModelBindingContext bindingContext,Type modelType)
{      
var model=Activator.CreateInstance(modelType);
var properties=modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach(属性中的var属性)
{
var attribute=property.Attributes.OfType().FirstOrDefault();
if(属性!=null)
{
var requiredType=(作为InterfaceBinderAttribute的属性);
property.SetValue(model,Activator.CreateInstance(requiredType),null);
}
}
收益模型;
}

我认为您需要重写model binder的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;
}
受保护的重写对象CreateModel(ControllerContext ControllerContext,ModelBindingContext bindingContext,Type modelType)
{      
var model=Activator.CreateInstance(modelType);
var properties=modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach(属性中的var属性)
{
var attribute=property.Attributes.OfType().FirstOrDefault();
if(属性!=null)
{
var requiredType=(作为InterfaceBinderAttribute的属性);
property.SetValue(model,Activator.CreateInstance(requiredType),null);
}
}
收益模型;
}

我想你错过了什么:

ViewModel是一个非可视类

所有域类型关注点都应映射到ViewModel CLA