Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple AutoMapper.Configure() in Global.asax

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.

like image 828
manu79 Avatar asked Dec 06 '13 18:12

manu79


People also ask

How do I test AutoMapper configuration?

To test our configuration, we simply create a unit test that sets up the configuration and executes the AssertConfigurationIsValid method: var configuration = new MapperConfiguration(cfg => cfg. CreateMap<Source, Destination>()); configuration. AssertConfigurationIsValid();

What is the use of AutoMapper in C#?

AutoMapper in C# is a library used to map data from one object to another. It acts as a mapper between two objects and transforms one object type into another. It converts the input object of one type to the output object of another type until the latter type follows or maintains the conventions of AutoMapper.

What is AutoMapper good for?

AutoMapper is a simple library that helps us to transform one object type into another. It is a convention-based object-to-object mapper that requires very little configuration. The object-to-object mapping works by transforming an input object of one type into an output object of a different type.

What is an AutoMapper profile?

automapper Profiles Basic Profile Profiles permit the programmer to organize maps into classes, enhancing code readability and maintainability. Any number of profiles can be created, and added to one or more configurations as needed. Profiles can be used with both the static and instance-based APIs.


3 Answers

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>();
});
like image 198
Gruff Bunny Avatar answered Oct 21 '22 23:10

Gruff Bunny


@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.

like image 35
theyetiman Avatar answered Oct 21 '22 23:10

theyetiman


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);
    });
}
like image 39
wonea Avatar answered Oct 21 '22 23:10

wonea