简体   繁体   English

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

[英]How to use generic Profile with Automapper and Asp.Net Core Dependency Injection

I would like to create the .Net Core class library that will be contains following extension method: 我想创建.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;
        }
    }

I have following Automapper 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));
        }
    }

These entities: 这些实体:

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

If I try to use it like this: 如果我尝试这样使用它:

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

I get this error: 我收到此错误:

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

How can I fix this issue? 如何解决此问题?

The root cause of your issue is the incorrect usage of the AddAutoMapper extension method. 问题的根本原因是AddAutoMapper扩展方法的使用不正确。 This method scans assemblies for profiles (and other AutoMapper components) and registers an IMapper component in the DI container using the configuration found. 此方法扫描程序集以查找配置文件(和其他AutoMapper组件),并使用找到的配置在DI容器中注册IMapper组件。 (I suggest you taking a look at its sources to understand what's going on exactly under the hood.) (我建议您查看其来源,以了解到底是怎么回事。)

You get the exception because AddAutoMapper finds the UserMappingProfile class but has no clue how to instantiate it as it has 4 open type arguments. 之所以会出现异常,是因为AddAutoMapper找到了UserMappingProfile类,但是不知道如何实例化它,因为它有4个开放类型参数。

The easiest way of resolving the issue is to make your generic profile class abstract and to subclass it with the desired type arguments: 解决此问题的最简单方法是使通用概要文件类抽象化,并使用所需的类型参数将其子类化:

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> { }

Now you don't need MyServiceExtensions at all, just a services.AddAutoMapper() call and your configuration will be picked up automatically. 现在,您根本不需要MyServiceExtensions ,只需调用services.AddAutoMapper() ,您的配置就会自动被提取。

However, if you insist on doing the configuration using your own extension methods, you have to avoid AddAutoMapper as it's intended to be called only once. 但是,如果您坚持使用自己的扩展方法进行配置,则必须避免AddAutoMapper,因为它只能被调用一次。 Instead of scanning assemblies for Profile classes, you can provide your own registration logic. 您可以提供自己的注册逻辑,而不是为Profile类扫描程序集。 An example using the builder pattern: 使用构建器模式的示例:

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

Then the mapping profile registration would look like this: 然后,映射配置文件注册将如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM