Техника для переноса метаданных для просмотра моделей с помощью AutoMapper
Я использую AutoMapper для сопоставления моих доменных объектов с моими моделями представления. У меня есть метаданные в моем доменном слое, которые я хотел бы перенести на слой представления и в ModelMetadata. (Эти метаданные не являются логикой пользовательского интерфейса, но предоставляют необходимую информацию для моих представлений).
Прямо сейчас мое решение состоит в том, чтобы использовать отдельный MetadataProvider (независимо от ASP.NET MVC) и использовать соглашения для применения соответствующих метаданных к объекту ModelMetadata через AssociatedMetadataProvider. Проблема с этим подходом заключается в том, что при связывании ModelMetadata из домена мне приходится проверять те же соглашения, что и при использовании автоматического сопоставления, и, похоже, должен быть способ сделать это более ортогональным. Кто-нибудь может порекомендовать лучший способ сделать это?
3 ответа
Я использую описанный ниже подход, чтобы автоматически копировать аннотации данных из моих сущностей в модель представления. Это гарантирует, что такие вещи, как StringLength и Required, всегда будут одинаковыми для entity / viewmodel.
Он работает с использованием конфигурации Automapper, поэтому работает, если свойства именуются в модели представления, если 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);
}
}
Валидатор Провиддер
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);
}
}
Вспомогательный метод, указанный в 2 классах
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 разрешено добавлять только один раз, и большую часть дня я занимался его поиском.
Решение Бетти отлично подходит для "наследования" аннотаций данных. Я расширил эту идею, чтобы включить проверку, предоставляемую IValidatableObject.
public class MappedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
private readonly IMapper _mapper;
public MappedModelValidatorProvider(IMapper mapper)
{
_mapper = mapper;
}
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var mappedAttributes = _mapper.ConfigurationProvider.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes);
foreach (var validator in base.GetValidators(metadata, context, mappedAttributes))
{
yield return validator;
}
foreach (var typeMap in _mapper.ConfigurationProvider.GetAllTypeMaps().Where(i => i.SourceType == metadata.ModelType))
{
if (typeof(IValidatableObject).IsAssignableFrom(typeMap.DestinationType))
{
var model = _mapper.Map(metadata.Model, typeMap.SourceType, typeMap.DestinationType);
var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeMap.DestinationType);
yield return new ValidatableObjectAdapter(modelMetadata, context);
}
}
}
}
Затем в Global.asax.cs:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MappedModelValidatorProvider(Mapper.Instance));
Если ваши метаданные снабжены атрибутами, определите атрибуты в MetaDataTypes, а затем примените один и тот же MetaDataType как к классу вашего домена, так и к вашим моделям представления. Вы можете определить все MetaDataTypes в отдельной dll, которая является ссылкой на оба слоя. Есть некоторые проблемы с этим подходом, если ваши классы ViewModel не имеют некоторых свойств, которые используются в MetaDataType, но это можно исправить с помощью специального провайдера (у меня есть код, если вам нравится этот подход).