.NET 8中如何在单例服务内访问作用域服务?
单例服务中访问作用域服务的.NET 8解决方案
问题背景
需要在单例服务内访问作用域服务IUserRequestData(存储当前请求上下文信息,类似HttpContext.Items),现有实现尝试在CustomMapper(实现IMapperConfiguration)的构造函数中注入IUserRequestData,但.NET DI禁止单例依赖作用域服务。若将IMapperConfiguration设为作用域服务,每次请求调用Map()会重复配置全局单例的TypeAdapterConfig,造成冗余或逻辑覆盖。
现有代码:
public class CustomMapper(IUserRequestData data) : IMapperConfiguration { public void Map() { TypeAdapterConfig<User, UserResponse>.NewConfig() .Map( u => u.GroupName, src => src.Groups.FirstOrDefault(g => g.CnId == data.GetCurrentCnId() ) != null ? src.Groups.FirstOrDefault( g => g.CnId == data.GetCurrentCnId() )!.Name : "" ); TypeAdapterConfig<UserUpdateInput, User>.NewConfig(); } }
可行解决方案
方案1:全局配置映射,执行时动态获取作用域服务
TypeAdapterConfig是全局单例,应在应用启动时完成配置,将依赖请求上下文的逻辑移到映射执行阶段:
- 将
CustomMapper注册为单例,注入IServiceProvider:
public class CustomMapper(IServiceProvider serviceProvider) : IMapperConfiguration { public void Configure() { TypeAdapterConfig<User, UserResponse>.NewConfig() .Map(dest => dest.GroupName, src => { var requestData = serviceProvider.GetRequiredService<IUserRequestData>(); var currentCnId = requestData.GetCurrentCnId(); var group = src.Groups.FirstOrDefault(g => g.CnId == currentCnId); return group?.Name ?? ""; }); TypeAdapterConfig<UserUpdateInput, User>.NewConfig(); } }
- 应用启动时仅调用一次
Configure()完成全局配置:
// Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddScoped<IUserRequestData, UserRequestData>(); builder.Services.AddSingleton<IMapperConfiguration, CustomMapper>(); var app = builder.Build(); // 初始化全局映射配置 var mapperConfig = app.Services.GetRequiredService<IMapperConfiguration>(); mapperConfig.Configure(); // 后续中间件配置...
方案2:使用Mapster依赖注入扩展(推荐)
利用Mapster官方DI扩展,自动在映射时从当前作用域获取服务:
- 安装依赖包:
Install-Package Mapster.DependencyInjection
- 全局配置时通过映射上下文获取服务:
public class CustomMapper : IMapperConfiguration { public void Configure() { TypeAdapterConfig<User, UserResponse>.NewConfig() .Map(dest => dest.GroupName, (src, context) => { var requestData = context.GetService<IUserRequestData>(); var currentCnId = requestData?.GetCurrentCnId() ?? default; var group = src.Groups.FirstOrDefault(g => g.CnId == currentCnId); return group?.Name ?? ""; }); TypeAdapterConfig<UserUpdateInput, User>.NewConfig(); } }
- 注册Mapster并初始化配置:
// Program.cs builder.Services.AddMapster(); builder.Services.AddSingleton<IMapperConfiguration, CustomMapper>(); var app = builder.Build(); var mapperConfig = app.Services.GetRequiredService<IMapperConfiguration>(); mapperConfig.Configure();
方案3:作用域服务配置(不推荐)
若必须在请求周期内调整映射规则,可将IMapperConfiguration注册为作用域服务,但需避免重复覆盖全局配置:
// Program.cs builder.Services.AddScoped<IMapperConfiguration, CustomMapper>();
在请求处理流程中调用Map()时,建议先检查配置是否已存在,避免重复创建:
public void Map() { if (!TypeAdapterConfig<User, UserResponse>.Config.HasMapping) { TypeAdapterConfig<User, UserResponse>.NewConfig() .Map(/* ... */); } // ... }
核心注意事项
- .NET DI禁止单例依赖作用域服务,是为了防止作用域服务被单例长期持有导致生命周期泄漏,必须确保作用域服务仅在请求周期内被临时获取和使用。
TypeAdapterConfig是全局单例配置,应在应用启动时一次性完成,请求周期内重复配置会引发逻辑冲突或性能问题。
内容的提问来源于stack exchange,提问作者eestein
相关产品推荐
相关产品推荐

