Ответ 1
Я использую приведенный ниже подход, чтобы автоматически копировать аннотации данных из моих объектов в мою модель представления. Это гарантирует, что такие вещи, как StringLength и обязательные значения, всегда одинаковы для модели entity/viewmodel.
Он работает с использованием конфигурации Automapper, поэтому работает, если свойства называются по-разному на viewmodel, пока AutoMapper настроен правильно.
Вам нужно создать пользовательский ModelValidatorProvider и пользовательский ModelMetadataProvider, чтобы это работало. Моя память о том, почему немного туманна, но я считаю, что это работает как проверка на стороне сервера, так и на стороне клиента, а также любое другое форматирование, которое вы делаете на основе метаданных (например, астерикс рядом с обязательными полями).
Примечание. Я немного упростил свой код, добавив его ниже, поэтому может быть несколько небольших проблем.
Поставщик метаданных
public class MetadataProvider : DataAnnotationsModelMetadataProvider
{
private IConfigurationProvider _mapper;
public MetadataProvider(IConfigurationProvider mapper)
{
_mapper = mapper;
}
protected override System.Web.Mvc.ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
//Grab attributes from the entity columns and copy them to the view model
var mappedAttributes = _mapper.GetMappedAttributes(containerType, propertyName, attributes);
return base.CreateMetadata(mappedAttributes, containerType, modelAccessor, modelType, propertyName);
}
}
Validator Provivder
public class ValidatorProvider : DataAnnotationsModelValidatorProvider
{
private IConfigurationProvider _mapper;
public ValidatorProvider(IConfigurationProvider mapper)
{
_mapper = mapper;
}
protected override System.Collections.Generic.IEnumerable<ModelValidator> GetValidators(System.Web.Mvc.ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var mappedAttributes = _mapper.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes);
return base.GetValidators(metadata, context, mappedAttributes);
}
}
Помощник Метод, упомянутый выше в двух классах
public static IEnumerable<Attribute> GetMappedAttributes(this IConfigurationProvider mapper, Type sourceType, string propertyName, IEnumerable<Attribute> existingAttributes)
{
if (sourceType != null)
{
foreach (var typeMap in mapper.GetAllTypeMaps().Where(i => i.SourceType == sourceType))
{
foreach (var propertyMap in typeMap.GetPropertyMaps())
{
if (propertyMap.IsIgnored() || propertyMap.SourceMember == null)
continue;
if (propertyMap.SourceMember.Name == propertyName)
{
foreach (ValidationAttribute attribute in propertyMap.DestinationProperty.GetCustomAttributes(typeof(ValidationAttribute), true))
{
if (!existingAttributes.Any(i => i.GetType() == attribute.GetType()))
yield return attribute;
}
}
}
}
}
if (existingAttributes != null)
{
foreach (var attribute in existingAttributes)
{
yield return attribute;
}
}
}
Другие заметки
- Если вы используете инъекцию зависимостей, убедитесь, что ваш контейнер уже не заменяет встроенного поставщика метаданных или поставщика проверки подлинности. В моем случае я использовал пакет Ninject.MVC3, который связал один из них после создания ядра, после чего мне пришлось переподчинить его, чтобы мой класс был фактически использован. Я получал исключения о том, что "Required" был разрешен только один раз, и потребовалось большую часть дня, чтобы отследить его.