using System.Net.Http;
using Microsoft.Extensions.Logging;

namespace Dpz.Core.ServiceTest;

public abstract class Basic
{
    private readonly IServiceProvider _serviceProvider;

    protected Basic()
    {
        var config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.Test.json")
            .Build();
        Configuration = config;
        ConnectionString = Configuration.GetConnectionString("mongodb");
        IServiceCollection services = new ServiceCollection();

        // const string cacheName = "redis";
        // const string memoryCacheName = "dpangzi-memory";
        // services.AddEasyCaching(options =>
        // {
        //     options.UseInMemory(memoryCacheName);
        //     options.UseHybrid(cacheConfig =>
        //     {
        //         cacheConfig.TopicName = "service-cache";
        //         cacheConfig.EnableLogging = true;
        //         cacheConfig.LocalCacheProviderName = memoryCacheName;
        //         cacheConfig.DistributedCacheProviderName = cacheName;
        //     }, "hybrid-cache");
        // });
        services.AddBusinessServices(Configuration);

        services.AddSingleton<IConfiguration>(_ => config);
        services.AddSingleton<HttpClient>(_ => new HttpClient());
        services.AddTransient<IUnitOfWork, UnitOfWork>();
        services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
        services.AddScoped<IWaitExecutionService, WaitExecutionService>();
        services.AddScoped<IObjectStorageOperation, ObjectStorageService>();
        services.AddScoped<IAccountService, AccountService>();
        services.AddScoped<IMaterialIconService, MaterialIconService>();
        services.AddScoped<IAppOptionService, AppOptionService>();


        //Steam API
        services.AddHttpClient<ISteamGameService, SteamGameService>((sp, client) =>
        {
            ActivatorUtilities.CreateInstance<SteamGameService>(sp);
            client.BaseAddress = new Uri("https://api.steampowered.com");
        }).ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler
        {
            ServerCertificateCustomValidationCallback = (_, _, _, _) => true
        });

        _serviceProvider = services
            .AddLogging(builder => builder.AddConsole())
            .BuildServiceProvider();
    }

    protected T GetService<T>()
    {
        return _serviceProvider.GetService<T>() ?? throw new Exception($"service {typeof(T).Name} is null");
    }

    protected IServiceProvider ServiceProvider => _serviceProvider;

    protected string? ConnectionString { get; }

    protected IConfiguration Configuration { get; }

    // protected IEasyCachingProvider CachingProvider => _serviceProvider.GetService<IEasyCachingProvider>();
    //
    // protected IHybridCachingProvider HybridCachingProvider => _serviceProvider.GetService<IHybridCachingProvider>();

    protected IMapper Mapper => GetService<IMapper>();

    protected IRepository<AppOption> AppOptionRepository => new Repository<AppOption>(ConnectionString);

    protected IRepository<Comment> CommentRepository => new Repository<Comment>(ConnectionString);

    protected IRepository<Article> ArticleRepository => new Repository<Article>(ConnectionString);

    protected IRepository<Mumble> MumbleRepository => new Repository<Mumble>(ConnectionString);

    protected IRepository<PageMetadata> PageMetadataRepository => new Repository<PageMetadata>(ConnectionString);

    protected IRepository<SteamGame> SteamGameRepository => new Repository<SteamGame>(ConnectionString);

    protected IRepository<LanguageIcons> LanguageIconsRepository => new Repository<LanguageIcons>(ConnectionString);

    protected IRepository<FileIcons> FileIconsRepository => new Repository<FileIcons>(ConnectionString);

    protected IRepository<FolderIcons> FolderIconsRepository => new Repository<FolderIcons>(ConnectionString);
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

下面把这段 C# 代码的功能、结构和一些需要注意的点条理化地说明,便于理解它在测试工程中的作用。

总体作用

  • 这是一个抽象基类 Basic,通常用于测试或集成测试场景,负责构建并初始化依赖注入容器(IServiceProvider)、读取配置、注册常用服务和提供便捷的属性/方法供子类使用。子类可以通过继承 Basic 来获得统一的测试环境和共享依赖。

构造函数中做了什么(按步骤)

  1. 读取配置

    • 使用 ConfigurationBuilder 加载文件 appsettings.Test.json 并构建 IConfiguration。
    • 从配置中读取名为 "mongodb" 的连接字符串并保存到 ConnectionString 字段。
  2. 创建并配置 IServiceCollection

    • new ServiceCollection():创建 DI 服务集合。
    • services.AddBusinessServices(Configuration):调用自定义扩展方法(假定在项目中定义)批量注册业务所需服务(如 AutoMapper、业务服务、仓储等——具体取决于该扩展方法的实现)。
    • 注册一些单例/短暂/作用域服务:
      • IConfiguration 单例(返回之前的 config)。
      • HttpClient 单例(直接 new)。
      • IUnitOfWork -> UnitOfWork(Transient)。
      • IRepository<> -> Repository<>(Scoped)。
      • 若干具体服务(IWaitExecutionService、IObjectStorageOperation、IAccountService、IMaterialIconService、IAppOptionService)均以 Scoped 注册。
    • Steam API:通过 AddHttpClient 为 ISteamGameService 注册 HttpClient:
      • 配置 BaseAddress 为 https://api.steampowered.com。
      • ConfigurePrimaryHttpMessageHandler 指定 HttpClientHandler,并将 ServerCertificateCustomValidationCallback 设置为总是返回 true(允许所有服务器证书)。
      • 注意:内部调用 ActivatorUtilities.CreateInstance(sp) 看上去是多余的(AddHttpClient 本身会用 DI 创建实现),该调用只是创建实例但没有使用返回值。
  3. 日志与构建 ServiceProvider

    • services.AddLogging(builder => builder.AddConsole()):添加控制台日志提供器。
    • 最后调用 BuildServiceProvider() 得到 IServiceProvider 并保存到私有字段 _serviceProvider。

提供的辅助方法和属性

  • GetService():从 _serviceProvider 获取服务,若为 null 则抛出异常(便于尽早发现未注册的依赖)。
  • ServiceProvider:公开 IServiceProvider。
  • ConnectionString、Configuration:公开读取到的连接字符串和配置。
  • Mapper:快捷属性,使用 GetService() 获取 AutoMapper(假定在 AddBusinessServices 中注册了 IMapper)。
  • 一组仓储相关的属性(例如 AppOptionRepository、CommentRepository 等):
    • 每个属性都返回 new Repository(ConnectionString) —— 注意:这些不是从 DI 容器获取,而是每次访问都会 new 一个 Repository 实例,使用相同的 ConnectionString。

被注释掉的缓存相关代码

  • 有一段关于 EasyCaching(内存/混合缓存)的注册被注释掉了,表示原来有计划使用缓存,但现在暂时禁用。

需要注意/潜在问题与设计说明

  • HttpClient 单例:
    • 代码中 services.AddSingleton(_ => new HttpClient()); 把 HttpClient 注册为单例。通常推荐使用 IHttpClientFactory(即 AddHttpClient)以管理生命周期和处理 DNS 变化,但此处也为 ISteamGameService 使用了 AddHttpClient(更推荐)。
  • ServerCertificateCustomValidationCallback = (,,,) => true
    • 这会忽略所有 HTTPS 证书错误,在测试环境可以,但生产环境存在安全风险。
  • ActivatorUtilities.CreateInstance(sp) 在 AddHttpClient 的配置委托中调用似乎没有必要,且可能导致重复实例化或副作用。
  • 仓储注册冲突/不一致:
    • 代码既在 DI 中用 services.AddScoped(typeof(IRepository<>), typeof(Repository<>)) 注册了泛型仓储,又在类中提供了一组直接返回 new Repository(ConnectionString) 的属性。二者并不一致:通过属性取得的是直接 new 的实例(每次新建),而通过 GetService<IRepository>() 会得到由容器管理的作用域实例。应统一使用一种方式。
  • ConnectionString 可能为 null:
    • 如果 appsettings.Test.json 中没有相应连接串,ConnectionString 会为 null,直接 new Repository(connectionString) 可能导致运行时问题。GetService() 可用于检查。
  • GetService() 抛异常:
    • 这个行为在测试中有利于快速发现未注册依赖,但在某些情况下可能希望返回 null 或提供更友好的错误信息。
  • 配置文件路径
    • appsettings.Test.json 的路径是相对路径,运行测试时需要确保文件存在于运行目录或通过复制到输出目录等方式可访问。

总结

  • Basic 是一个测试/基础类,用于集中初始化配置和依赖注入容器,注册常用服务(业务服务、仓储、HttpClient、日志等),并提供便捷的属性和方法供测试类获取配置、服务、仓储等。
  • 在测试环境中它简化了“搭建运行时环境”的工作,但代码中有一些值得改进或需要小心使用的地方(证书校验、重复仓储实例化、ActivatorUtilities 的可疑调用等)。
评论加载中...