Пользовательское сопоставление с помощью 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; }
}

При сопоставлении Category с a CategoryDto с AutoMapper мне бы хотелось:

Свойства должны отображаться как обычно, за исключением тех, которые имеют атрибут MapTo. В этом случае я должен прочитать значение Атрибута, чтобы найти целевое свойство. Значение свойства source используется для нахождения значения для ввода в свойстве назначения (с помощью словаря). Пример всегда лучше, чем 1000 слов...

Пример:

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"

Свойство Key отображается в MyValueProperty (через атрибут MapTo), а назначенное значение - "MyValue", потому что значение свойства источника - "MyKey", которое отображается (через словарь), чтобы "MyValue".

Можно ли использовать AutoMapper? Мне нужно, конечно, решение, которое работает на каждом объекте, а не только на Category/CategoryDto.

Ответ 1

Я наконец (после стольких часов!!!!) нашел решение. Я разделяю это с сообществом; надеюсь, это поможет кому-то еще...

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

Ответ 2

Это должно быть достаточно простым, используя реализацию метода IValueResolver и ResolveUsing(). Вам просто нужно иметь конструктор для распознавателя, который принимает информацию о свойствах (или если вы хотите быть фантазией, которая принимает выражение лямбда и разрешает информацию о свойствах, подобную Как получить PropertyInfo определенного свойства? Хотя я сам не тестировал его, я предполагаю, что следующее будет работать:

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

Затем, чтобы настроить это сопоставление, вам нужно сделать:

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

Конечно, это довольно грубая ламда, и я бы посоветовал вам очистить ее, указав свойство вашего свойства на исходном объекте, на котором он должен смотреть, на основе информации об атрибуте/свойства, чтобы вы могли просто пройти чистую новый PropertyBasedResolver (свойство) в ResolveUsing(), но, надеюсь, это объясняет достаточно, чтобы поставить вас на правильный путь.

Ответ 3

Предположим, что у меня есть следующие классы

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

Вы можете передать лямбду в ResolveUsing:

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


 ));