ASP.Net Core 5 ConfigureServices 使用基于服务创建实例的反射

ASP.Net Core 5 ConfigureServices using reflection based on instance created by a service

我创建了一个 .Net Core 5 API 有两种模型:

我有一项服务负责将实体类型映射到在 ConfigureServices 中添加为单例的 Dtos 类型:

services.AddSingleton(typeof(IEntityDtoMappingProvider), typeof(EntityDtoMappingProvider));

服务EntityDtoMappingProvider有一个方法,returns通过此接口描述的反射在程序集的实体和Dtos之间进行映射:

public interface IEntityDtoMappingProvider
{
    Dictionary<Type,Type> GetEntityDtoMapping(Assembly assembly);
}

我有一个 AutoMapper 配置文件 需要实体和 DTO 映射,由第一个服务返回 IEntityDtoMappingProvider:

public class EntitiesToDtosProfile : Profile
{
    public EntitiesToDtosProfile(Dictionary<Type,Type> mapping)
    {
        if (mapping == null || mapping.Count == 0)
        {
            throw new ArgumentException( $"Empty mapping argument passed to {nameof(EntitiesToDtosProfile)} profile", nameof(mapping));
        }

        foreach(var item in mapping)
        {
            // Create AutoMapper mapping both ways based on those types
            CreateMap(item.Key, item.Value); // Entity-DTO
            CreateMap(item.Value, item.Key); // DTO-Entity
        }
    }
}

我需要在 Startup.csConfigureServices 方法中创建 AutoMapper 配置文件:

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddSingleton(typeof(IEntityDtoMappingProvider), typeof(EntityDtoMappingProvider));

    // Note: Using services.BuildServiceProvider() is a bad practice because an additional copy of singleton services being created
    using (var serviceProvider = services.BuildServiceProvider())
    {
        var mappingService = serviceProvider.GetRequiredService<IEntityDtoMappingProvider>();
        var mappings = mappingService.GetEntityDtoMapping(typeof(Workflow).Assembly);

        // Add AutoMapper IMapper to services
        var mappingConfig = new MapperConfiguration(mc =>
        {
            mc.AddProfile(new EntitiesToDtosProfile(mappings));
        });
        var mapper = mappingConfig.CreateMapper();
        services.AddSingleton(mapper);
        
        // Here I should call other IServiceCollection extensions like:

        // Database-related services: GenericRepository<TEntity, TDbContext> : IGenericRepository<TEntity>
        services.AddDatabaseGenericRepositories<ApplicationDbContext>(mappings, Log.Logger);

        // Mapping-related services: MappingHelper<TEntity, TDto> : IMappingHelper<TEntity, TDto>
        services.AddMappingHelpers(mappings, Log.Logger);

        // ...
    }
    // ...
}

正如我在代码中所说,使用 services.BuildServiceProvider() 是一种不好的做法,因为正在创建的单例服务的附加副本会创建第二个容器,这可以创建撕裂的单例并导致跨多个容器引用对象图。 Microsoft .Net Core 5 documentation backing those statements.

请回答我应该如何在 CreateServices 中使用 Dictionary 创建类型为 Entity-DTO 的映射IEntityDtoMappingProvider 为了构建 AutoMapper 配置文件 EntitiesToDtosProfile 并通过反射创建其他服务而不调用 services.BuildServiceProvider 考虑到以下因素:

您可以注册一个服务工厂,它接受服务提供者实例并使用它来解析其他服务。例如:

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddSingleton(typeof(IEntityDtoMappingProvider), typeof(EntityDtoMappingProvider));
    
    services.AddSingleton(sp =>
    {
        var mappingService = sp.GetRequiredService<IEntityDtoMappingProvider>();
        var mappings = mappingService.GetEntityDtoMapping(typeof(Workflow).Assembly);

        var mappingConfig = new MapperConfiguration(mc =>
        {
            mc.AddProfile(new EntitiesToDtosProfile(mappings));
        });
        
        return mappingConfig.CreateMapper();
    });
    // ...
}

您需要修改 AddDatabaseGenericRepositoriesAddMappingHelpers 方法来执行类似的操作。

如果您注册 IEntityDtoMappingProvider 只是为了使用它来构建映射组件,那么您可能不应该注册它。这种一次性配置通常最好在容器本身的范围之外完成。正如您所建议的那样,您可能可以完全删除接口并直接使用具体的 class。

记录器配置等也一样。