模型绑定接口属性

时间:2016-01-11 12:30:02

标签: c# asp.net-mvc model-binding

我使用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; }
}

如果我使用TelephoneDto类而不是ITelephone接口,它可以正常工作。

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

@jonathanconway在这个问题中给出的答案接近我想做的事。

Custom model binder for a property

但我真的想将这与简单地告诉defaultbinder用于特定接口的类型的简单性结合起来。排序方式与使用KnownType属性的方式相同。 defaultbinder显然知道如何映射模型,只要它知道它应该创建哪个类。

如何告诉DefaultModelBinder它应该使用什么类来反序列化接口然后绑定它?它目前崩溃,因为发布的模型(AddContactPersonForm)包含&#34;复杂&#34; model(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;
    }
}

2 个答案:

答案 0 :(得分:1)

我认为你错过了一些东西:

ViewModel是一个非可视

应将所有域类型问题映射到ViewModel类。 ViewModel中不需要接口,因为它们都应该特定于您正在呈现的页面/视图。

如果您打算在多个页面上拥有相同的数据 - 您仍然可以使用类 - 只需使用部分视图或子操作。

如果您需要更复杂的模型,请使用继承或合成技术。

只需使用类来完全避免这个问题。

答案 1 :(得分:0)

我认为您需要覆盖模型绑定器的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;
}