MVC 3 Привязка модели подтипа (абстрактный класс или интерфейс)

Скажем, у меня есть модель 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);

        }

50
задан Community 23 May 2017 в 02:25
поделиться