【问题标题】:Configuring AutoMapper to fulfil ITypeConverter<,> constructor dependecies with Autofac使用 Autofac 配置 AutoMapper 以实现 ITypeConverter<,> 构造函数依赖项
【发布时间】:2016-10-27 20:22:59
【问题描述】:

我第一次使用 Autofac 将 AutoMapper 的 IMapper 接口注入到具有对象映射要求的类中。我已经取得了一些进展,with a little help,使用 Assembly Scanning 将各种依赖项添加到 AutoMapper 的寄存器中:

builder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
    .AsClosedTypesOf(typeof(ITypeConverter<,>))
    .AsImplementedInterfaces();
builder.RegisterAssemblyTypes(typeof(AutoMapperExtensions).Assembly)
    .AssignableTo<Profile>().As<Profile>();

builder.Register(context => {
    var profiles = context.Resolve<IEnumerable<Profile>>();
    return new MapperConfiguration(x => {
        foreach (var profile in profiles) x.AddProfile(profile);
    });
}).SingleInstance().AutoActivate().AsSelf();

builder.Register(context => {
    var componentContext = context.Resolve<IComponentContext>();
    var config = componentContext.Resolve<MapperConfiguration>();
    return config.CreateMapper();
}).As<IMapper>();

这对于没有任何注入依赖项的ITypeConverter&lt;,&gt; 非常有效:

public class SourceToDestinationTypeConverter : ITypeConverter<SourceModel, DestinationModel> {
    public DestinationModel Convert(SourceModel source, DestinationModel destination, ResolutionContext context) {
        if (source.Items == null) {
            return null;
        }

        return new DestinationModel {
            FirstItem = source.Items.FirstOrDefault(),
            LastItem = source.Items.LastOrDefault()
        };
    }
}

但是,从我添加依赖项的那一刻起,在这个人为的示例中,验证器:

public class SourceToDestinationTypeConverter : ITypeConverter<SourceModel, DestinationModel> {
    private readonly IValidator<SourceModel> _validator;

    public SourceToDestinationTypeConverter(IValidator<SourceModel> validator) {
        _validator = validator;
    }

    public DestinationModel Convert(SourceModel source, DestinationModel destination, ResolutionContext context) {
        if (!_validator.Validate(source)) return null;

        return new DestinationModel {
            FirstItem = source.Items.FirstOrDefault(),
            LastItem = source.Items.LastOrDefault()
        };
    }
}

抛出以下异常:

Application.TypeConverters.SourceToDestinationTypeConverter 需要有一个 0 args 或只有可选 args 的构造函数

我似乎很清楚 AutoMapper 需要被告知使用 Autofac 来满足依赖关系。但是,我不知道如何告诉它这样做。

如果需要进一步澄清错误,完整的解决方案是available on GitHub

【问题讨论】:

    标签: c# inversion-of-control automapper autofac automapper-5


    【解决方案1】:

    注意: Travis Illig 提供了hollistic answer to the question,我将其标记为答案,因为它以广泛而通用的方式回答了问题。不过,我也想记录我的问题的具体解决方案

    您需要非常小心地将依赖解析器连接到 AutoMapper,准确地说,您必须在闭包中解析组件上下文 - 不这样做将导致上下文在 AutoMapper 有机会之前被释放解决它的依赖关系。

    解决方案 #1

    在我的示例中,以下代码块使用先前定义的 MapperConfiguration 注册 IMapper

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

    可以通过使用MapperConfiguration.CreateMapper() 的重载进行简单调整,该重载接受Func&lt;Type, object&gt; 作为名为@9​​87654327@ 的参数,AutoMapper 将使用该参数来构造依赖项:

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

    必须使用组件上下文context,因为它在闭包中声明,尝试使用c 将导致以下异常:

    此解析操作已结束。使用 lambda 注册组件时,无法存储 lambda 的 IComponentContext 'c' 参数。相反,要么从 'c' 再次解析 IComponentContext,要么解析基于 Func&lt;&gt; 的工厂以从中创建后续组件。

    解决方案 #2

    使用与解决方案#1 非常相似的技术,可以使用IMapperConfiguration.ConstructServiceUsing(Func&lt;Type, object&gt;),它提供了更具可读性的代码。原代码:

    builder.Register(c => {
        var profiles = c.Resolve<IEnumerable<Profile>>();
        return new MapperConfiguration(x => {
            foreach (var profile in profiles) x.AddProfile(profile);           
        });
    }).SingleInstance().AsSelf();
    

    以及调用x.ConstructServiceUsing(constructor)的更新代码:

    builder.Register(c => {
        var profiles = c.Resolve<IEnumerable<Profile>>();
        var context = c.Resolve<IComponentContext>();
        return new MapperConfiguration(x => {
            foreach (var profile in profiles) x.AddProfile(profile);
            x.ConstructServicesUsing(context.Resolve);                   
        });
    }).SingleInstance().AsSelf();
    

    同样,如果您未能在闭包/lambda 中创建 IComponentContext 的实例,则上下文将在 Mapper 创建依赖项之前被释放。

    【讨论】:

    • 我必须为我的 Asp.net Core 2.1+ 项目使用解决方案 #2(使用通用主机控制台应用程序,而不是 WebHost Web 项目)。之前(使用 WebHost),我可以使用 cfg.ConstructServicesUsing(_autofacContainer.Resolve);,因为我有可用的 Autofac 容器。对于通用主机,我不得不使用.UseServiceProviderFactory(new AutofacServiceProviderFactory()) 并且无法访问 Autofac 容器。
    【解决方案2】:

    我猜你在 AutoMapper 配置期间忘记了add the call to ConstructServicesUsing。确保这样做

    确切地如何将 Autofac 与您的应用程序集成实际上取决于您拥有什么样的应用程序(Windows 服务?MVC?Web API?Windows 窗体?UAP?)以及您对生命周期范围使用的期望是。您的问题中均未包含这些内容。但是,如果您在网上搜索“autofac constructservicesusing”,您会找到大量示例,包括关于同一主题的其他几个 StackOverflow 问题。

    Here's a simple example that shows pretty much exactly what you're doing.如果您使用的是 MVC 或 Web API 应用程序并且需要按请求范围支持,I have a full blog walkthrough on that

    顺便说一句,我不确定AutoActivate 调用是否真的有必要。 SingleInstance 是线程安全的,实际上构建 AutoMapper 配置文件并不需要那么长时间。您可能想尝试不使用它,特别是如果 AutoMapper 本身直到 AutoActivate 运行后才执行该部分。

    【讨论】:

    • 这就是我一直在寻找的,你有一个具体的例子来说明如何使用 Autofac 实现这一点吗?我已将ConfigureAutoMapper 扩展方法更新为调用config.CreateMapper(context.Resolve)。但是,我收到一条错误消息,因为上下文已被释放:Additional information: 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&lt;&gt; based factory to create subsequent components from.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多