53

I am working with AutoMapper and some of the values for the entity being mapped to are variables in my current method. I have tried to Google it but to no avail. Can I pass a set of KeyValue Pairs or an object or something to my mapping to have it use those values?

Sample of Post Mapping Modification

//comment variable is a Comment class instance
var imageComment = AutoMapper.Mapper.Map<Data.ImageComment>(comment);
//I want to pass in imageId so I dont have to manually add it after the mapping
imageComment.ImageId = imageId;
CWitty
  • 4,488
  • 3
  • 23
  • 40

5 Answers5

56

AutoMapper handles this key-value pair scenario out of the box.

Mapper.CreateMap<Source, Dest>()
    .ForMember(d => d.Foo, opt => opt.ResolveUsing(res => res.Context.Options.Items["Foo"]));

Then at runtime:

Mapper.Map<Source, Dest>(src, opt => opt.Items["Foo"] = "Bar");

A bit verbose to dig into the context items but there you go.

Jimmy Bogard
  • 26,045
  • 5
  • 74
  • 69
  • 1
    Is there any ways to use the ResolveUsing with the ConstructUsing method? – David Létourneau Aug 10 '16 at 17:37
  • 1
    Is it possible the `ResolveUsing` method no longer accepts a lambda with just the context as argument or am I missing an extension function? I had to define a lambda with `(input, output, memberValue, context)` as arguments before I could get it to work with Automapper 5.2. Of course, it could still be the compiler which can no longer figure out which overload it needs to use... – JBert Feb 07 '17 at 12:37
  • 5.x added overloads, and those overloads were adjusted so that you wouldn't get weird compile-time errors. – Jimmy Bogard Feb 08 '17 at 16:19
  • 9
    I get an error with res.Context - the source model doesn't contain a definition for context. Using .net core 2.0 and AutoMapper 6.2.1 – egmfrs Mar 12 '18 at 12:21
48

AutoMapper 9.0.0

As of version 8.0.0 the API of AutoMapper has been changed. In doing so ResolveUsing was consolidated with MapFrom. Take a look at the corresponding pull request for further information.

Profile

public class CoreProfile : Profile
{
    public CoreProfile()
    {
        CreateMap<Source, Destination>()
            .ForMember(d => d.Bar,
                opt => opt.MapFrom(
                    (src, dst, _, context) => context.Options.Items["bar"]
                )
            );    
    }
}

Mapping

var destination = mapper.Map<Destination>(
    source,opt => {
        opt.Items["bar"] = "baz";
    }
);
MUG4N
  • 19,377
  • 11
  • 56
  • 83
  • 2
    It'd be nice if you write a few words about what's changed compared to the previous answer (Automapper 6.0.2). – Razor23 Donetsk Jul 03 '20 at 12:54
  • 1
    @Razor23Donetsk, thanks for the hint. I have added the missing description of the changes between the versions. – MUG4N Jul 11 '20 at 09:44
  • @Razor23Donetsk The missing description is the `MapFrom()` operation (and several others) got new overloads that expose the resolution context to the mapping expression. – Suncat2000 Oct 08 '21 at 15:48
  • 2
    In Automapper 12.0.0 `context` doesn't have `Options` property. Instead `context.Items` is used. – stanimirsp Dec 21 '22 at 09:56
40

For Automapper 6.0.2:

Profile:

public class CoreProfile : Profile
{
    public CoreProfile()
    {
        CreateMap<Source, Dest>()
            .ForMember(d => d.Foo,
                opt => opt.ResolveUsing(
                    (src, dst, arg3, context) => context.Options.Items["Foo"]
                )
            );
    }
}

Mapping:

    var result = Mapper.Map<PlanResult>(aa, opt => {
        opt.Items["Foo"] = 2;
        opt.Items["OtherFoo"] = 1000;
    });
Richard Garside
  • 87,839
  • 11
  • 80
  • 93
Leszek P
  • 1,807
  • 17
  • 24
6

Objects can be passed to the resolver with the Items Dictionary option. The standard API to do this is pretty verbose (as seen in the accepted answer) but can be simplified nicely using a few extension methods:

/// <summary>
/// Map using a resolve function that is passed the Items dictionary from mapping context
/// </summary>
public static void ResolveWithContext<TSource, TDest, TMember, TResult>(
    this IMemberConfigurationExpression<TSource, TDest, TMember> memberOptions, 
    Func<TSource, IDictionary<string, object>, TDest, TMember, TResult> resolver
) {
    memberOptions.ResolveUsing((src, dst, member, context) => resolver.Invoke(src, context.Items, dst, member));
}

public static TDest MapWithContext<TSource, TDest>(this IMapper mapper, TSource source, IDictionary<string, object> context, Action<IMappingOperationOptions<TSource, TDest>> optAction = null) {
    return mapper.Map<TSource, TDest>(source, opts => {
        foreach(var kv in context) opts.Items.Add(kv);
        optAction?.Invoke(opts);
    });
}

Which can be used like this:

// Define mapping configuration
Mapper.CreateMap<Comment, ImageComment>()
    .ForMember(
        d => d.ImageId,
        opt => opt.ResolveWithContext(src, items, dst, member) => items["ImageId"])
    );

// Execute mapping
var context = new Dictionary<string, object> { { "ImageId", ImageId } };
return mapper.MapWithContext<TSource, TDest>(source, context);

If you have an object that you commonly need to pass to mapper resolvers (for example, the current user), you can go one step further and define more specialized extensions:

public static readonly string USER_CONTEXT_KEY = "USER";

/// <summary>
/// Map using a resolve function that is passed a user from the
/// Items dictionary in the mapping context
/// </summary>
public static void ResolveWithUser<TSource, TDest, TMember, TResult>(
    this IMemberConfigurationExpression<TSource, TDest, TMember> memberOptions,
    Func<TSource, User, TResult> resolver
) {
    memberOptions.ResolveWithContext((src, items, dst, member) =>
        resolver.Invoke(src, items[USER_CONTEXT_KEY] as User));
}

/// <summary>
/// Execute a mapping from the source object to a new destination
/// object, with the provided user in the context.
/// </summary>
public static TDest MapForUser<TSource, TDest>(
    this IMapper mapper,
    TSource source,
    User user,
    Action<IMappingOperationOptions<TSource, TDest>> optAction = null
) {
    var context = new Dictionary<string, object> { { USER_CONTEXT_KEY, user } };
    return mapper.MapWithContext(source, context, optAction);
}

Which can be used like:

// Create mapping configuration
Mapper.CreateMap<Source, Dest>()
    .ForMember(d => d.Foo, opt => opt.ResolveWithUser((src, user) src.Foo(user));

// Execute mapping
return mapper.MapWithUser(source, user);
mark.monteiro
  • 2,609
  • 2
  • 33
  • 38
  • 3
    You may want to update your answer for 8.0 since the replaced `ResolveUsing` with `MapFrom` http://docs.automapper.org/en/latest/8.0-Upgrade-Guide.html#resolveusing – Curly Brace Feb 06 '19 at 12:29
0

Suppose you have these two objects:

public class ObjectA {
 public string Property1 { get; set; }
 public int Property2 { get; set; }
}
public class ObjectB {
 public string Property1 { get; set; }
 public int Property2 { get; set; }
}

And you want to copy an existing object of type ObjectA into a new object of type ObjectB, using AutoMapper you have to do this:

var objectA = new ObjectA { Property1 = "Hello, World!", Property2 = 1 }
var objectB = new ObjectB();

// Copy data from a to b
AutoMapper.Mapper
  .CreateMap<ObjectA, ObjectB>()
  .BeforeMap((source, dest) => { dest.ImageId = imageId });
AutoMapper.Mapper.Map(objectA, objectB); // source, destination;

// Or:
var objectB = AutoMapper.Mapper.Map<ObjectB>(objectA);
Roberto Conte Rosito
  • 2,080
  • 12
  • 22