19

I am using AutoMapper to map between DTO objects and my business objects. I've two AutoMapperConfiguration.cs files - one in my service layer and another one in my web api layer.

As shown in the answer at the following link Where to place AutoMapper.CreateMaps?

I am calling the Configure() of both these files in my Global.asax class

AutoMapperWebConfiguration.Configure();
AutoMapperServiceConfiguration.Configure();

but it seems like the my Service Configure call (the second call) is overwriting the mappings of the web api layer (the first call) and I get an exception saying the Mapping is missing.

If I reverse the Configure calls to look like this

AutoMapperServiceConfiguration.Configure();
AutoMapperWebConfiguration.Configure();

I don't get the exception for web api mapping but I get the same mapping exception for the Service layer.

Am I doing something wrong because this is clearly marked as an answer in the above stack overflow link?

Here's my code:

public static class AutoMapperServiceConfiguration
{
    public static void Configure()
    {
        Mapper.Initialize(x =>
        {
            x.AddProfile<CmciFlowTestToGenericFlowTestSimpleMappingProfile>();
            x.AddProfile<FsrsFlowTestToGenericFlowTestSimpleMappingProfile>();
        });
    }
}

public class FsrsFlowTestToGenericFlowTestSimpleMappingProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<FsrsFlowTest, GenericFlowTest>()
            .ConvertUsing<FsrsFlowTestToGenericFlowTestSimpleConverter>();
    }
}

public class FsrsFlowTestToGenericFlowTestSimpleConverter : TypeConverter<FsrsFlowTest, GenericFlowTest>
{
    protected override GenericFlowTest ConvertCore(FsrsFlowTest source)
    {
        if (source == null)
        {
            return null;
        }

        return new GenericFlowTest
            {
                FlowTestDate = source.FlowTestDates,
                StaticPsi = source.HydrantStaticPsi.ToString(),
                ResidualPsi = source.HydrantResidualPsi.ToString(),
                TotalFlow = source.NffGallonsPerMinute.ToString(),
                FlowTestLocation = source.FsrsFlowTestLocations.Any()
                          ? source.FsrsFlowTestLocations.First().LocationDescription
                          : null
            };
    }

public class CmciFlowTestToGenericFlowTestSimpleMappingProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<CmciFlowTest, GenericFlowTest>()
            .ConvertUsing<CmciFlowTestToGenericFlowTestSimpleConverter>();
    }
}

public class CmciFlowTestToGenericFlowTestSimpleConverter : TypeConverter<CmciFlowTest, GenericFlowTest>
{
    protected override GenericFlowTest ConvertCore(CmciFlowTest source)
    {
        if (source == null)
        {
            return null;
        }

        return new GenericFlowTest
            {
                FlowTestDate = source.FlowTestDates,
                StaticPsi = source.HydrantStaticPsi.ToString(),
                ResidualPsi = source.HydrantResidualPsi.ToString(),
                TotalFlow = source.CalculatedHydrantGallonsPerMinute.ToString(),
                FlowTestLocation = source.StaticLocationHydrantFlowPSI
            };
    }
}    

public static class AutoMapperWebConfiguration
{
    public static void Configure()
    {
        Mapper.Initialize(x =>
            {
                x.AddProfile<ServiceToWebApiMappingProfile>();
                x.AddProfile<WebApiToServiceMappingProfile>();
            });
    }
}

public class ServiceToWebApiMappingProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<ServiceFlowTest, FlowTest>();
    }
}

public class WebApiToServiceMappingProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<PropertyAddress, ServicePropertyAddress>();
    }
}

To get around this issue, I am adding the service profiles in the AutoMapperWebConfiguration class and only calling AutoMapperWebConfiguration.Configure() in global.asax.

Community
  • 1
  • 1
manu79
  • 303
  • 1
  • 3
  • 12

3 Answers3

29

The calls to Mapper.Initialize reset the mapper so everything that's gone before is wiped.

Move the calls to AddProfile into one Mapper.Initialize call and all should be well:

Mapper.Initialize(x =>
{
    x.AddProfile<CmciFlowTestToGenericFlowTestSimpleMappingProfile>();
    x.AddProfile<FsrsFlowTestToGenericFlowTestSimpleMappingProfile>();
    x.AddProfile<ServiceToWebApiMappingProfile>();
    x.AddProfile<WebApiToServiceMappingProfile>();
});
Gruff Bunny
  • 27,738
  • 10
  • 72
  • 59
  • That's exactly what I am doing right now. I am adding profiles from my service layer in my AutoMapperWebConfiguration class. I was confused due to the post http://stackoverflow.com/questions/6825244/where-to-place-automapper-createmaps which shows multiple Configure() calls in the global.asax. – manu79 Dec 26 '13 at 16:52
11

@GruffBunny's answer is correct, but I tried to make it a bit neater for scalability (e.g. if you have many, complex Mapper.Initialize() methods, and might add more in the future).

I did this by implementing the following structure in all of my AutoMapperConfiguration.cs files:

Extract the Action<IConfiguration> from your existing Mapper.Initialize() method into a public property

I call it ConfigAction in each one.

public static Action<IConfiguration> ConfigAction = new Action<IConfiguration>(x =>
        {
            x.AddProfile<SomeProfile>();            
            x.AddProfile<SomeOtherProfileProfile>();
            //... more profiles
        });

This allows you to invoke the action from anywhere you need to call Mapper.Initialize.

Mapper.Initialize() inside Configure() now just references this property

public static void Configure()
{
    Mapper.Initialize(ConfigAction);
}

You can then invoke all your different ConfigActions in your single, centralized call to Mapper.Initialize()

AutoMapperConfiguration.Configure() in Application_Start() becomes

Mapper.Initialize(x =>
            {
                Project1.AutoMapperConfiguration.ConfigAction.Invoke(x);
                Project2.AutoMapperConfiguration.ConfigAction.Invoke(x);
                Project3.AutoMapperConfiguration.ConfigAction.Invoke(x);
                //... keep adding as your project grows
            });

This eliminates the need to copy-and-paste the method body from each separate Mapper.Initialize() call into your central call. DRY and all that.

Community
  • 1
  • 1
theyetiman
  • 8,514
  • 2
  • 32
  • 41
6

To update @theyetiman's brilliant answer for AutoMapper 5.2 & .NET Core.

public static class AutoMapperConfiguration
{
    public static void Configure()
    {
        Mapper.Initialize(ConfigAction);
    }

    public static Action<IMapperConfigurationExpression> ConfigAction = cfg =>
    {
        cfg.AddProfile<SomeProfile>();            
        cfg.AddProfile<SomeOtherProfileProfile>();
    };
}

API or web startup.

public Startup(IHostingEnvironment env)
{
    Mapper.Initialize(x =>
    {
        Project1.AutoMapperConfiguration.ConfigAction.Invoke(x);
        Project2.AutoMapperConfiguration.ConfigAction.Invoke(x);
        Project3.AutoMapperConfiguration.ConfigAction.Invoke(x);
    });
}
Ziaullah Khan
  • 2,020
  • 17
  • 20
wonea
  • 4,783
  • 17
  • 86
  • 139