【问题标题】:Calling 'BuildServiceProvider' from application code results in copy of Singleton warning. How do I avoid this?从应用程序代码调用“BuildServiceProvider”会导致单例警告的副本。我该如何避免这种情况?
【发布时间】:2019-11-22 17:53:06
【问题描述】:

我刚刚从另一个项目粘贴了最后的 4 行,它可以工作,但我收到警告。我显然对 DI 理解不够好……它想让我改变什么?

  public void ConfigureServices(IServiceCollection services)
        {
            if (HostingEnvironment.EnvironmentName == "Local")
            {
                services.AddHealthChecksUI()
               .AddHealthChecks()
               .AddCheck<TestWebApiControllerHealthCheck>("HomePageHealthCheck")
               .AddCheck<DatabaseHealthCheck>("DatabaseHealthCheck");
            }

        services.Configure<PwdrsSettings>(Configuration.GetSection("MySettings"));
        services.AddDbContext<PwdrsContext>(o => o.UseSqlServer(Configuration.GetConnectionString("PwdrsConnectionRoot")));

        services.AddMvc(o =>
        {
            o.Filters.Add<CustomExceptionFilter>();
        });

        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy", b => b
                .SetIsOriginAllowed((host) => true)
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials());
        });

        services.AddSwaggerDocument();
        services.AddHttpContextAccessor();

        services.AddAutoMapper(typeof(ObjectMapperProfile));
        services.AddTransient<IEmailSender, EmailSender>();
        services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
        services.AddScoped(typeof(IAsyncRepository<>), typeof(Repository<>));
        services.AddScoped<IRfReportTypeRepository, RfReportTypeRepository>();
        services.AddScoped<IRfReportRepository, RfReportRepository>();
        services.AddScoped<IRfReportLookupsService, RfReportLookupsService>();
        services.AddScoped<IRfReportService, RfReportService>();

        services.Configure<RAFLogging>(Configuration.GetSection("RAFLogging"));
        ServiceProvider serviceProvider = services.BuildServiceProvider(); //WARNING IS HERE
        IOptions<RAFLogging> RAFLogger = serviceProvider.GetRequiredService<IOptions<RAFLogging>>();
        RegisterSerilogLogger logger = new RegisterSerilogLogger(RAFLogger);
    }

【问题讨论】:

  • 首先,您为什么要构建提供程序?这可能是XY problem。您能否重新格式化问题,以便我们更清楚地了解当前问题以及您实际上想要做什么?
  • 我不确定。我想我已经有了一个,也许正在创造另一个?
  • WARNING IS HERE 是什么意思?请提供有关警告的详细信息。向我们展示警告的文本​​。这是编译器警告吗?来自某些代码分析插件的警告?如果有,是哪一个?这是运行时异常吗?向我们展示异常的所有相关细节(消息、类型、堆栈跟踪、内部异常)。
  • @punkouter "它想让我改变什么":不要通过调用BuildServiceProvider() 手动构建服务提供者。这个方法应该只被 Host 调用一次。重复的服务提供者可能会导致一些意想不到的错误。
  • 警告是标题。我想 IServiceCollection 是我应该把这个记录器放在哪里?我需要更好地了解 IServiceCollection 与 ServiceProvider。

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


【解决方案1】:

如果在 ConfigureServices 中调用 BuildServiceProvider(),则会显示警告“从应用程序代码调用 'BuildServiceProvider' 会导致创建一个额外的单例服务副本”

我解决了这个问题:

创建另一个函数(传递的参数是IServiceCollection)并进入函数调用BuildServiceProvider()

例如你的代码应该是:

public void ConfigureServices(IServiceCollection services)
{
    if (HostingEnvironment.EnvironmentName == "Local")
    {
        services.AddHealthChecksUI()
        .AddHealthChecks()
        .AddCheck<TestWebApiControllerHealthCheck>("HomePageHealthCheck")
        .AddCheck<DatabaseHealthCheck>("DatabaseHealthCheck");
    }

    services.Configure<PwdrsSettings>(Configuration.GetSection("MySettings"));
    services.AddDbContext<PwdrsContext>(o => o.UseSqlServer(Configuration.GetConnectionString("PwdrsConnectionRoot")));

    services.AddMvc(o =>
    {
        o.Filters.Add<CustomExceptionFilter>();
    });

    services.AddCors(options =>
    {
        options.AddPolicy("CorsPolicy", b => b
            .SetIsOriginAllowed((host) => true)
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials());
    });

    services.AddSwaggerDocument();
    services.AddHttpContextAccessor();

    services.AddAutoMapper(typeof(ObjectMapperProfile));
    services.AddTransient<IEmailSender, EmailSender>();
    services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
    services.AddScoped(typeof(IAsyncRepository<>), typeof(Repository<>));
    services.AddScoped<IRfReportTypeRepository, RfReportTypeRepository>();
    services.AddScoped<IRfReportRepository, RfReportRepository>();
    services.AddScoped<IRfReportLookupsService, RfReportLookupsService>();
    services.AddScoped<IRfReportService, RfReportService>();

    RegisterSerilogLogger logger = CreateRegisterSerilogLogger(services);
}

private RegisterSerilogLogger CreateRegisterSerilogLogger(IServiceCollection services){
        services.Configure<RAFLogging>(Configuration.GetSection("RAFLogging"));
        ServiceProvider serviceProvider = services.BuildServiceProvider(); //No warning here ))
        IOptions<RAFLogging> RAFLogger = serviceProvider.GetRequiredService<IOptions<RAFLogging>>();
        RegisterSerilogLogger logger = new RegisterSerilogLogger(RAFLogger);
    return logger;
}

或者使用 IApplicationBuilder 的 ApplicationServices。 ApplicationServices 的类型是 IServiceProvider。

我提到此解决方案仅用于删除警告

调用 BuildServiceProvider 会创建第二个容器,该容器可以创建撕裂的单例并导致跨多个容器引用对象图。


2021 年 1 月 24 日更新

我读过亚当弗里曼的Pro ASP.NET Core 3 8th 书。为此,Adam Freeman 在第 157 页中使用了 app.ApplicationServices 而不是 services.BuildServiceProvider(),该应用程序是该方法位于 Startup.cs 中的 Configure 方法的参数

我认为正确的版本是使用应用程序的ApplicationServices属性,该应用程序是Configure方法参数中的IApplicationBuilder。 ApplicationServices 的类型是 IServiceProvider。

Adam Freeman 的 Pro ASP.NET Core 3 第 8 本书:Pro ASP.NET Core 3

Adam Freeman 的示例项目:SportStore project's Startup.csSportStore project's SeedData.cs

微软关于 DI 的建议:Dependency injection in ASP.NET Core

Stackoverflow 中类似问题的答案:https://stackoverflow.com/a/56058498/8810311https://stackoverflow.com/a/56278027/8810311

【讨论】:

  • 从另一个函数调用此方法不是正确的解决方案。您应该避免在代码中的任何地方调用它。这只是删除警告。
  • @Adys 我同意你的朋友,我提到这个解决方案是删除警告:) 我认为正确的版本是调用应用程序的 ServiceProvider,该应用程序是 Configure 方法中的 IApplicationBuilder
  • 要删除警告,您可以直接取消它。
  • 我使用了 var serviceProvider = services.BuildServiceProvider(); var logger = serviceProvider.GetService>(); services.AddSingleton(typeof(ILogger), logger);在函数中。我之前收到了一个额外的单身警告
  • 什么是RegisterSerilogLogger?应该使用它来代替 addSingleton
【解决方案2】:

调用“BuildServiceProvider”的ONLY目的是获取服务提供者实例,

要删除此调用并仍然能够使用 IServiceProvider,请更改 Configure 方法以将其作为参数获取:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider provider)

【讨论】:

  • 你不需要注入 IServiceProvider 因为 app.ApplicationServices 是 IServiceProvider 。 – 拉米尔·阿利耶夫 2 月 24 日 11:48
猜你喜欢
  • 1970-01-01
  • 2011-04-01
  • 2013-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多