web-dev-qa-db-fra.com

Cartographie personnalisée avec AutoMapper

J'ai deux objets très simples:

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

Lors du mappage d'une Category sur une CategoryDto avec AutoMapper, j'aimerais le comportement suivant: 

Les propriétés doivent être mappées comme d'habitude, à l'exception de celles qui possèdent l'attribut MapTo. Dans ce cas, je dois lire la valeur de l'attribut pour trouver la propriété cible. La valeur de la propriété source est utilisée pour trouver la valeur à injecter dans la propriété de destination (à l'aide d'un dictionnaire). Un exemple vaut toujours mieux que 1000 mots ...

Exemple:

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"

La propriété Key est mappée sur la MyValueProperty (via l'attribut MapTo) et la valeur affectée est "MyValue", car la valeur de la propriété source est "MyKey" qui est mappée (via le dictionnaire) sur "MyValue".

Est-ce possible d'utiliser AutoMapper? J'ai évidemment besoin d'une solution qui fonctionne sur tous les objets, pas seulement sur Category/CategoryDto.

12
Bidou

J'ai finalement (après tant d'heures !!!!) trouvé une solution. Je la partage avec la communauté; j'espère que cela aidera quelqu'un d'autre ...

Éditer: Notez que c'est maintenant beaucoup plus simple (AutoMapper 5.0+), vous pouvez faire comme j'ai répondu dans cet article: Comment faire pour qu'AutoMapper tronque les chaînes en fonction de l'attribut MaxLength?

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

Cela devrait être assez simple en utilisant une implémentation de IValueResolver et de la méthode ResolveUsing (). Vous avez simplement besoin d'un constructeur pour le résolveur qui prend les informations de propriété (ou si vous voulez être sophistiqué, qui prend une expression lambda et résout les informations de propriété comme Comment obtenir le PropertyInfo d'une propriété spécifique? Bien que je n’ai pas testé moi-même, j’imagine que ce qui suit pourrait fonctionner:

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

Ensuite, pour configurer ce mappage, vous devez faire:

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

Bien sûr, c’est une très grosse lamda et je vous conseillerais de la nettoyer en laissant votre résolveur de propriété déterminer la propriété de l’objet source qu’il devrait examiner en fonction de l’attribut/propriété de ResolveUsing () mais espérons que cela explique suffisamment pour vous mettre sur la bonne voie.

0
Daniel King

Supposons que j'ai les cours suivants

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

Vous pouvez passer un lambda pour résoudre:

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


 ));
0
Naimish Mungara