Скажем, у меня есть модель 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; }
}
В моем UI у пользователя есть выпадающий список, он может выбрать тип продукта, и элементы ввода отображаются в соответствии с нужным типом продукта. Я разобрался со всем этим (использую ajax get при изменении выпадающего списка, возвращаю шаблон partial/editor и соответствующим образом перенастраиваю валидацию 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 - Binding To Abstract Model
Поэтому я изменил связующую модель, чтобы она переопределяла только 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 и Pants. Это просто модели 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);
}