wake-up-neo.com

Benutzerdefinierte Zuordnung mit AutoMapper

Ich habe zwei sehr einfache Objekte:

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

Wenn ich eine Category einer CategoryDto mit AutoMapper zuordnen möchte, möchte ich folgendes Verhalten: 

Die Eigenschaften sollten wie üblich zugeordnet werden, mit Ausnahme der Eigenschaften, die das Attribut MapTo besitzen. In diesem Fall muss ich den Wert des Attributs lesen, um die Zieleigenschaft zu finden. Der Wert der Quelleigenschaft wird verwendet, um den Wert zu finden, der in die Zieleigenschaft eingefügt werden soll (mithilfe eines Wörterbuchs). Ein Beispiel ist immer besser als 1000 Wörter ...

Beispiel:

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"

Die Key-Eigenschaft wird der MyValueProperty (über das MapTo-Attribut) zugeordnet. Der zugewiesene Wert ist "MyValue", da der Quelleigenschaftswert "MyKey" ist, der (über das Wörterbuch) "MyValue" zugeordnet wird.

Ist dies mit AutoMapper möglich? Ich brauche natürlich eine Lösung, die für jedes Objekt funktioniert, nicht nur für Category/CategoryDto.

12
Bidou

Ich habe endlich (nach so vielen Stunden !!!!) eine Lösung gefunden. Ich teile dies mit der Community; hoffentlich wird es jemand anderem helfen ...

Editieren: Beachten Sie, dass es jetzt viel einfacher ist (AutoMapper 5.0+). Sie können wie ich in diesem Beitrag geantwortet haben: Wie kann man AutoMapper dazu bringen, Zeichenketten nach dem MaxLength-Attribut abzuschneiden?

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

Dies sollte mit einer Implementierung von IValueResolver und der ResolveUsing () -Methode relativ einfach sein. Sie benötigen im Grunde nur einen Konstruktor für den Resolver, der die Eigenschaftsinformationen aufnimmt (oder, wenn Sie Lust haben, einen Lambda-Ausdruck aufzunehmen und die Eigenschaftsinformationen auflösen, ähnlich wie Wie erhalte ich die PropertyInfo einer bestimmten Eigenschaft? Obwohl ich es nicht selbst getestet habe, kann ich mir Folgendes vorstellen:

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

Um das Mapping einzurichten, müssen Sie Folgendes tun:

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

Natürlich ist das ein ziemlich grobes Lamda, und ich würde vorschlagen, dass Sie es bereinigen, indem Sie Ihren Property Resolver die Eigenschaft für das Quellobjekt festlegen, das auf der Grundlage des Attributs/der Property-Informationen betrachtet werden soll. So können Sie einfach einen neuen PropertyBasedResolver ( Eigenschaft) in ResolveUsing (), aber hoffentlich erklärt das genug, um Sie auf die richtige Spur zu bringen.

0
Daniel King

Nehmen wir an, ich habe die folgenden Klassen

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

Sie können ein Lambda an ResolveUsing übergeben:

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


 ));
0
Naimish Mungara