【问题标题】:Modelbinding a interface property模型绑定接口属性
【发布时间】:2016-01-11 12:30:01
【问题描述】:

我正在使用 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,这很好。但是我真的只是想说modelbinder应该创建什么样的实例,而不是手动映射整个模型。

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

Custom model binder for a property

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

如何告诉 DefaultModelBinder 它应该使用什么类来反序列化接口然后绑定它?它目前崩溃,因为发布的模型 (AddContactPersonForm) 包含一个具有接口 ITelephone 的“复杂”模型 (ExternalContactDto)。

这是我目前得到的。

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;
    }
}

【问题讨论】:

  • 那么你的问题到底是什么?
  • @AnupSharma,对不起。我编辑了问题并试图澄清我需要帮助的地方。

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


【解决方案1】:

我认为你错过了什么:

ViewModel 是一个非可视的

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

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

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

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

【讨论】:

  • 这是基于 MVVM 但同样的原则适用 - stackoverflow.com/questions/13156040/…
  • 是的,在这种情况下,AddContactPersonForm 类是我的视图模型。但是,视图模型包含对域类 (ExternalContactDto) 的引用。可以讨论这是否是最好的方法,但在我的项目中,我们不允许像 automapper 这样的第三方解决方案,所以我尽量避免在所有事情上都使用映射器。但是,您的观点是正确的。通过使用更具体的视图模型解决了这个问题。
  • 如果您正在认真考虑这可能会有所帮助,此示例创建一个自定义属性绑定器,并使用 IOC 注入一个可以为属性提供值的组件。 (这也将有助于您尚未遇到的其他属性)aboutcode.net/2011/03/12/mvc-property-binder.html
  • 谢谢,但经过进一步考虑,我认为您是对的。这会回来咬我。我的视图模型不应引用域模型,因此不需要接口。
  • 好电话 - 我必须承认你不能使用映射器是一种耻辱 - 但你可以自己映射它们。祝你好运!
【解决方案2】:

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

【讨论】:

  • 我可能误解了你的答案,但问题是当我使用这种方法时,它会遍历视图模型的属性(AddContactPersonForm),而不是嵌套模型的属性(ExternalContactDto)。 CreateModel 只被调用一次,那就是视图模型。我的界面位于视图模型属性中的类中。
  • 在这个模型绑定器中,您创建AddContactPersonForm 对象,然后循环遍历它的所有属性,那些具有InterfaceBinderAttribute 的属性将使用基于InterfaceBinderAttribute 的具体类型实例化。它确实只被调用一次
猜你喜欢
  • 2018-01-02
  • 2016-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-19
  • 2011-03-02
  • 1970-01-01
相关资源
最近更新 更多