Matthias Güntert
Matthias Güntert

Reputation: 4638

How to pass arguments to a constructor of a base mapping profile?

I am currently migrating several mapping profiles from Automapper 4.2.1 to the latest version 9.0.0. The old mapping profiles are build up hierarchical, where the abstract base class requires an argument of type IDatetime. This injection is used for testing only.

public abstract MappingProfileBase : Profile 
{
    protected MappingProfileBase(IDatetime datetime)
    {
        this.CreateMap<Foo, FooDto>()
            .ForMember(dest => dest.SomeTimeStamp, opt => opt.MapFrom(src => datetime));
            // Further mappings 
    }
}

public MappingProfileA : MappingProfileBase
{
    public MappingProfileA(IDatetime datetime) : base(datetime)
    {
        this.CreateMap<FooDerived, FooDerivedDto>()        
            // Further and more specific mappings 
    }   
}

Now I would like to move to the new Include and IncludeBase<> methods and undo the inheritance of MappingProfileA and MappingProfileBase but simply don't know how to deal with the injected interface. None of the new methods is taking any arguments.

This is how I think it should look like:

public class MappingProfileBase : Profile
{
    public MappingProfileBase(IDatetime datetime)
    {
        this.CreateMap<Foo, FooDto>()
            .ForMember(dest => dest.SomeTimeStamp, opt => opt.MapFrom(src => datetime));
            // Further mappings
    }
}

public class MappingProfileA : Profile
{
    public MappingProfileA()
    {
        this.CreateMap<FooDerived, FooDerivedDto>();
            .IncludeBase<Foo, FooDto>();
    }
}

So how can I pass arguments to the constructor of the base class? What other possibilities do exist?

Upvotes: 0

Views: 2028

Answers (1)

Matthias G&#252;ntert
Matthias G&#252;ntert

Reputation: 4638

Thanks (again) to Lucian. I solved it by providing a profile instance to AddProfile (instead of a Type) using Autofac. I therefor had to register the concrete type beforehand to the container.

private static MapperConfiguration GetMappingConfiguration()
{
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterType<DateTime>().As<IDateTime>();

    var assembly = Assembly.GetExecutingAssembly();
    var loadedProfiles = assembly.ExportedTypes
        .Where(type => type.IsSubclassOf(typeof(Profile)))
        .ToArray();

    containerBuilder.RegisterTypes(loadedProfiles);

    var container = containerBuilder.Build();

    var config = new MapperConfiguration(cfg =>
    {
        cfg.ConstructServicesUsing(container.Resolve);

        foreach (var profile in loadedProfiles)
        {
            var resolvedProfile = container.Resolve(profile) as Profile;
            cfg.AddProfile(resolvedProfile);
        }
    });

    return config;
}

Here are two example profiles:

public class BaseMappingProfile : Profile
{
    public BaseMappingProfile(IDateTime datetime)
    {
        this.CreateMap<Foo, FooDto>()
            .ForMember(d => d.Timestamp, o => o.MapFrom(s => datetime))
            .ForMember(d => d.PropertyA, o => o.MapFrom(s => s.Property1));
    }
}

public class FooMappingProfile : Profile
{
    public FooMappingProfile()
    {
        this.CreateMap<FooDerived, FooDerivedDto>()
            .IncludeBase<Foo, FooDto>()
            .ForMember(d => d.PropertyB, o => o.MapFrom(s => s.Property2));
    }
}

And then from within a unit test:

[Fact]
public void Should_Map_From_Foo_To_FooDto()
{
    var config = GetMappingConfiguration();
    var mapper = config.CreateMapper();

    var foo = new Foo {Property1 = "I am property 1"};
    var fooDto = mapper.Map<FooDto>(foo);

    // Asserts...
}

Of course the static method GetMappingConfiguration requires further modification. But in principle it works.

Using IncludeBase comes with the benefit of no code duplication. But on the other hand more complex profiles and test setup especially in this case.

Upvotes: 0

Related Questions