16

Ich verwende AutoMapper, um meine Domänenobjekte meinen Ansichtsmodellen zuzuordnen. Ich habe Metadaten in meiner Domain-Ebene, die ich in die Ansichtsebene und in ModelMetadata übertragen möchte. (Diese Metadaten sind keine UI-Logik, sondern liefern die notwendigen Informationen für meine Ansichten).Verfahren zum Übertragen von Metadaten an Ansichtsmodelle mit AutoMapper

Derzeit besteht meine Lösung darin, einen separaten MetadataProvider (unabhängig von ASP.NET MVC) zu verwenden und Konventionen zu verwenden, um die relevanten Metadaten über einen AssociatedMetadataProvider auf das ModelMetadata-Objekt anzuwenden. Das Problem bei diesem Ansatz besteht darin, dass ich bei der Bindung von ModelMetadata aus der Domäne nach denselben Konventionen testen muss, wie ich es mit meinem AutoMapping mache, und es scheint, als sollte es eine Möglichkeit geben, diese orthogonaler zu machen. Kann jemand einen besseren Weg empfehlen, dies zu erreichen?

+0

zeigen einige Code, so können wir sehen, was Sie tun. – rboarman

Antwort

14

Ich verwende den folgenden Ansatz, um automatisch Datenanmerkungen von meinen Entitäten in mein Ansichtsmodell zu kopieren. Dies stellt sicher, dass Dinge wie StringLength und Required für Entity/ViewModel immer gleich sind.

Es funktioniert mit der Automapper-Konfiguration, funktioniert also, wenn die Eigenschaften im Viewmodel unterschiedlich benannt sind, solange AutoMapper richtig eingerichtet ist.

Sie müssen einen benutzerdefinierten ModelValidatorProvider und einen benutzerdefinierten ModelMetadataProvider erstellen, damit dies funktioniert. Meine Erinnerung an warum ist ein wenig neblig, aber ich glaube, dass es sowohl serverseitige und clientseitige Validierungsarbeiten sind, als auch jede andere Formatierung, die Sie basierend auf den Metadaten vornehmen (zB ein Stern neben den erforderlichen Feldern).

Hinweis: Ich habe meinen Code leicht vereinfacht, da ich ihn unten hinzugefügt habe, daher kann es einige kleine Probleme geben.

Metadaten-Anbieter

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

Helper oben beschriebene Methode 2 Klassen

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

} 
Referenced in Hinweise

Andere

  • Wenn Sie Dependency Injection verwenden, stellen Sie sicher, dass Ihr Behälter nicht bereits mit dem eingebauten Metadaten-Anbieter oder Validator Anbieter zu ersetzen. In meinem Fall benutzte ich das Ninject.MVC3-Paket, das einen von ihnen gebunden hat, nachdem ich den Kernel erstellt hatte, und musste ihn anschließend erneut binden, so dass meine Klasse tatsächlich verwendet wurde. Ich bekam Ausnahmen von Required, die nur einmal hinzugefügt werden durften, brauchte den größten Teil eines Tages, um es aufzuspüren.
+0

das ist hilfreich. Ich habe einen ähnlichen Ansatz, den ich jetzt verwende, aber mit meiner eigenen Metadatenquelle (nicht AutoMapper). Es könnte erweitert werden, um das zu tun, was auch Ihr tut. Helfen Sie mir, etwas zu verstehen: Sie geben metadata.ContainerType als Quelltyp ein, aber es scheint, als würde es nach dem Typ Ihres Geschäftsobjekts suchen. Dadurch denke ich, dass Sie (a) ModelMetadata für Ihr Geschäftsobjekt abrufen und die Ansichtsmodellattribute kopieren oder (b) Ihre Ansichtsmodelle mit AutoMapper auf Ihre Geschäftsobjekte abbilden (mein Anwendungsfall ist das Gegenteil). Kannst du das klären? – smartcaveman

+0

Es sieht so aus, als ob der 'IConfigurationProvider' der Ort ist, an dem man arbeiten kann. Betrachtet man die [Quelle] (https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/IConfigurationProvider.cs), scheint es ein besserer Ansatz für mein Szenario zu sein, eine Verkabelung zu Event EventHandler zu erstellen TypeMapCreatedEventArgs> TypeMapCreated; 'in meinem IoC. Hast du diese Art von Ansatz versucht? [Es sieht so aus, als ob es jedes Mal ausgelöst wird, wenn ein Typ erstellt wird] (https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/ConfigurationStore.cs), damit ich das in meinen bestehenden Metadaten-Provider – smartcaveman

+0

einbinden kann Ich kartiere beide Richtungen mit AutoMapper. Dieser Code dient zum Anwenden der Metadaten aus den Geschäftsobjekten auf meine Ansichtsmodelle. Ich verwende jedoch die Zuordnungen, die in die andere Richtung gehen, um die Metadaten zu finden. Kein besonderer Grund, den ich kenne, und jetzt, wo du es erwähnst, scheint es ein bisschen seltsam zu sein. – Betty

1

Wenn Ihre Metadaten mit Attributen versehen sind, definieren Sie die Attribute in MetaDataTypes, und wenden Sie denselben MetaDataType auf Ihre Domänenklasse und auf Ihre Ansichtsmodelle an. Sie können alle MetaDataTypes in einer separaten DLL definieren, die von beiden Layern referenziert wird. Es gibt einige Probleme mit diesem Ansatz, wenn Ihre ViewModel-Klassen nicht über einige Eigenschaften verfügen, die im MetaDataType verwendet werden, aber dies kann mit einem benutzerdefinierten Provider behoben werden (ich habe den Code, wenn Sie diesen Ansatz mögen).

+0

Die Metadaten stammen von verschiedenen Orten, und ich habe derzeit keine dedizierten 'Metadata' Klassen (und will sie auch nicht). Ich denke, dass dies ein guter Ort für mich sein könnte, um nach einem Erweiterbarkeitspunkt zu suchen. Danke – smartcaveman

+0

Sie könnten eine Art "Broker" -Metadatenanbieter schreiben, der einfach den Metadatenabruf von einem Typ an einen anderen umleitete. Dann könnten Sie die ViewModel-Klasse zum Domain-Typ "umleiten". –

+0

Das ist im Grunde, was ich gerade mache. Das Problem besteht darin, dass der 'Broker' das ModelMetadata-Objekt auswertet, um einen Verweis auf die Metadaten für das zugeordnete Domänenobjekt zu erhalten. Und die Logik, die benötigt wird, um zu wissen, wie die Ansicht eine Projektion von ist, entspricht in etwa der Logik, die ich für AutoMapping verwende. Ich möchte die Redundanz loswerden. – smartcaveman

1

Bettys Lösung eignet sich hervorragend zum "Erben" von Datenanmerkungen. Ich habe diese Idee erweitert, um auch die von IValidatableObject bereitgestellte Validierung einzuschließen.

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

Dann in Global.asax.cs:

ModelValidatorProviders.Providers.Clear(); 
ModelValidatorProviders.Providers.Add(new MappedModelValidatorProvider(Mapper.Instance));