AutoMapper 依赖注入(DI)完全指南:AddAutoMapper、服务注册与低层 API 详解
【免费下载链接】AutoMapperA convention-based object-object mapper in .NET.项目地址: https://gitcode.com/gh_mirrors/au/AutoMapper
导读
本文基于 AutoMapper 官方文档的 Dependency Injection 章节,结合当前仓库源码(src/AutoMapper/ServiceCollectionExtensions.cs、src/AutoMapper/Configuration/MapperConfigurationExpression.cs等),系统讲解如何在 ASP.NET Core 中通过AddAutoMapper完成配置装配与依赖注入,包括程序集扫描、Profile 注册、IMapper/IConfigurationProvider的生命周期管理、八类可自动注册的服务类型,以及不依赖容器的低层ConstructServicesUsingAPI。读完本文,你将能在自己的 .NET 项目中熟练接入 AutoMapper 的 DI 机制,并理解其内部注册逻辑与使用边界。
一、版本背景:13.0 之后 DI 支持内置于核心包
从13.0 版本开始,AddAutoMapper扩展方法已经成为 AutoMapper 核心包(AutoMapper)的一部分,原先独立的AutoMapper.Extensions.Microsoft.DependencyInjection包已被停止维护、不再发布。这一点在仓库的 13.0-Upgrade-Guide.md 中有明确记载:"AddAutoMapperis part of the core package and the DI package is discontinued"。
因此,现代项目中你只需要引用核心包,即可直接使用Microsoft.Extensions.DependencyInjection命名空间下的AddAutoMapper扩展方法。仓库中的实现位于 ServiceCollectionExtensions.cs,其命名空间正是Microsoft.Extensions.DependencyInjection,意味着它能无缝融入标准 ASP.NET Core 的容器体系。
二、ASP.NET Core 中的标准用法
2.1 用 Profile 定义配置
AutoMapper 的映射配置推荐以 Profile(配置文件类)为单位组织。每个 Profile 继承自AutoMapper.Profile,并在构造函数(或Configure方法)中调用CreateMap等 API 声明映射规则。关于 Profile 的完整说明,参见文档 Configuration.md 中的 Profile Instances 一节。仓库中 Profile.cs 的源码展示了 Profile 的核心结构:它持有TypeMapConfigs(映射配置集合),并通过CreateMap<TSource, TDestination>()、CreateProjection<TSource, TDestination>()等方法将映射表达式加入集合。
2.2 在启动时调用 AddAutoMapper
在Startup.ConfigureServices(或 .NET 6+ 的Program.cs中)调用IServiceCollection的扩展方法AddAutoMapper,并告知 AutoMapper 在哪些程序集中查找 Profile:
services.AddAutoMapper(cfg => { }, profileAssembly1, profileAssembly2 /*, ...*/);其中profileAssembly1、profileAssembly2是System.Reflection.Assembly实例,也可以使用marker type(标记类型)写法,由 AutoMapper 自动推导其所在的程序集:
services.AddAutoMapper(cfg => { }, typeof(ProfileTypeFromAssembly1), typeof(ProfileTypeFromAssembly2) /*, ...*/);两种写法在源码中都有对应的重载:ServiceCollectionExtensions.cs 提供了params Assembly[]与params Type[]两组入口;其中Type[]重载通过t.GetTypeInfo().Assembly将标记类型转换为程序集,再交给统一的内部方法处理。
cfg参数是一个Action<IMapperConfigurationExpression>委托,可以在这里直接补充额外的全局配置(例如cfg.CreateMap<Source, Destination>()、cfg.AddProfile<TProfile>()等)。仓库还额外提供了一个接收Action<IServiceProvider, IMapperConfigurationExpression>的重载,让你在配置阶段就能从容器解析服务。
2.3 注入并消费 IMapper
完成注册后,即可在任意服务或控制器中注入IMapper并在运行时执行映射:
public class EmployeesController { private readonly IMapper _mapper; public EmployeesController(IMapper mapper) => _mapper = mapper; // 使用 _mapper.Map 或 _mapper.ProjectTo }IMapper接口定义在 Mapper.cs 中,提供了Map<TSource, TDestination>、Map<TDestination>(object source)、带Action<IMappingOperationOptions>的重载等一系列映射入口。
三、AddAutoMapper 的注册机制:生命周期与装配细节
结合 ServiceCollectionExtensions.cs 的AddAutoMapperClasses私有方法,可以梳理出AddAutoMapper内部实际完成的四件事:
- 收集配置:若传入
configAction,将其通过services.AddOptions<MapperConfigurationExpression>().Configure<IServiceProvider>(...)注册为IOptions配置源;若传入程序集,则调用options.AddMaps(assembliesToScan)扫描这些程序集中的 Profile 与[AutoMap]特性标注的类(参见 MapperConfigurationExpression.cs 中 AddMaps 的说明)。 - 过滤并注册服务类:程序集会先过滤掉动态程序集和 AutoMapper 自身的程序集(
a.IsDynamic && a != typeof(Mapper).Assembly),再筛选出实现了 AutoMapper 服务接口的具体类,逐个用TryAddTransient注册(TryAdd语义可避免重复注册)。 - 注册 IConfigurationProvider 为单例(Singleton):工厂方法从容器解析
IOptions<MapperConfigurationExpression>和ILoggerFactory,构造MapperConfiguration。这与 MapperConfiguration.cs 中MapperConfiguration(MapperConfigurationExpression, ILoggerFactory)的构造函数一一对应——IConfigurationProvider持有全部编译好的执行计划,天然适合全局复用。 - 注册 IMapper:
IMapper的默认生命周期是Transient,且通过sp.GetService(容器解析)作为其服务构造器;IMapper的注册还带有一个防重复检查(if (services.Any(sd => sd.ServiceType == typeof(IMapper))) return services;),保证重复调用AddAutoMapper不会产生双份注册。
3.1 自定义 IMapper 生命周期
IMapper的默认生命周期为Transient(每次解析都新建实例),该默认值定义在 MapperConfigurationExpression.cs(public ServiceLifetime ServiceLifetime { get; set; } = ServiceLifetime.Transient;)。你也可以通过带ServiceLifetime参数的重载改成Singleton或Scoped:
services.AddAutoMapper(cfg => { }, assemblies, ServiceLifetime.Singleton);仓库测试 ServiceLifetimeTests.cs 对此做了验证:默认调用AddAutoMapper(_ => { })后,IMapper的ServiceDescriptor.Lifetime为Transient;传入ServiceLifetime.Singleton后则变为Singleton。
注意:
IConfigurationProvider始终是Singleton,只有IMapper的生命周期可配置。由于IMapper内部持有IConfigurationProvider,即使IMapper是 Transient,其执行计划仍被全局缓存复用。测试 Integrations/ServiceLifetimeTests.cs 还演示了把注入IMapper的服务注册为单例的完整用法。
四、Autofac 及其他 DI 容器
- Autofac:社区提供了第三方包
AutoMapper.Contrib.Autofac.DependencyInjection可供尝试。 - 其他 DI 引擎:AutoMapper 官方维护过一个 DI 示例合集(
AutoMapper/wiki/DI-examples),涵盖多种容器的接入方式。
对于不在上述包支持范围内的容器,核心思路是使用下一节介绍的低层 API 手动接线。
五、低层 API:ConstructServicesUsing 与静态服务定位
不依赖AddAutoMapper时,AutoMapper 允许为自定义值解析器(Custom Value Resolvers)、自定义类型转换器(Custom Type Converters)、值转换器(Value Converters)和基于类的条件(Class-based Conditions)通过静态服务定位的方式提供构造函数依赖:
var configuration = new MapperConfiguration(cfg => { cfg.ConstructServicesUsing(ObjectFactory.GetInstance); cfg.CreateMap<Source, Destination>(); }, loggerFactory);ConstructServicesUsing接收一个Func<Type, object>工厂委托,用于在映射过程中创建上述服务类型实例。该 API 有三层使用位置,作用范围依次收窄:
- 配置级:
IMapperConfigurationExpression.ConstructServicesUsing(MapperConfigurationExpression.cs),默认实现是Activator.CreateInstance(见该文件第 104 行_serviceCtor的初值),即未配置时通过反射无参构造。 - Mapper 实例级:
MapperConfiguration.CreateMapper(Func<Type, object> serviceCtor)(MapperConfiguration.cs)可为特定 Mapper 实例指定服务工厂。 - 单次映射级:
IMappingOperationOptions.ConstructServicesUsing(IMappingOperationOptions.cs),用于"子容器/嵌套容器"场景。其实现会把新工厂与原工厂做回退串联:ServiceCtor = t => constructor(t) ?? ctor(t)(见该文件第 58-62 行),即先尝试新工厂,失败(返回 null)时回退到上层工厂。
使用ConstructServicesUsing时,值解析器/转换器上的构造函数参数将由该工厂按Type解析后注入。这是把 AutoMapper 手动桥接到任意 IoC 容器的标准方式,也是AddAutoMapper内部机制的底层形态。
六、自动类注册:八类服务接口一览
调用AddAutoMapper并指定程序集后,AutoMapper 会从这些程序集中自动发现以下接口的实现类,并以ServiceLifetime.Transient注册到容器:
| 接口 | 用途 |
|---|---|
IValueResolver<TSource, TDestination, TDestMember> | 自定义成员值解析器 |
IMemberValueResolver<TSource, TDestination, TSourceMember, TDestMember> | 带源成员参数的自定义值解析器 |
ITypeConverter<TSource, TDestination> | 自定义类型转换器 |
IValueConverter<TSourceMember, TDestinationMember> | 值转换器 |
IDestinationFactory<TSource, TDestination> | 目标对象工厂 |
ICondition<TSource, TDestination, TMember> | 基于类的条件判断 |
IPreCondition<TSource, TDestination> | 映射前置条件 |
IMappingAction<TSource, TDestination> | 映射动作(Before/After Map) |
源码中的判定逻辑一目了然:ServiceCollectionExtensions内维护了一个开放泛型数组AmTypes = [typeof(IValueResolver<,,>), typeof(IMemberValueResolver<,,,>), typeof(ITypeConverter<,>), typeof(IValueConverter<,>), typeof(IDestinationFactory<,,>), typeof(ICondition<,,>), typeof(IPreCondition<,>), typeof(IMappingAction<,>)](ServiceCollectionExtensions.cs),随后对每个扫描到的具体类执行type.GetGenericInterface(openType) != null判断(第 88 行),命中的即调用services.TryAddTransient(type)注册。也就是说,只要你的类实现了上述任一接口,无需任何额外注册代码,容器就能在映射时将其解析注入。
需要注意:这些服务接口的判定依赖类型恰好实现对应的开放泛型接口。如果你的实现类还实现了其他接口(如IDisposable),并不影响注册;但如果同时实现多个 AutoMapper 服务接口,它们会被分别注册。
七、Queryable Extensions:ProjectTo 与 DI 的边界
从 8.0 版本开始,可以调用IMapper.ProjectTo进行查询投影;更早的版本则需要把配置对象传给扩展方法IQueryable.ProjectTo<T>(IConfigurationProvider)。
必须特别强调的是,ProjectTo比Map能力更受限:投影是在 LINQ 提供程序(如 EF Core 翻译成 SQL)层面完成的,只支持底层提供程序允许的映射选项(详细说明见 Queryable-Extensions.md 的 Supported Mapping Options 一节)。因此:
- 在
ProjectTo场景下,不能像Map那样使用依赖注入的值解析器和转换器——因为查询翻译发生在数据库端,无法执行任意 C# 服务代码。 - 如果确实需要服务参与计算,应退回到内存映射(
Map),或在投影前先把数据取回内存。
这一点也解释了为何IMapper.ProjectTo只依赖IConfigurationProvider即可工作:投影构建只读取映射配置,不触达服务容器。IConfigurationProvider可通过容器直接解析,也可通过IMapper.ConfigurationProvider属性获取(见 ServiceCollectionExtensions.cs 的注释说明)。
八、实战建议与常见坑
- 程序集扫描的过滤规则:
AddAutoMapper会自动跳过动态程序集和 AutoMapper 自身程序集(a != typeof(Mapper).Assembly),避免把 AutoMapper 内部类型误注册,无需你手动处理。 - 重复调用 AddAutoMapper 是安全的:服务类注册使用
TryAddTransient,IMapper注册有防重复检查,多次调用不会产生重复注册或异常。 - IMapper 生命周期与作用域的匹配:默认
Transient最安全,适用于大多数 Web 场景;若改为Singleton,需注意IMapper中不应捕获 scoped 服务。仓库测试已验证单例服务注入IMapper后仍可在作用域内正常映射。 - 低层 API 与容器二选一:使用
AddAutoMapper时无需(也不应)再手动ConstructServicesUsing;二者是"容器集成"与"手工接线"的两种方案,混用可能造成解析行为不一致。 - ProjectTo 与 DI 不兼容:投影查询中不要依赖值解析器/转换器的容器注入,改用可在 LINQ 表达式树中表达的映射方式。
结语
AutoMapper 的 DI 支持在 13.0 之后彻底融入核心包,AddAutoMapper承担了"程序集扫描 → Profile 装配 → 八类服务接口自动注册 →IConfigurationProvider单例 +IMapper可配置生命周期"的全部职责;ConstructServicesUsing则为非标准容器提供了等价的低层接入点。理解这两条路径及其在ProjectTo上的边界,即可在真实项目中安全、高效地使用 AutoMapper 的依赖注入能力。
【免费下载链接】AutoMapperA convention-based object-object mapper in .NET.项目地址: https://gitcode.com/gh_mirrors/au/AutoMapper
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考