2015-07-29 18 views
10

Mam dwa bardzo proste obiekty:klienta Mapowanie z AutoMapper

public class CategoryDto 
{ 
    public string Id { get; set; } 

    public string MyValueProperty { get; set; } 
} 

public class Category 
{ 
    public string Id { get; set; } 

    [MapTo("MyValueProperty")] 
    public string Key { get; set; } 
} 

Podczas mapowania Category Do CategoryDto z AutoMapper Chciałbym następujące zachowanie:

The właściwości powinny być odwzorowane jak zwykle, z wyjątkiem tych, które mają atrybut MapTo. W takim przypadku muszę odczytać wartość atrybutu, aby znaleźć właściwość docelową. Wartość właściwości źródłowej służy do znalezienia wartości do wstrzyknięcia we właściwość docelową (za pomocą słownika). Przykładem jest zawsze lepsze niż 1000 słów ...

Przykład:

Dictionary<string, string> keys = 
    new Dictionary<string, string> { { "MyKey", "MyValue" } }; 

Category category = new Category(); 
category.Id = "3"; 
category.Key = "MyKey"; 

CategoryDto result = Map<Category, CategoryDto>(category); 
result.Id    // Expected : "3" 
result.MyValueProperty // Expected : "MyValue" 

Obiekt Key jest mapowany do MyValueProperty (przez MapTo Attribute) oraz przyporządkowana jest wartość „MyValue”, ponieważ Wartość właściwości źródła to "MyKey", która jest mapowana (przez słownik) na "MyValue".

Czy jest to możliwe przy użyciu AutoMapera? Potrzebuję oczywiście rozwiązania, które działa na każdym obiekcie, nie tylko na Category/CategoryDto.

+0

Dlaczego trzeba atrybutów , w pierwszym kroku możesz skonfigurować niestandardowe odwzorowania i odwzorować klucz właściwości na wartość. Czy to jest możliwe? – whymatter

+0

Chciałbym stworzyć ogólny program odwzorowujący, którego będę mógł użyć wszędzie ... wtedy mógłbym odwzorować dowolną jednostkę na dowolne dto bez żadnego dodatkowego kodu ... – Bidou

+0

Imho, sprawiłeś, że twoja istota jest odpowiedzialna za coś, za co nie powinna być odpowiedzialna. Model viewmodel powinien określić, gdzie powinien uzyskać dane potrzebne do jego zbudowania, a nie odwrotnie. – Peter

Odpowiedz

6

W końcu (po tylu godzinach !!!!) znalazłem rozwiązanie. Dzielę się tym ze społecznością; mam nadzieję, że to pomoże ktoś inny ...

public static class Extensions 
{ 
    public static IMappingExpression<TSource, TDestination> MapTo<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression) 
    { 
     Type sourceType = typeof(TSource); 
     Type destinationType = typeof(TDestination); 

     TypeMap existingMaps = Mapper.GetAllTypeMaps().First(b => b.SourceType == sourceType && b.DestinationType == destinationType); 
     string[] missingMappings = existingMaps.GetUnmappedPropertyNames(); 

     if (missingMappings.Any()) 
     { 
      PropertyInfo[] sourceProperties = sourceType.GetProperties(); 
      foreach (string property in missingMappings) 
      { 
       foreach (PropertyInfo propertyInfo in sourceProperties) 
       { 
        MapToAttribute attr = propertyInfo.GetCustomAttribute<MapToAttribute>(); 
        if (attr != null && attr.Name == property) 
        { 
         expression.ForMember(property, opt => opt.ResolveUsing(new MyValueResolve(propertyInfo))); 
        } 
       } 
      } 
     } 

     return expression; 
    } 
} 

public class MyValueResolve : IValueResolver 
{ 
    private readonly PropertyInfo pInfo = null; 

    public MyValueResolve(PropertyInfo pInfo) 
    { 
     this.pInfo = pInfo; 
    } 

    public ResolutionResult Resolve(ResolutionResult source) 
    { 
     string key = pInfo.GetValue(source.Value) as string; 
     string value = dictonary[key]; 
     return source.New(value); 
    } 
} 
+5

Cieszę się, że znalazłeś rozwiązanie. Czy możesz nieco poszerzyć swoją odpowiedź, aby wyjaśnić, jakie to podejście przyjmuje i podać przykład użycia? – JohnnyHK

0

To powinno być dość proste przy użyciu implementacji metody IValueResolver i ResolveUsing(). Zasadniczo potrzebujesz konstruktora dla programisty, który pobiera informacje o właściwościach (lub jeśli chcesz mieć ochotę, który przyjmuje wyrażenie lambda i rozwiązuje informacje o właściwościach podobne do How to get the PropertyInfo of a specific property?. Chociaż sam tego nie przetestowałem, wyobrażam sobie następujących będzie działać:

public class PropertyBasedResolver : IValueResolver 
{ 
    public PropertyInfo Property { get; set; } 

    public PropertyBasedResolver(PropertyInfo property) 
    { 
      this.Property = property; 
    } 

    public ResolutionResult Resolve(ResolutionResult source) 
    { 
      var result = GetValueFromKey(property, source.Value); // gets from some static cache or dictionary elsewhere in your code by reading the prop info and then using that to look up the value based on the key as appropriate 
      return source.New(result) 
    } 
} 

Następnie założyć, że mapowanie trzeba zrobić:

AutoMapper.Mapper.CreateMap<Category, CategoryDto>() 
    .ForMember(
     dest => dest.Value, 
     opt => opt.ResolveUsing(
       src => 
        new PropertyBasedResolver(typeof(Category.Key) as PropertyInfo).Resolve(src))); 

oczywiście to jest lambda całkiem brutto i chciałbym zaproponować, aby go oczyścić poprzez swoją nieruchomość resolver określa właściwość obiektu źródłowego, na który powinien patrzeć w oparciu o informacje o atrybucie/właściwości, aby można było ju st przekazać czysty nowy PropertyBasedResolver (właściwość) do ResolveUsing(), ale mam nadzieję, że to wyjaśnia wystarczająco, aby umieścić na właściwej drodze.

+0

Dziękuję za odpowiedź. Właściwie właściwość 'Value' nie jest znana podczas kompilacji. Muszę to odkryć za pomocą atrybutu 'MapTo', więc to rozwiązanie nie działa ... – Bidou

+0

Nie rozumiem.Czy mówisz, że własność 'Value' sama nie jest znana (tak jak w ogóle nie masz pojęcia o dto)? Czy jest pytanie, jak zmapować dynamiczny dto do znanego modelu? –

+0

Uaktualniłem pytanie, może teraz jest trochę bardziej zrozumiałe. W tym przykładzie 'Id' jest zamapowany na' Id' (domyślne odwzorowanie), a 'Key' powinien być zmapowany do' MyValueProperty', ponieważ ma atrybut 'MapTo', który wskazuje na' MyValueProperty '. Wartość samego modelu to "MyKey", więc wartość w DTO będzie miała wartość "MyValue", ponieważ jest odwzorowana w ten sposób w słowniku. Czy jest to trochę bardziej jasne? – Bidou

0

Załóżmy mam następujące klasy

public class foo 
{ 
    public string Value; 
} 
public class bar 
{ 
    public string Value1; 
    public string Value2; 
} 

można przekazać lambda do ResolveUsing:

.ForMember(f => f.Value, o => o.ResolveUsing(b => 
{ 
    if (b.Value1.StartsWith("A"));) 
    { 
     return b.Value1; 
    } 
    return b.Value2; 
} 


)); 
+0

Nie, nie możesz tego zrobić ... "Wartość" nie jest znana, muszę pracować nad generalnym źródłem TSource i TDestination ... w przeciwnym razie, muszę utworzyć "ForMember" dla wszystkich jednostek DTO <--> wielki projekt, który mógłby potencjalnie być setką!). Z moim rozwiązaniem, robię to tylko raz ... – Bidou

Powiązane problemy