如何正确解析要在 ASP.NET Core 3.1 的 ConfigureServices() 中使用的服务?
Posted
技术标签:
【中文标题】如何正确解析要在 ASP.NET Core 3.1 的 ConfigureServices() 中使用的服务?【英文标题】:How to correctly resolve services to use in the ConfigureServices() in ASP.NET Core 3.1? 【发布时间】: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
对象中检索所需的配置值
【参考方案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<IAppSetting, AppSettings>();
更改为services.AddScoped<IAppSetting, AppSettings>();
,我仍然遇到同样的错误
@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;
);
【讨论】:
以上是关于如何正确解析要在 ASP.NET Core 3.1 的 ConfigureServices() 中使用的服务?的主要内容,如果未能解决你的问题,请参考以下文章
使用存储过程时出现 ASP.Net Core 3.1 MVC 错误
Asp .Net Core 3.1 Razor 页面:复杂模型绑定
如何在 ASP.NET Core 3.1 中使用 Java?