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

时间:2018-08-20 09:17:52

标签: c# asp.net-core .net-core automapper

我想创建.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;
        }
    }

我有以下自动映射器配置文件:

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:无法创建的实例   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(Action 1配置)中   C:\ projects \ automapper \ src \ AutoMapper \ MapperConfiguration.cs:第307行   在   AutoMapper.ServiceCollectionExtensions.AddAutoMapperClasses(IServiceCollection   服务,动作1 additionalInitAction, IEnumerable 1   AssemblyToScan)中   C:\ projects \ automapper-extensions-microsoft-dependencyinjectio \ src \ AutoMapper.Extensions.Microsoft.DependencyInjection \ ServiceCollectionExtensions.cs:line   89分   GenericMapping.Services.Extensions.MyServiceExtensions.AddMyService [TUserDto,TUserDtoKey,TUser,TUserKey](IServiceCollection   服务)中   C:\ Projects \ GenericMapping \ GenericMapping.Services \ Extensions \ MyServiceExtensions.cs:line   14在GenericMapping.Startup.ConfigureServices(IServiceCollection   服务)在C:\ Projects \ GenericMapping \ GenericMapping \ Startup.cs:line中   33}

如何解决此问题?

1 个答案:

答案 0 :(得分:3)

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

之所以会出现异常,是因为 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 ,因为它只能被调用一次。您可以提供自己的注册逻辑,而不必扫描 Profile 类的程序集。使用构建器模式的示例:

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