使用 Autofac 配置 AutoMapper 以实现 ITypeConverter<,> 构造函数依赖

Configuring AutoMapper to fulfil ITypeConverter<,> constructor dependecies with Autofac

我第一次使用 Autofac 将 AutoMapper 的 IMapper 接口注入到具有对象映射要求的 类 中。我取得了一些进展,,使用程序集扫描将各种依赖项添加到 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<,> 非常有效:

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 needs to have a constructor with 0 args or only optional args

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

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

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

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

Here's a simple example that shows pretty much exactly what you're doing. If you're using an MVC or Web API app and need per-request-scope support, I have a full blog walkthrough on that.

请注意,我不确定 AutoActivate 调用是否真的有必要。 SingleInstance 是线程安全的,实际上懒惰地构建 AutoMapper 配置文件并不需要那么长时间。您可能想尝试不使用它,特别是如果 AutoMapper 本身在 AutoActivate 具有 运行.

之后才执行该部分

NOTE: Travis Illig has provided a which I am marking as the answer as it answers the question in a broad and generic way. However, I also wanted to document the specific solution to my 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<Type, object> 作为名为 serviceCtor 的参数,AutoMapper 将使用它来构造依赖项:

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

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

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.

解决方案 #2

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

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 创建依赖项之前被释放。