Подтвердить что ты не робот

Техника для переноса метаданных для просмотра моделей с помощью AutoMapper

Я использую AutoMapper для сопоставления объектов моего домена с моими моделями просмотров. У меня есть метаданные в моем доменном слое, которые я хотел бы переносить на уровень представления и в ModelMetadata. (Эти метаданные не являются логикой пользовательского интерфейса, но предоставляют мне необходимую информацию).

В настоящее время мое решение заключается в использовании отдельного метаданных-разработчика (независимо от ASP.NET MVC) и использования соглашений для применения соответствующих метаданных к объекту ModelMetadata через AssociatedMetadataProvider. Проблема с этим подходом заключается в том, что я должен тестировать те же соглашения при привязке ModelMetadata из домена, как и в случае с AutoMapping, и кажется, что должен быть способ сделать это более ортогональным. Может ли кто-нибудь рекомендовать лучший способ достичь этого?

4b9b3361

Ответ 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" был разрешен только один раз, и потребовалось большую часть дня, чтобы отследить его.

Ответ 2

если ваши метаданные снабжены атрибутами, определите атрибуты в MetaDataTypes, затем примените один и тот же тип MetaDataType к вашему классу домена и к вашим режимам просмотра. Вы можете определить все MetaDataTypes в отдельной dll, которая является ссылкой обоими слоями. Есть некоторые проблемы с этим подходом, если у ваших классов ViewModel нет некоторых свойств, которые используются в MetaDataType, но это может быть исправлено с помощью специального поставщика (у меня есть код, если вы похожи на этот подход).

Ответ 3

Решение Betty отлично подходит для "наследования" аннотаций данных. Я продлил эту идею и включил проверку, предоставленную 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));