В ответ на ответ и запрос Михаэля - вот что я в итоге сделал. Я оставил исходный ответ в основном из вежливости, поскольку одно из предложенных Натаном решений сработало бы.
Вывод этого является заменой для класса DefaultModelBinder
, который можно либо зарегистрировать глобально (тем самым позволяя всем типам моделей использовать преимущества псевдонимов), либо выборочно наследовать для пользовательских связывателей моделей.
Все начинается, как и ожидалось, с:
/// <summary>
/// Allows you to create aliases that can be used for model properties at
/// model binding time (i.e. when data comes in from a request).
///
/// The type needs to be using the DefaultModelBinderEx model binder in
/// order for this to work.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class BindAliasAttribute : Attribute
{
public BindAliasAttribute(string alias)
{
//ommitted: parameter checking
Alias = alias;
}
public string Alias { get; private set; }
}
И тогда мы получим этот класс:
internal sealed class AliasedPropertyDescriptor : PropertyDescriptor
{
public PropertyDescriptor Inner { get; private set; }
public AliasedPropertyDescriptor(string alias, PropertyDescriptor inner)
: base(alias, null)
{
Inner = inner;
}
public override bool CanResetValue(object component)
{
return Inner.CanResetValue(component);
}
public override Type ComponentType
{
get { return Inner.ComponentType; }
}
public override object GetValue(object component)
{
return Inner.GetValue(component);
}
public override bool IsReadOnly
{
get { return Inner.IsReadOnly; }
}
public override Type PropertyType
{
get { return Inner.PropertyType; }
}
public override void ResetValue(object component)
{
Inner.ResetValue(component);
}
public override void SetValue(object component, object value)
{
Inner.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return Inner.ShouldSerializeValue(component);
}
}
Это прокси "правильный" PropertyDescriptor, который обычно находится в DefaultModelBinder
, но представляет его имя как псевдоним.
Далее у нас есть новый класс связующих моделей:
public class DefaultModelBinderEx : DefaultModelBinder
{
protected override System.ComponentModel.PropertyDescriptorCollection
GetModelProperties(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var toReturn = base.GetModelProperties(controllerContext, bindingContext);
List<PropertyDescriptor> additional = new List<PropertyDescriptor>();
//now look for any aliasable properties in here
foreach (var p in
this.GetTypeDescriptor(controllerContext, bindingContext)
.GetProperties().Cast<PropertyDescriptor>())
{
foreach (var attr in p.Attributes.OfType<BindAliasAttribute>())
{
additional.Add(new AliasedPropertyDescriptor(attr.Alias, p));
if (bindingContext.PropertyMetadata.ContainsKey(p.Name))
bindingContext.PropertyMetadata.Add(attr.Alias,
bindingContext.PropertyMetadata[p.Name]);
}
}
return new PropertyDescriptorCollection
(toReturn.Cast<PropertyDescriptor>().Concat(additional).ToArray());
}
}
И, с технической точки зрения, это все, что нужно сделать. Теперь вы можете зарегистрировать этот класс DefaultModelBinderEx
по умолчанию, используя решение, опубликованное в качестве ответа в этом SO: Измените подшивку модели по умолчанию в asp.net MVC , или вы можете использовать ее в качестве основы для своего собственная модель переплета.
После того, как вы выбрали свой шаблон для того, как вы хотите, чтобы связыватель сработал, вы просто применяете его к типу модели следующим образом:
public class TestModelType
{
[BindAlias("LPN")]
//and you can add multiple aliases
[BindAlias("L")]
//.. ad infinitum
public string LongPropertyName { get; set; }
}
Причина, по которой я выбрал этот код, заключалась в том, что я хотел что-то, что могло бы работать с дескрипторами пользовательских типов, а также иметь возможность работать с любым типом. Точно так же я хотел, чтобы система провайдеров значений использовалась еще при поиске значений свойств модели. Итак, я изменил метаданные, которые DefaultModelBinder
видит, когда начинается привязка. Это немного более многогранный подход - но концептуально он делает на уровне метаданных именно то, что вы хотите.
Один потенциально интересный и слегка раздражающий побочный эффект будет, если ValueProvider
содержит значения для нескольких псевдонимов или псевдоним и свойство по имени. В этом случае будет использовано только одно из полученных значений. Трудно придумать способ объединения всех этих типов безопасным способом, когда вы просто работаете с object
s. Однако это похоже на предоставление значения как в форме сообщения, так и в строке запроса - и я не уверен, что именно MVC делает в этом сценарии, - но я не думаю, что это рекомендуемая практика.
Другая проблема, конечно, заключается в том, что вы не должны создавать псевдоним, равный другому псевдониму, или имя действительного свойства.
Мне нравится применять мои связующие модели, в общем, используя класс CustomModelBinderAttribute
. Единственная проблема с этим может быть, если вам нужно извлечь из типа модели и изменить ее поведение привязки - так как CustomModelBinderAttribute
наследуется в поиске атрибутов, выполняемом MVC.
В моем случае это нормально, я разрабатываю новую структуру сайта и могу внедрить новую расширяемость в мои базовые связующие, используя другие механизмы для удовлетворения этих новых типов; но это будет не для всех.