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?
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ę.
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).
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));
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