Technika przenoszenia metadanych do przeglądania modeli za pomocą AutoMapper

Używam AutoMapper do mapowania obiektów mojej domeny do moich modeli widoku. Mam metadane w warstwie domeny, które chciałbym przenieść do warstwy widoku i do ModelMetadata. (Te metadane nie są logiką interfejsu użytkownika, ale dostarczają niezbędnych informacji do moich poglądów).

W tej chwili moim rozwiązaniem jest użycie osobnego Metadataprovidera (niezależnie od ASP.NET MVC) i używać konwencji, aby zastosować odpowiednie metadane do obiektu ModelMetadata poprzez AssociatedMetadataProvider. Problem z tym podejściem jest to, że muszę przetestować te same konwencje podczas wiązania ModelMetadata z domeny, jak to robię z moim AutoMapping, i wydaje się, że powinien być sposób, aby to bardziej ortogonalne. Czy ktoś może polecić lepszy sposób na osiągnięcie tego celu?

Author: smartcaveman, 2012-04-03

3 answers

Używam poniższego podejścia do automatycznego kopiowania adnotacji danych z moich jednostek do mojego modelu widoku. Zapewnia to, że rzeczy takie jak StringLength i wymagane wartości są zawsze takie same dla entity / viewmodel.

Działa przy użyciu konfiguracji Automapper, więc działa, jeśli właściwości są inaczej nazwane w viewmodel, o ile AutoMapper jest poprawnie skonfigurowany.

Musisz utworzyć niestandardowy ModelValidatorProvider i Niestandardowy ModelMetadataProvider, aby to zadziałało. My pamięć NA dlaczego jest trochę zamglona, ale uważam, że tak działa zarówno Walidacja po stronie serwera, jak i klienta, a także wszelkie inne formatowanie, które robisz na podstawie metadanych (np asterix obok wymaganych pól).

Uwaga: nieco uprościłem mój kod, ponieważ dodałem go poniżej, więc może być kilka małych problemów.

Metadata Provider

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);
    }
}

Metoda pomocnicza, o której mowa powyżej 2 klasy

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;
        }
    }

}

Inne Uwagi

  • jeśli używasz dependency injection, upewnij się, że kontener nie zastępuje już wbudowanego dostawcy metadanych lub dostawcy walidatora. W moim przypadku używałem Ninject.Pakiet MVC3, który wiązał jeden z nich po stworzeniu jądra, potem musiałem go ponownieindować, więc moja klasa została faktycznie użyta. Dostawałem wyjątki o tym, że można je dodać tylko raz, śledzenie tego zajęło większość dnia na ziemię.
 14
Author: Betty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-11 10:21:44

Jeśli metadatatype są dostarczane z atrybutami zdefiniuj atrybuty w MetaDataTypes, następnie zastosuj ten sam MetaDataType zarówno do swojej klasy domain, jak i do swoich viewmodels. Możesz zdefiniować wszystkie Metadatypy w oddzielnym dll, który jest odwołaniem przez obie warstwy. Istnieją pewne problemy z tym podejściem, jeśli klasy ViewModel nie mają pewnych właściwości, które są używane w MetaDataType, ale można to naprawić za pomocą niestandardowego dostawcy(Mam kod, jeśli lubisz to podejście).

 1
Author: Francesco Abbruzzese,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-05 18:53:54

Rozwiązanie Betty jest doskonałe do "dziedziczenia" adnotacji danych. Rozszerzyłem ten pomysł o walidację dostarczoną przez 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);
            }
        }
    }
}

Następnie w globalnym.asax.cs:

ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MappedModelValidatorProvider(Mapper.Instance));
 1
Author: Chris Haines,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-03 09:36:26