【问题标题】:ASP.Net Core 5 ConfigureServices using reflection based on instance created by a serviceASP.Net Core 5 ConfigureServices 使用基于服务创建的实例的反射
【发布时间】:2022-01-12 20:01:28
【问题描述】:

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

  • 实体(由 Entity Framework Core 使用)
  • DTO(请求和响应的数据传输对象,将实体中的“{Property}Id”属性替换为 DTO 中的“{Property}Code”)

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

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

EntityDtoMappingProvider服务有一个方法,该方法通过该接口描述的反射返回一个程序集的实体和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
        }
    }
}

我需要在 ConfigureServices 方法的 Startup.cs 中创建 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.

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

  • 我使用 IServiceCollection 的扩展方法通过反射创建了许多服务需要在 ConfigureServices 中进行 Entity-DTO 映射
  • 我不能使用具有 Dictionary 类型属性的 IOptions,因为不应在 ConfigureServices 中使用 IOptions:"由于服务注册的顺序,可能存在不一致的选项状态。”来源:IOptions Microsoft Documentation.
  • 我查看了alotofquestions(有些可能有点无关),但都使用 services.BuildServiceProvider()IOptions 解决了他们的问题 这不行。

【问题讨论】:

  • 如果您只是注册IEntityDtoMappingProvider 以便使用它来构建您的映射组件,那么也许您不应该注册它。
  • 好点,我只用它来创建一个 AutoMapper 配置文件 (EntitiesToDtosProfile) 并通过映射循环的反射创建大量服务。我可能会丢失接口并只创建实例,而根本不注册它。您可以创建它作为答案,如果没有更好的结果,我会接受它。
  • 完成。有时最简单的解决方案是最好的。

标签: c# dependency-injection asp.net-core-5.0


【解决方案1】:

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

记录器配置也是如此。

【讨论】:

    【解决方案2】:

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

    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 方法来做类似的事情。

    【讨论】:

    • 您的答案仅适用于单个服务注册(在您的情况下为单例)。 AddDatabaseGenericRepositories 和 AddMappingHelpers 方法添加多个服务循环遍历映射并创建通用类型,例如:“var interfaceType = typeof(IMappingHelper).MakeGenericType(mapping.Key, mapping.Value)”、“var implementationType = typeof(MappingHelper).MakeGenericType(mapping.Key, mapping.Value))”,然后像“services.AddSingleton(interfaceType, implementationType);”一样将它们一一注册(或 AddTransient 等)。
    • 在这种情况下,我怀疑您需要听从 satnhak 的建议,手动创建 EntityDtoMappingProvider 实例,而不是在 DI 容器中注册它。
    猜你喜欢
    • 2016-09-10
    • 2016-04-16
    • 1970-01-01
    • 2022-11-24
    • 1970-01-01
    • 2018-03-29
    • 1970-01-01
    • 1970-01-01
    • 2011-09-12
    相关资源
    最近更新 更多