简体   繁体   中英

Automapper - How to get containing class type when mapping a member?

During a runtime mapping operation (like when you use ResolveUsing or a custom TypeConverter) is it possible to get the container classes (or types at least) of the source and destination members?

I know that when you map one object to another, that the objects don't have to be members of some "parent" or "container" object, but I'm talking about the situation when AutoMapper is recursively copying a complex object.

Here's an example:

Here I'm copying (or setting it up at least) Cars & Boats of "kind A" to "kind B".

public class VehicleCopyProfile : AutoMapper.Profile
{
    public VehicleCopyProfile()
    {
        this.CreateMap<CarA, CarB>();
        this.CreateMap<BoatA, BoatB>();

        this.CreateMap<WindshieldA, WindshieldB>(
            .ConvertUsing((s, d, resContext) =>
            {
                // *** How can I tell if s is coming from a Car or a Boat? ***
            });

    }
}

// Cars & Boats each have a Windshield

public class CarA
{
    public WindshieldA Windshield {get;set;}
}

public class BoatA
{
    public WindshieldA Windshield {get;set;}
}

public class WindshieldA
{
    public string Name {get;set;}
}





public class CarB
{
    public WindshieldB Windshield {get;set;}
}

public class BoatB
{
    public WindshieldB Windshield {get;set;}
}


public class WindshieldB
{
    public string Name {get;set;}
}

Here is a solution using AutoMapper ResolutionContext Items as proposed by @Lucian Bargaoanu in comment. The idea is to use Before and After Map to store information in the Resolution Context. I use a Stack to keep track of the whole chain of relationship.

namespace SO51101306
{
    public static class IMappingExpressionExtensions
    {
        public static IMappingExpression<A, B> RegisterChainOfTypes<A, B>(this IMappingExpression<A, B> mapping)
        {
            mapping.BeforeMap((a, b, ctx) => {
                ctx.PushTypeInChainOfTypes(typeof(A));
            });

            mapping.AfterMap((a, b, ctx) => {
                ctx.PopLastTypeInChainOfTypes();
            });
            return mapping;
        }
    }

    public static class ResolutionContextExtensions
    {
        const string chainOfTypesKey = "ChainOfTypes";

        private static Stack<Type> GetOrCreateChainOfTypesStack(ResolutionContext ctx)
        {
            var hasKey = ctx.Items.ContainsKey(chainOfTypesKey);
            return hasKey ? (Stack<Type>)ctx.Items[chainOfTypesKey] : new Stack<Type>();
        }

        public static void PushTypeInChainOfTypes(this ResolutionContext ctx, Type type)
        {
            var stack = GetOrCreateChainOfTypesStack(ctx);
            stack.Push(type);
            ctx.Items[chainOfTypesKey] = stack;
        }

        public static Type PopLastTypeInChainOfTypes(this ResolutionContext ctx)
        {
            var stack = (Stack<Type>)ctx.Items[chainOfTypesKey];
            return stack.Pop();
        }

        public static bool HasParentType(this ResolutionContext ctx, Type parentType)
        {
            var stack = GetOrCreateChainOfTypesStack(ctx);
            return stack.Contains(parentType);
        }

    }

    public class CarCopyProfile : Profile
    {
        public CarCopyProfile()
        {
            CreateMap<CarA, CarB>().RegisterChainOfTypes();
            CreateMap<BoatA, BoatB>().RegisterChainOfTypes();

            CreateMap<WindshieldA, WindshieldB>()
            .ConvertUsing((wa,wb,ctx)=> {
                if(ctx.HasParentType(typeof(CarA)))
                {
                    Console.WriteLine("I'm coming from CarA");
                    //Do specific stuff here
                }
                else if (ctx.HasParentType(typeof(BoatA)))
                {
                    Console.WriteLine("I'm coming from boatA");
                    //Do specific stuff here
                }
                return wb;
            });

        }
    }

    public class CarA
    {
        public WindshieldA Windshield { get; set; }
    }

    public class BoatA
    {
        public WindshieldA Windshield { get; set; }
    }

    public class CarB
    {
        public WindshieldB Windshield { get; set; }
    }

    public class BoatB
    {
        public WindshieldB Windshield { get; set; }
    }

    public class WindshieldA
    {
        public string Name { get; set; }
    }

    public class WindshieldB
    {
        public string Name { get; set; }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Mapper.Initialize(c => c.AddProfile<CarCopyProfile>());

            var carA = new CarA{Windshield = new WindshieldA()};
            var boatA = new BoatA{Windshield = new WindshieldA()};

            var carB = Mapper.Map<CarB>(carA);
            var boatB = Mapper.Map<BoatB>(boatA);
        }
    }
}

This will output:

I'm coming from CarA
I'm coming from boatA

Another way is to use custom value resolver:

class CustomResolver<T1, T2>:IValueResolver ... { ... }

this.CreateMap<CarA, CarB>()
.ForMember(x => x.Windshield , opt => opt.ResolveUsing(new CustomResolver<CarA, CarB>()));

Then in you CustomResolver implementation:

var windshieldB = Mapper.Map<WindshieldB>(windshieldA, x => {x.Items["type1"] = typeof(T1); x.Items["type2"] = typeof(T2);});

And then:

this.CreateMap<WindshieldA, WindshieldB>(
            .ConvertUsing((s, d, resContext) =>
            {
                // resContext.Options.Items["type1"]
            });

See http://docs.automapper.org/en/stable/Custom-value-resolvers.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM