【问题标题】:How to correctly resolve services to use in the ConfigureServices() in ASP.NET Core 3.1?如何正确解析要在 ASP.NET Core 3.1 的 ConfigureServices() 中使用的服务?
【发布时间】:2020-02-05 22:24:35
【问题描述】:

我有一个基于 ASP.NET Core 3.1 的应用程序。在应用程序启动期间,我想在ConfigureServices(IServiceCollection services) 中注册我的服务。但是在配置服务的过程中,我想根据数据库中的设置来启动应用程序。

这是我的代码

public void ConfigureServices(IServiceCollection services)
{
    // Register context
    services.AddDbContext<AppDbContext>(options =>
    {
        options.UseMySql(Configuration.GetConnectionString("MySqlServerConnection"));
    });

    // Reister the setting provider
    services.AddSingleton<IAppSetting, AppSettings>();

    // Create a resolver which is WRONG!!
    var resolver = services.BuildServiceProvider();

    var setting = resolver.GetService<IAppSetting>();

    List<string> allowedCors = new List<string>()
    {
         setting.MainUrl.ToString()
    };

    if (setting.HasCustomAssetsUri)
    {
        allowedCors.Add(settings.AssetsUrl.ToString());
    }

    if (settings.HasCustomPhotosUri)
    {
        allowedCors.Add(settings.PhotosUrl.ToString());
    }

    services.AddCors(options =>
    {
        options.AddPolicy("AllowSubDomainTraffic",
        builder =>
        {
            builder.WithOrigins(allowedCors.ToArray())
                   .AllowAnyHeader()
                   .AllowAnyMethod();
        });
    });

    // More services
}

正如您在上面的代码中看到的,我注册了IAppSetting,但我立即想使用它来访问数据库,因此获取当前配置。我目前正在调用services.BuildServiceProvider() 来创建一个新的解析器,然后我可以使用它来创建IAppSetting 的一个实例。

我的AppSetting 实现依赖于同样注册的AppDbContext。这是我的应用程序设置

public class AppSetting : IAppSetting
{
    private AppDbContext context;

    public AppSetting(AppDbContext context)
    {
        this.context = context;
    }

    // methods...
}

调用BuildServiceProvider() 将导致创建一个额外的单例服务副本。所以我尽量避免这样做。

如何正确注册然后在ConfigureServices() 方法中解析IAppSetting,以便我可以使用它来访问在数据库中找到的设置?

更新

我尝试使用 Nkosi 提供的解决方案,但出现以下错误

System.InvalidOperationException: '无法解析范围服务 来自根提供商的“IAppSetting”。'

这是完整的堆栈跟踪。

This exception was originally thrown at this call stack:
    Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(System.Type, Microsoft.Extensions.DependencyInjection.IServiceScope, Microsoft.Extensions.DependencyInjection.IServiceScope)
    Microsoft.Extensions.DependencyInjection.ServiceProvider.Microsoft.Extensions.DependencyInjection.ServiceLookup.IServiceProviderEngineCallback.OnResolve(System.Type, Microsoft.Extensions.DependencyInjection.IServiceScope)
    Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(System.Type, Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope)
    Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(System.Type)
    Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(System.IServiceProvider, System.Type)
    Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(Microsoft.Extensions.DependencyInjection.ServiceLookup.FactoryCallSite, Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext)
    Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCallSite, Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext)
    ...
    [Call Stack Truncated]

【问题讨论】:

  • 您不需要AppSetting 来开始机智。改为使用配置系统从数据库加载设置。
  • 文档中的 custom configuration provider example 使用 EF Core 从数据库加载设置。您可以轻松地采用它来加载您的设置。
  • 至于在 DI 设置阶段访问配置,这是already supported。您可以从传递给 Startup 的IConfiguration 对象中检索所需的配置值

标签: asp.net-core .net-core asp.net-core-mvc asp.net-core-3.0 asp.net-core-3.1


【解决方案1】:

参考Use DI services to configure options

使用 DI 配置 CORS 选项,将所有逻辑移至配置委托

//...

// Register context
services.AddDbContext<AppDbContext>(options => {
    options.UseMySql(Configuration.GetConnectionString("MySqlServerConnection"));
});

// Reister the setting provider
services.AddScoped<IAppSetting, AppSettings>(); 

//configure CORS options using DI
services.AddOptions<CorsOptions>()
    .Configure<IServiceScopeFactory>((options, sp) => {
        using(var scope = sp.CreateScope()) {
            IAppSetting settings = scope.ServiceProvider.GetRequiredService<IAppSetting>();
            List<string> allowedCors = new List<string>() {
                 setting.MainUrl.ToString()
            };

            if (setting.HasCustomAssetsUri) {
                allowedCors.Add(settings.AssetsUrl.ToString());
            }

            if (settings.HasCustomPhotosUri) {
                allowedCors.Add(settings.PhotosUrl.ToString());
            }
            options.AddPolicy("AllowSubDomainTraffic", builder => {
                builder.WithOrigins(allowedCors.ToArray())
                       .AllowAnyHeader()
                       .AllowAnyMethod();
            });
        }
    });

services.AddCors();

//...

这样,现在一切都被推迟到真正需要它们的时候,并且您不必过早地构建服务集合。

【讨论】:

  • 感谢您的回答。我收到以下错误 System.InvalidOperationException: 'Cannot resolve scoped service 'IAppSetting' from root provider.' 我将使用完整的堆栈跟踪更新我的问题
  • @John 问题是 DbContext 注册了一个作用域并且设置是一个单例。单例不能有范围依赖。在此处查看警告docs.microsoft.com/en-us/aspnet/core/fundamentals/…
  • 谢谢。即使将services.AddSingleton&lt;IAppSetting, AppSettings&gt;(); 更改为services.AddScoped&lt;IAppSetting, AppSettings&gt;(); ,我仍然遇到同样的错误
  • @John 使用本地范围更新。注意变化
  • 那行得通。但是CreateScope() 不会创建 ServiceProvider 的第二个实例,从而导致第二个实例设置服务吗?
【解决方案2】:

考虑使用IConfigureOptions,如下例所示(避免自己创建范围):

public class ConfigureCorsOptions : IConfigureOptions<CorsOptions>
{
    private readonly IAppSetting _settings;

    public ConfigureCorsOptions(IAppSetting settings) => _settings = settings;


    public void Configure(CorsOptions options)
    {
        List<string> allowedCors = new List<string>()
        {
             _setting.MainUrl.ToString()
        };

        if (setting.HasCustomAssetsUri)
        {
            allowedCors.Add(_settings.AssetsUrl.ToString());
        }

        if (_settings.HasCustomPhotosUri)
        {
            allowedCors.Add(_settings.PhotosUrl.ToString());
        }

        options.AddPolicy("AllowSubDomainTraffic", builder =>
        {
           builder.WithOrigins(allowedCors.ToArray())
                  .AllowAnyHeader()
                  .AllowAnyMethod();
        });
    }
}

要将此类注册到 DI 容器,您可以使用以下内容:

services.AddTransient<IConfigureOptions<CorsOptions>, ConfigureCorsOptions>();

【讨论】:

    【解决方案3】:
    services.AddSingleton<AppSetting>();
    services.AddSingleton<IAppSetting>(ctx =>
        {
          var setting = ctx.GetRequiredService<AppSetting>();
          return setting;
        });
    

    【讨论】:

      猜你喜欢
      • 2020-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-23
      • 2017-05-15
      • 2022-11-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多