16

I have two very simple objects:

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

When mapping a Category to a CategoryDto with AutoMapper, I would like the following behavior:

The properties should be mapped as usual, except for those that have the MapTo attribute. In this case, I have to read the value of the Attribute to find the target property. The value of the source property is used to find the value to inject in the destination property (with the help of a dictionary). An example is always better that 1000 words...

Example:

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"

The Key property is mapped to the MyValueProperty (via the MapTo Attribute), and the assigned value is "MyValue", because the source property value is "MyKey" which is mapped (via dictionary) to "MyValue".

Is this possible using AutoMapper ? I need of course a solution that works on every object, not just on Category/CategoryDto.

Bidou
  • 7,378
  • 9
  • 47
  • 70
  • Why do you need the Attributes, in the first step you could set up custom mappings and map property key to value. Is that possible? – whymatter Jul 29 '15 at 18:33
  • I would like to create a generic mapper that I can use everywhere... then I could map any entity to any dto without any additional code... – Bidou Jul 29 '15 at 18:39
  • Imho you are making your entity responsible for something it should not be responsible for. The viewmodel should define where it should get the data it needs to construct itself, not the other way around. – Peter Feb 21 '17 at 12:24

3 Answers3

11

I finally (after so many hours !!!!) found a solution. I share this with the community; hopefully it will help someone else...

Edit: Note that it's now much simpler (AutoMapper 5.0+), you can do like I answered in this post: How to make AutoMapper truncate strings according to MaxLength attribute?

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);
    }
}
Bidou
  • 7,378
  • 9
  • 47
  • 70
  • 9
    Glad you found a solution. Can you expand your answer a bit to explain the approach this takes and provide a usage example? – JohnnyHK Aug 03 '15 at 17:35
0

This should be fairly straightforward using an implementation of IValueResolver and the ResolveUsing() method. You basically just need to have a constructor for the resolver that takes in the property info (or if you wanna be fancy that takes in a lambda expression and resolves the property info similar to How to get the PropertyInfo of a specific property?. Though I haven't tested it myself I imagine the following would work:

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

Then to set up that mapping you need to do:

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

Of course that is a pretty gross lamda and I would suggest that you clean it up by having your property resolver determine the property on the source object it should look at based on the attribute/property info so you can just pass a clean new PropertyBasedResolver(property) into the ResolveUsing() but hopefully this explains enough to put you on the right track.

Community
  • 1
  • 1
Daniel King
  • 224
  • 1
  • 10
  • Thank you for your answer. Actually, the `Value` property is not known at compile time. I have to discover it with the help of the `MapTo` attribute, so this solution does not work... – Bidou Jul 31 '15 at 17:48
  • I don't understand. Are you saying that the property `Value` property itself isn't known (as in you don't have a concept of the dto at all)? Is the question how to map a dynamic dto to a known model? – Daniel King Jul 31 '15 at 18:58
  • I updated the question, maybe it's a bit more clear now. In the example, `Id` is mapped to `Id` (default mapping), and `Key` should be mapped to `MyValueProperty` because it has the `MapTo` attribute that points to `MyValueProperty'. The value itself of the model is "MyKey", so the value in the Dto will be "MyValue", because it's mapped like that in the dictionary. Is it a bit more clear? – Bidou Jul 31 '15 at 21:25
  • I appreciate the complex multi-operator statement in the second code block :) – Max von Hippel Aug 07 '15 at 05:57
0

Lets assume I have the following classes

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

You can pass a lambda to ResolveUsing:

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


 ));
Naimish Mungara
  • 213
  • 1
  • 4
  • 14
  • No, you can't do that... `Value` is not known, I have to work on a generic TSource and TDestination... otherwise, I have to create a "ForMember" for all couple Dto<-->Entity (in a big project, that could be potentially many hundred !). With my solution, I just do it once... – Bidou Aug 07 '15 at 16:27