Я успешно получил ковариант для работы, то есть привязку View к абстрактному базовому классу. На самом деле, у меня есть привязка к списку . Затем я создаю конкретный вид, строго типизированный для каждого подкласса. Это заботится о привязке.
Но перепривязка не удастся, потому что DefaultModelBinder знает только об абстрактном базовом классе, и вы получите исключение типа «Не удается создать абстрактный класс». Решение состоит в том, чтобы иметь свойство в вашем базовом классе, например:
public virtual string BindingType
{
get
{
return this.GetType().AssemblyQualifiedName;
}
}
Свяжите это со скрытым вводом в вашем представлении. Затем вы замените свой ModelBinder по умолчанию на собственный в Global.asax:
// Replace default model binder with one that can deal with BaseParameter, etc.
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
И в вашей пользовательской связующей модели вы перехватываете привязку. Если это для одного из ваших известных абстрактных типов, вы анализируете свойство BindingType и заменяете тип модели, чтобы получить экземпляр подкласса:
public class CustomModelBinder : DefaultModelBinder
{
private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType)
{
if (modelType.IsInterface || modelType.IsAbstract)
{
// This is our convention for specifying the actual type of a base type or interface.
string key = string.Format("{0}.{1}", bindingContext.ModelName, Constants.UIKeys.BindingTypeProperty);
var boundValue = bindingContext.ValueProvider.GetValue(key);
if (boundValue != null && boundValue.RawValue != null)
{
string newTypeName = ((string[])boundValue.RawValue)[0].ToString();
logger.DebugFormat("Found type override {0} for Abstract/Interface type {1}.", modelType.Name, newTypeName);
try
{
modelType = System.Type.GetType(newTypeName);
}
catch (Exception ex)
{
logger.ErrorFormat("Error trying to create new binding type {0} to replace original type {1}. Error: {2}", newTypeName, modelType.Name, ex.ToString());
throw;
}
}
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
if (propertyDescriptor.ComponentType == typeof(BaseParameter))
{
string match = ".StringValue";
if (bindingContext.ModelName.EndsWith(match))
{
logger.DebugFormat("Try override for BaseParameter StringValue - looking for real type's Value instead.");
string pattern = match.Replace(".", @"\.");
string key = Regex.Replace(bindingContext.ModelName, pattern, ".Value");
var boundValue = bindingContext.ValueProvider.GetValue(key);
if (boundValue != null && boundValue.RawValue != null)
{
// Do some work here to replace the base value with a subclass value...
return value;
}
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
Здесь мой абстрактный класс - BaseParameter, и я заменяю свойство StringValue значением, отличным от подкласса (не показано).
Обратите внимание, что, хотя вы можете выполнить повторную привязку к правильному типу, значения формы, связанные только с подклассом, не будут автоматически округляться, поскольку связыватель модели видит только свойства базового класса. В моем случае мне нужно было только заменить одно значение в GetValue и получить его вместо этого из подкласса, так что это было легко. Если вам нужно связать много свойств подкласса, вам нужно будет проделать немного больше работы и извлечь их из формы (ValueProvider [0]) и заполнить экземпляр самостоятельно.
Обратите внимание, что вы можете добавить новую привязку модели для определенного типа, чтобы избежать проверки универсального типа.