Несколько AutoMapper.Configure() в Global.asax

Я использую AutoMapper для сопоставления между объектами DTO и моими бизнес-объектами. У меня два файла AutoMapperConfiguration.cs - один в моем слое сервиса и еще один в моем веб-слое api.

Как показано в ответе по следующей ссылке Где разместить AutoMapper.CreateMaps?

Я вызываю Configure() обоих этих файлов в классе Global.asax

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

но похоже, что мой вызов службы настройки (второй вызов) переписывает сопоставления уровня веб-api (первый вызов), и я получаю исключение, говорящее, что отображение отсутствует.

Если я отменил вызов Configure, чтобы выглядеть так:

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

Я не получаю исключение для сопоставления веб-api, но я получаю ту же ошибку отображения для уровня сервиса.

Я делаю что-то неправильно, потому что это четко обозначено как ответ в вышеупомянутой ссылке?

Здесь мой код:

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>();
    }
}

Чтобы обойти эту проблему, я добавляю профили службы в классе AutoMapperWebConfiguration и вызываю только AutoMapperWebConfiguration.Configure() в global.asax.

Ответ 1

Вызов Mapper.Initialize reset сопоставителя, поэтому все, что было раньше, стирается.

Переместите вызовы в AddProfile в один Mapper.Initialize, и все должно быть хорошо:

Mapper.Initialize(x =>
{
    x.AddProfile<CmciFlowTestToGenericFlowTestSimpleMappingProfile>();
    x.AddProfile<FsrsFlowTestToGenericFlowTestSimpleMappingProfile>();
    x.AddProfile<ServiceToWebApiMappingProfile>();
    x.AddProfile<WebApiToServiceMappingProfile>();
});

Ответ 2

Ответ на

@GruffBunny правильный, но я попытался сделать его немного более аккуратным для масштабируемости (например, если у вас много методов сложного Mapper.Initialize() и может добавить в будущем больше).

Я сделал это, выполнив следующую структуру в всех моих AutoMapperConfiguration.cs файлах:

Извлеките Action<IConfiguration> из существующего метода Mapper.Initialize() в общедоступное свойство

Я называю это ConfigAction в каждом из них.

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

Это позволяет вам вызывать действие из любого места, где вам нужно позвонить Mapper.Initialize.

Mapper.Initialize() внутри Configure() теперь просто ссылается на это свойство

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

Затем вы можете вызывать все ваши разные ConfigAction в одном, централизованном вызове Mapper.Initialize()

AutoMapperConfiguration.Configure() в Application_Start() становится

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

Это устраняет необходимость копирования и вставки тела метода из каждого отдельного вызова Mapper.Initialize() в ваш центральный вызов. DRY и все такое.

Ответ 3

Обновление @theyetiman блестящего ответа для AutoMapper 5.2 и .NET Core.

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

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

API или веб-запуск.

public Startup(IHostingEnvironment env)
{
    Mapper.Initialize(x =>
    {
        Project1.AutoMapperConfiguration.ConfigAction.Invoke(x);
        Project2.AutoMapperConfiguration.ConfigAction.Invoke(x);
        Project3.AutoMapperConfiguration.ConfigAction.Invoke(x);
    });
}