AutoMapper 中用于自定义的依赖注入 类

Dependency injection in AutoMapper for custom classes

我有一个实体和 Dto 并尝试自定义映射那些 classes 以具有 EmpUrl=baseurl+EmpPath.I 我计划从配置文件中获取 baseurl 并通过 DI 进行映射但出现以下错误:

System.MissingMethodException: 无法动态创建类型 'MappingProfile' 的实例。原因:没有定义无参数构造函数。

 public MyMappingProfile(IOptions <ApplicationSettings> applicationsettings)
        {
            CreateMap<Emp, EmpDto>()

            .ForMember(e => e.EmpUrl, e => e.MapFrom(src => applicationsettings.Value.BaseDomain + src.EmpPath));
             
        }

注册依赖的class如下:

public class MyModule : Autofac.Module
    {
        private readonly IConfiguration Configuration;

        public ApplicationModule(IConfiguration configuration)
        {
            Configuration = configuration;
            
        }
        protected override void Load(ContainerBuilder builder)
        {

            // Register Automapper profiles
            var config = new MapperConfiguration(cfg => { cfg.AddMaps(typeof(MappingProfile).Assembly); });


config.AssertConfigurationIsValid();

        builder.Register(c => config)
            .AsSelf()
            .SingleInstance();

        builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
            .As<IMapper>()
            .InstancePerLifetimeScope();

    
            var settings = new ApplicationSettings();
            Configuration.Bind(settings);
            builder.RegisterInstance(Options.Create(settings));

        }
      
    }

Startup.cs:

public void ConfigureContainer(ContainerBuilder container)
        {
            container.RegisterModule(new ApplicationModule(Configuration));                
           
        }

我做错了什么?

假设 MyMappingProfile 继承自 AutoMapper.Profile class,这里有几个问题:

  1. cfg.AddMaps 方法在构造 MapperConfiguration 实例期间立即调用(根据 AutoMapper v11.0.1 source)。这是在 ApplicationModule 的应用程序启动加载期间(不是在运行时从容器延迟解析依赖项期间)。 AddMaps 调用创建了 MyMappingProfile 的新实例,甚至在 ApplicationSettings 从配置绑定并添加到 DI 容器之前执行。
  2. 可能与#1 相关,cfg.AddMaps 方法调用直接使用 .NET System.Activator.CreateInstance 来实例化映射配置文件(例如 MyMappingProfile)并且不传递任何参数,即为什么会收到“未定义无参数构造函数”错误。此流程不使用传递给 MapperConfiguration.CreateMapper 方法的 serviceCtor 参数。 (AutoMapper MapperConfigurationExpression.AddMaps 调用 AddMapsCore,每个 v11.0.1 source 调用 AddProfile(Type)

使用 AutoMapper 在运行时从 DI 容器解析 IOptions<ApplicationSettings> 的一种方法是使用传递给 MapperConfiguration.CreateMapper.

的服务工厂方法 (serviceCtor)

映射配置文件可能类似于:

public class MyMappingProfile : Profile
{
    public MyMappingProfile()
    {
        CreateMap<Emp, EmpDto>()
            .ForMember(dest => dest.EmpUrl, e => e.MapFrom((src, dest, destMember, ctx) =>
            {
                if (string.IsNullOrWhiteSpace(src.EmpPath))
                {
                    return null;
                }

                var applicationSettings = (IOptions<ApplicationSettings>)ctx.Options.ServiceCtor(typeof(IOptions<ApplicationSettings>));
                return applicationSettings.Value.BaseDomain + src.EmpPath;
            }));
    }
}

为了保持映射干净或重用逻辑,您可以将内联 MapFrom 逻辑提取到映射配置文件 class 中的本地方法中,这是一个单独的自定义 Value Resolver (or Member Value Resolver) used with opt.MapFrom, or a separate custom Value Converter 用于opt.ConvertUsing。自定义转换器或解析器可以使用通过构造函数注入的依赖项,而不是显式使用 ResolutionContext

如果您只尝试更改映射配置文件,那么 Autofac 将抛出一个 ObjectDisposedException 并包含来自此行的 This resolve operation has already ended. When registering components using lambdas, the IComponentContext 'c' parameter to the lambda cannot be stored. Instead, either resolve IComponentContext again from 'c', or resolve a Func<> based factory to create subsequent components from. 之类的消息:

var applicationSettings = (IOptions<ApplicationSettings>)ctx.Options.ServiceCtor(typeof(IOptions<ApplicationSettings>));

解决办法是把你的ApplicationModule.Load

builder.Register(c => c.Resolve<MapperConfiguration>()
    .CreateMapper(c.Resolve))
    .As<IMapper>()
    .InstancePerLifetimeScope();

builder.Register(c =>
    {
        var ctx = c.Resolve<IComponentContext>();
        var mapperConfig = c.Resolve<MapperConfiguration>();
        return mapperConfig.CreateMapper(ctx.Resolve);
    })
    .As<IMapper>()
    .InstancePerLifetimeScope();

这是基于个人经验,也包含在其他 SO 问题中,例如 This resolve operation has already ended. Autofac, Automapper & IMemberValueResolver. Dennis Doomen gives a good explanation of this Autofac design nuance in The curious case of a deadlock in Autofac

So to summarize, use the temporary container for resolving dependencies needed at construction time, but use the global container to resolve any run-time dependencies.

另一种软件设计替代方案是根本不解析 AutoMapper 配置文件内的 DI 容器中的 ApplicationSettings。相反,调用 IMapper.Map 的 class 可以通过构造函数注入注入 IOptions<ApplicationSettings>,然后就地转换 EmpDto.EmpUrl 或添加单独的 属性,如 EmpDto.EmpFullUrl 将基本域前缀添加到 EmpDto.EmpPath 属性 通过 AutoMapper 从 Emp.EmpPath 逐字映射。