Скажем, у меня есть модель продукта, модель продукта имеет свойство ProductSubType (abstract), и у нас есть две конкретные реализации Shirt and 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, чтобы иметь только отдельные свойства для Shirt and Pants. Это всего лишь модели View/Form, поэтому я думаю, что это сработает, но хотелось бы, чтобы текущий подход работал, если что-то понять, что происходит...
Спасибо за любую помощь!
Update:
Я не дал понять, но добавленное пользовательское связующее устройство наследует от DefaultModelBinder
Ответ
Установка ModelMetadata и модели была отсутствующей частью. Спасибо Манас!
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);
}