如何在 Automapper 和 Asp.Net Core 依赖注入中使用通用配置文件

Jen*_*nan 5 c# automapper .net-core asp.net-core

我想创建将包含以下扩展方法的 .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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

我有以下 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));
        }
    }
Run Code Online (Sandbox Code Playgroud)

这些实体:

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; }
    }
Run Code Online (Sandbox Code Playgroud)

如果我尝试像这样使用它:

services.AddMyService<MyUserDto, int, MyUser, int>();
Run Code Online (Sandbox Code Playgroud)

我收到此错误:

{System.ArgumentException: 无法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(Action在 C:\projects\automapper\src\AutoMapper\MapperConfiguration.cs 中创建 GenericMapping.Services.Mapping.UserMappingProfile 1 配置的实例:AutoMapper.ServiceCollectionExtensions.AddAutoMapperClasses(IServiceCollection services, Action1 additionalInitAction, IEnumerable1 assemblyToScan) 在 C:\projects\automapper-extensions-microsoft-dependencyinjectio\src\AutoMapper.Extensions.Microsoft.DependencyInjection\ServiceCollectionExtensions.cs:line 89 at GenericMapping.Services.Extensions.MyServiceExtensions.AddMyService[TUserDto,TUser,TUserDto 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:第 33 行}

我该如何解决这个问题?

Ada*_*mon 7

您问题的根本原因是AddAutoMapper扩展方法的使用不正确。此方法扫描程序集的配置文件(和其他 AutoMapper 组件)并使用找到的配置在 DI 容器中注册IMapper组件。(我建议你查看它的来源以了解幕后到底发生了什么。)

您会收到异常,因为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> { }
Run Code Online (Sandbox Code Playgroud)

现在您根本不需要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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后映射配置文件注册将如下所示:

services.AddMyMapper()
    .UseProfile<MyUserDto, int, MyUser, int>();
Run Code Online (Sandbox Code Playgroud)