Моделирование свойства интерфейса
1 smoksnes [2016-01-11 15:30:00]
Я использую 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; }
}
Он отлично работает, если я использую класс PhoneDto вместо интерфейса ITelephone.
Я понимаю, что мне нужно использовать ModelBinder, и это нормально. Но я действительно хочу сказать, какой экземпляр должен быть создан для создания модели, а не для сопоставления всей модели вручную.
Ответ @jonathanconway дал в этом вопросе близко к тому, что я хочу сделать.
Пользовательское связующее устройство для объекта
Но я действительно хотел бы объединить это с простотой просто сказать defaultbinder, какой тип использовать для определенного интерфейса. Аналогичным образом вы можете использовать атрибут KnownType. Функция defaultblinder явно знает, как сопоставить модель, если она знает, какой класс она должна создать.
Как я могу указать 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;
}
}
c# asp.net-mvc model-binding
2 ответа
1 Решение MichaelLake [2016-01-11 16:27:00]
Я думаю, что вы что-то пропустили:
ViewModel - это невизуальный класс
Все проблемы типа домена должны отображаться в классах ViewModel. в ваших ViewModels не должно быть необходимости в интерфейсах, поскольку все они должны быть конкретными для отображаемой страницы/представления.
Если вы планируете иметь одни и те же данные на нескольких страницах, вы все равно можете использовать классы - просто используйте частичные представления или дочерние действия.
Если вам нужна более сложная модель, используйте методы наследования или композиции.
Просто используйте классы, чтобы вообще избежать этой проблемы.
0 Alex Art. [2016-01-11 16:03:00]
Я думаю, что вам нужно переопределить метод 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;
}