Как использовать общий профиль с Automapper и Asp.Net Core Dependency Injection

Я хотел бы создать библиотеку классов .Net Core, которая будет содержать следующий метод расширения:

public static class MyServiceExtensions
    {
        public static IServiceCollection AddMyService<TUserDto, TUserDtoKey, TUser, TUserKey>(this IServiceCollection services)
            where TUserDto : UserDto<TUserDtoKey>
            where TUser : User<TUserKey>
        {
            services.AddAutoMapper(config =>
            {
                config.AddProfile<UserMappingProfile<TUserDto, TUserDtoKey, TUser, TUserKey>>();
            });

            return services;
        }
    }

У меня есть следующий профиль Automapper:

public class UserMappingProfile<TUserDto, TUserDtoKey, TUser, TUserKey> : Profile 
        where TUserDto : UserDto<TUserDtoKey>
        where TUser : User<TUserKey>
    {
        public UserMappingProfile()
        {
            CreateMap<TUserDto, TUser>(MemberList.Destination)
                .ForMember(x => x.Id, opts => opts.MapFrom(x => x.UserId));

            CreateMap<TUser, TUserDto > (MemberList.Source)
                .ForMember(x => x.UserId, opts => opts.MapFrom(x => x.Id));
        }
    }

Эти организации:

public class UserDto<TKey>
    {
        public TKey UserId { get; set; }

        public string UserName { get; set; }
    }

public class User<TKey>
    {
        public TKey Id { get; set; }

        public string UserName { get; set; }
    }

public class MyUser : User<int>
    {
        public string Email { get; set; }
    }

public class MyUserDto : UserDto<int>
    {
        public string Email { get; set; }
    }

Если я попытаюсь использовать это так:

services.AddMyService<MyUserDto, int, MyUser, int>();

Я получаю такую ​​ошибку:

{System.ArgumentException: Cannot create an instance of GenericMapping.Services.Mapping.UserMappingProfile4[TUserDto,TUserDtoKey,TUser,TUserKey] because Type.ContainsGenericParameters is true. at System.RuntimeType.CreateInstanceCheckThis() at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean wrapExceptions, Boolean skipCheckThis, Boolean fillCache) at System.Activator.CreateInstance(Type type, Boolean nonPublic, Boolean wrapExceptions) at AutoMapper.Configuration.MapperConfigurationExpression.AddProfile(Type profileType) in C:\projects\automapper\src\AutoMapper\Configuration\MapperConfigurationExpression.cs:line 44 at AutoMapper.ServiceCollectionExtensions.<>c__DisplayClass10_0.<AddAutoMapperClasses>g__ConfigAction|4(IMapperConfigurationExpression cfg) in C:\projects\automapper-extensions-microsoft-dependencyinjectio\src\AutoMapper.Extensions.Microsoft.DependencyInjection\ServiceCollectionExtensions.cs:line 83 at AutoMapper.MapperConfiguration.Build(Action1 configure) in C:\projects\automapper\src\AutoMapper\MapperConfiguration.cs:line 307 at AutoMapper.ServiceCollectionExtensions.AddAutoMapperClasses(IServiceCollection services, Action1 additionalInitAction, IEnumerable1 assembliesToScan) in C:\projects\automapper-extensions-microsoft-dependencyinjectio\src\AutoMapper.Extensions.Microsoft.DependencyInjection\ServiceCollectionExtensions.cs:line 89 at GenericMapping.Services.Extensions.MyServiceExtensions.AddMyService[TUserDto,TUserDtoKey,TUser,TUserKey](IServiceCollection services) in C:\Projects\GenericMapping\GenericMapping.Services\Extensions\MyServiceExtensions.cs:line 14 at GenericMapping.Startup.ConfigureServices(IServiceCollection services) in C:\Projects\GenericMapping\GenericMapping\Startup.cs:line 33}

Как я могу исправить эту проблему?

AM поддерживает SourceLink, поэтому вам должно быть легко просмотреть код, чтобы понять, что происходит.

Lucian Bargaoanu 21.08.2018 07:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
1
2 400
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Основная причина вашей проблемы - неправильное использование метода расширения AddAutoMapper. Этот метод сканирует сборки на предмет профилей (и других компонентов AutoMapper) и регистрирует компонент IMapper в контейнере DI, используя найденную конфигурацию. (Я предлагаю вам взглянуть на его источники, чтобы понять, что именно происходит под капотом.)

Вы получаете исключение, потому что AddAutoMapper находит класс UserMappingProfile, но не знает, как его создать, поскольку он имеет 4 аргумента открытого типа.

Самый простой способ решить эту проблему - сделать общий класс профиля абстрактным и создать подкласс с аргументами желаемого типа:

public abstract class UserMappingProfile<TUserDto, TUserDtoKey, TUser, TUserKey> : Profile
    where TUserDto : UserDto<TUserDtoKey>
    where TUser : User<TUserKey>
{
    public UserMappingProfile()
    {
        CreateMap<TUserDto, TUser>(MemberList.Destination)
            .ForMember(x => x.Id, opts => opts.MapFrom(x => x.UserId));

        CreateMap<TUser, TUserDto>(MemberList.Source)
            .ForMember(x => x.UserId, opts => opts.MapFrom(x => x.Id));
    }
}

public class UserMappingProfile : UserMappingProfile<MyUserDto, int, MyUser, int> { }

Теперь вам вообще не нужен MyServiceExtensions, просто вызовите services.AddAutoMapper(), и ваша конфигурация будет подобрана автоматически.

Однако, если вы настаиваете на выполнении настройки с использованием собственных методов расширения, вам следует избегать AddAutoMapper, поскольку он предназначен для однократного вызова. Вместо сканирования сборок на предмет классов Профиль вы можете предоставить свою собственную логику регистрации. Пример использования паттерна построителя:

public class UserMappingProfile<TUserDto, TUserDtoKey, TUser, TUserKey> : Profile
    where TUserDto : UserDto<TUserDtoKey>
    where TUser : User<TUserKey>
{
    public UserMappingProfile()
    {
        CreateMap<TUserDto, TUser>(MemberList.Destination)
            .ForMember(x => x.Id, opts => opts.MapFrom(x => x.UserId));

        CreateMap<TUser, TUserDto>(MemberList.Source)
            .ForMember(x => x.UserId, opts => opts.MapFrom(x => x.Id));
    }
}

public interface IMapperConfigurationBuilder
{
    IMapperConfigurationBuilder UseProfile<TUserDto, TUserDtoKey, TUser, TUserKey>()
        where TUserDto : UserDto<TUserDtoKey>
        where TUser : User<TUserKey>;
}

public static class MyServiceExtensions
{
    private class MapperConfigurationBuilder : IMapperConfigurationBuilder
    {
        public HashSet<Type> ProfileTypes { get; } = new HashSet<Type>();

        public IMapperConfigurationBuilder UseProfile<TUserDto, TUserDtoKey, TUser, TUserKey>()
            where TUserDto : UserDto<TUserDtoKey>
            where TUser : User<TUserKey>
        {
            ProfileTypes.Add(typeof(UserMappingProfile<TUserDto, TUserDtoKey, TUser, TUserKey>));
            return this;
        }
    }

    public static IMapperConfigurationBuilder AddMyMapper(this IServiceCollection services)
    {
        var builder = new MapperConfigurationBuilder();

        services.AddSingleton<IConfigurationProvider>(sp => new MapperConfiguration(cfg =>
        {
            foreach (var profileType in builder.ProfileTypes)
                cfg.AddProfile(profileType);
        }));

        services.AddScoped<IMapper>(sp => new Mapper(sp.GetRequiredService<IConfigurationProvider>(), sp.GetService));
        return builder;
    }
}

Тогда регистрация картографического профиля будет выглядеть так:

services.AddMyMapper()
    .UseProfile<MyUserDto, int, MyUser, int>();

Другие вопросы по теме