Скажем, у меня есть модель Product, у модели Product есть свойство ProductSubType (аннотация), и у нас есть две конкретные реализации Shirt и Pants.
Вот источник:
public class Product
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public decimal? Price { get; set; }
[Required]
public int? ProductType { get; set; }
public ProductTypeBase SubProduct { get; set; }
}
public abstract class ProductTypeBase { }
public class Shirt : ProductTypeBase
{
[Required]
public string Color { get; set; }
public bool HasSleeves { get; set; }
}
public class Pants : ProductTypeBase
{
[Required]
public string Color { get; set; }
[Required]
public string Size { get; set; }
}
В моем пользовательском интерфейсе у пользователя есть выпадающий список, он может выбрать тип продукта, а элементы ввода отображаются в соответствии с нужным типом продукта. Я все это выяснил (используя ajax, включите выпадающее меню, верните шаблон части / редактора и переустановите проверку jquery соответствующим образом).
Далее я создал привязку пользовательской модели для ProductTypeBase.
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ProductTypeBase subType = null;
var productType = (int)bindingContext.ValueProvider.GetValue("ProductType").ConvertTo(typeof(int));
if (productType == 1)
{
var shirt = new Shirt();
shirt.Color = (string)bindingContext.ValueProvider.GetValue("SubProduct.Color").ConvertTo(typeof(string));
shirt.HasSleeves = (bool)bindingContext.ValueProvider.GetValue("SubProduct.HasSleeves").ConvertTo(typeof(bool));
subType = shirt;
}
else if (productType == 2)
{
var pants = new Pants();
pants.Size = (string)bindingContext.ValueProvider.GetValue("SubProduct.Size").ConvertTo(typeof(string));
pants.Color = (string)bindingContext.ValueProvider.GetValue("SubProduct.Color").ConvertTo(typeof(string));
subType = pants;
}
return subType;
}
}
Это правильно связывает значения и работает по большей части, за исключением того, что я теряю проверку на стороне сервера. Итак, догадываясь, что я делаю это неправильно, я провел еще несколько поисков и наткнулся на ответ Дарина Димитрова:
ASP.NET MVC 2 - привязка к абстрактной модели
Итак, я переключил связыватель модели, чтобы он переопределял только CreateModel, но теперь он не связывает значения.
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
ProductTypeBase subType = null;
var productType = (int)bindingContext.ValueProvider.GetValue("ProductType").ConvertTo(typeof(int));
if (productType == 1)
{
subType = new Shirt();
}
else if (productType == 2)
{
subType = new Pants();
}
return subType;
}
Если выполнить MVC 3 src, похоже, что в BindProperties GetFilteredModelProperties возвращает пустой результат, и я думаю, это потому, что для модели bindingcontext задано значение ProductTypeBase, которое не имеет никаких свойств.
Может кто-нибудь заметить, что я делаю не так? Это не похоже, что это должно быть так сложно. Я уверен, что упускаю что-то простое ... У меня есть другая альтернатива, вместо того, чтобы вместо свойства SubProduct в модели Product иметь отдельные свойства для рубашки и штанов. Это просто модели View / Form, так что я думаю, что это сработает, но хотелось бы, чтобы текущий подход работал, если что-нибудь понимает, что происходит ...
Спасибо за любую помощь!
Обновление:
Я не прояснил, но добавленный мной пользовательский механизм связывания наследуется от DefaultModelBinder
Ответ
Установка ModelMetadata и Model была отсутствующей частью. Спасибо, Манас!
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
if (modelType.Equals(typeof(ProductTypeBase))) {
Type instantiationType = null;
var productType = (int)bindingContext.ValueProvider.GetValue("ProductType").ConvertTo(typeof(int));
if (productType == 1) {
instantiationType = typeof(Shirt);
}
else if (productType == 2) {
instantiationType = typeof(Pants);
}
var obj = Activator.CreateInstance(instantiationType);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType);
bindingContext.ModelMetadata.Model = obj;
return obj;
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}