Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper mapping unmapped properties to Dictionary / ExtensionData

How can I make AutoMapper to map missing unmapped properties to a dictionary inside the destination object? (Like ExtensionData during serialization)

Example:

class Source
{
    public int A {get;set;} 
    public int B {get;set;} 
    public int C {get;set;} 
}

class Destination
{
    public int A {get;set;}
    public Dictionary<string, object> D {get;set;}
}

Source s = new Source { A = 1, B = 2, C = 3 };
Destination d = ... // Mapping code

Now I want the following result:

d.A ==> 1
d.D ==> {{ "B", 2 }, { "C", 3 }}

* EDIT *

In the end I am looking for a solution w/o reflection. Meaning: During setup/configuration/initialization reflection is allowed, but during the mapping itself, I do not want any delays caused by reflection.

* EDIT *

I am looking for a generic solution, just like the serializers.

like image 572
Martin Mulder Avatar asked Apr 04 '17 08:04

Martin Mulder


People also ask

Does AutoMapper map private properties?

AutoMapper will map property with private setter with no problem. If you want to force encapsulation, you need to use IgnoreAllPropertiesWithAnInaccessibleSetter. With this option, all private properties (and other inaccessible) will be ignored.

Does AutoMapper map both ways?

Yes, or you can call CreateMap<ModelClass, ViewModelClass>(). ReverseMap() .


2 Answers

There are a lot of possible solutions for your problem. I've create a custom value resolver for your property and it works perfectly:

public class CustomResolver : IValueResolver<Source, Destination, Dictionary<string, object>>
{
    public Dictionary<string, object> Resolve(Source source, Destination destination, Dictionary<string, object> destMember, ResolutionContext context)
    {
        destMember = new Dictionary<string, object>();

        var flags = BindingFlags.Public | BindingFlags.Instance;
        var sourceProperties = typeof(Source).GetProperties(flags);

        foreach (var property in sourceProperties)
        {
            if (typeof(Destination).GetProperty(property.Name, flags) == null)
            {
                destMember.Add(property.Name, property.GetValue(source));
            }
        }

        return destMember;
    }
}

How to use it?

static void Main(string[] args)
{
    Mapper.Initialize(cfg => {
        cfg.CreateMap<Source, Destination>()
            .ForMember(dest => dest.D, opt => opt.ResolveUsing<CustomResolver>());
    });

    var source = new Source { A = 1, B = 2, C = 3 };

    var result = Mapper.Map<Source, Destination>(source);
}

public class Source
{
    public int A { get; set; }
    public int B { get; set; }
    public int C { get; set; }
}

public class Destination
{
    public int A { get; set; }
    public Dictionary<string, object> D { get; set; }
}
like image 96
Pawel Maga Avatar answered Sep 18 '22 16:09

Pawel Maga


I like Pawel's solution because is more generic. If you want something simpler but less generic you could initialize the mapper like this:

    Mapper.Initialize(cfg => {
                          cfg.CreateMap<Source, Destination>()
                              .ForMember(dest => dest.D, 
                                         opt => opt.MapFrom(r => new Dictionary<string,object>(){{ "B", r.B},{ "C", r.C}}));
    });
like image 44
Cristian Avatar answered Sep 20 '22 16:09

Cristian