Я хотел бы создать библиотеку классов .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.UserMappingProfile
4[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}
Как я могу исправить эту проблему?





Основная причина вашей проблемы - неправильное использование метода расширения 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>();
AM поддерживает SourceLink, поэтому вам должно быть легко просмотреть код, чтобы понять, что происходит.