当前位置:网站首页>Avoid using asp Net core 3.0 to inject services for startup classes
Avoid using asp Net core 3.0 to inject services for startup classes
2022-06-12 22:39:00 【Wind god Shura envoy】
How to upgrade to ASP.NET Core 3.0 The second in a series .
In this blog , I will describe from ASP.NET Core 2.x Application upgrade to .NET Core 3.0 One modification that needs to be made : You don't need to be in Startup The service is injected into the constructor .
stay ASP.NET Core 3.0 Migrate to a common host
stay .NET Core 3.0 in , ASP.NET Core 3.0 The hosting foundation of has been redesigned as a general-purpose host , Instead of using it in parallel . So this is for those who are using ASP.NET Core 2.x Developers who develop applications , What does that mean ? At this stage , I have migrated multiple applications , up to now , Everything is going well . The official migration guidance document can guide you to complete the required steps , therefore , I strongly recommend that you read this document .
During the migration , The two most common problems I encounter are :
- ASP.NET Core 3.0 The recommended way to configure Middleware in is to use endpoint routing (
Endpoint Routing). - General purpose host is not allowed to be
StartupClass injection service
The first of these , I've explained it before . Endpoint routing (Endpoint Routing) Is in ASP.NET Core 2.2 Introduced in , But it's limited to MVC Use in . stay ASP.NET Core 3.0 in , Endpoint routing has been implemented by the recommended terminal middleware , Because it provides many benefits . The most important one is , It allows middleware to get which endpoint will eventually be executed , And you can retrieve metadata about this endpoint (metadata). for example , You can authorize the health check endpoint application .
Endpoint routing requires special attention when configuring middleware order . I suggest you upgrade your app before , First read the instructions in the official migration document , Later, I will write a blog to introduce how to convert terminal middleware into endpoint routing .
Second point , It's the service injection that has been mentioned Startup class , But not enough publicity . I'm not sure if it's because not many people do it , Still in some scenes , It's easy to solve . In this paper , I will show some problem scenarios , And provide some solutions .
ASP.NET Core 2.x Start the service injected into the class
stay ASP.NET Core 2.x In the version , There is a little-known feature , That's where you can be Program.cs Configure your dependency injection container in the file . I've used this method for strongly typed options before , Then use these configurations when configuring the rest of the dependency injection container .
So let's see ASP.NET Core 2.x Example :
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureSettings(); // Configure the service , The following will be in Startup Use in
}
Have you noticed here that CreateWebHostBuilder I have called one ConfigureSettings() Methods ? This is an extension method I use to configure and apply strongly typed options . for example , This extension method may look like this :
public static class SettingsinstallerExtensions
{
public static IWebHostBuilder ConfigureSettings(this IWebHostBuilder builder)
{
return builder.ConfigureServices((context, services) =>
{
var config = context.Configuration;
services.Configure<ConnectionStrings>(config.GetSection("ConnectionStrings"));
services.AddSingleton<ConnectionStrings>(
ctx => ctx.GetService<IOptions<ConnectionStrings>>().Value)
});
}
}
So here ,ConfigureSettings() Method is called IWebHostBuilder Example of ConfigureServices() Method , Some settings are configured . Because these services will be in Startup It is configured into the dependency injection container before initialization , So in Startup Class in the constructor , These configured services can be injected .
public static class Startup
{
public class Startup
{
public Startup(
IConfiguration configuration,
ConnectionStrings ConnectionStrings) // Inject preconfigured Services
{
Configuration = configuration;
ConnectionStrings = ConnectionStrings;
}
public IConfiguration Configuration {
get; }
public ConnectionStrings ConnectionStrings {
get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Use the connection string in the configuration
services.AddDbContext<BloggingContext>(options =>
options.UseSqlServer(ConnectionStrings.BloggingDatabase));
}
public void Configure(IApplicationBuilder app)
{
}
}
}
I find , When I first want to be in ConfigureServices When configuring other services with strongly typed option objects in the method , This model is very useful . In my example above ,ConnectionStrings Object is a strongly typed object , And this object is when the program enters Startup Before , Non null validation has been performed . This is not a formal basic technology , But real-time proof is very easy to use .
PS: How to ASP.NET Core Add validation for strongly typed option objects
However , If you switch to ASP.NET Core 3.0 After the universal host , You will find that this implementation will receive the following error message at run time .
Unhandled exception. System.InvalidOperationException: Unable to resolve service for type 'ExampleProject.ConnectionStrings' while attempting to activate 'ExampleProject.Startup'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services)
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass12_0.<UseStartup>b__0(HostBuilderContext context, IServiceCollection services)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at ExampleProject.Program.Main(String[] args) in C:\repos\ExampleProject\Program.cs:line 21
In this way ASP.NET Core 3.0 Is no longer supported in . You can Startup Class's constructor injection IHostEnvironment and IConfiguration, But that's all . As for the reason , It should be that the previous implementation will bring some problems , Now I will give you a detailed description .
Be careful : If you insist on ASP.NET Core 3.0 Use in
IWebHostBuilder, Instead of using a general-purpose host , You can still use the previous implementation . But I strongly advise you not to do this , And try to migrate to a common host as much as possible .
Two single cases ?
Inject services into Startup The fundamental problem with classes is , It will cause the system to build the dependency injection container twice . In the example I showed before ,ASP.NET Core Know you need one ConnectionStrings object , But the only way to know how to build this object is based on “ part ” The build IServiceProvider( In the previous example , We use ConfigureSettings() The extension method provides this “ part ” To configure ).
So why is this a problem ? The problem is this ServiceProvider It's a temporary one “ root ”ServiceProvider. It creates services and injects them into Startup in . then , The remaining dependency injection container configuration will be used as ConfigureServices Part of the method runs , And temporary ServiceProvider At this time, it has been discarded . Then a new ServiceProvider Will be created , It contains applications “ complete ” Configuration of .
such , Even if the service configuration uses Singleton Life cycle , It will also be created twice :
- When using “ part ”
ServiceProviderwhen , Created once , And aim atStartupInjected - When using " complete "
ServiceProviderwhen , Created once
For my use case , Strongly typed options , This may be irrelevant . The system can not only have one configuration instance , It's just a better choice . But this is not always the case . This kind of service “ Let the cat out of the ” It seems to be the main reason for changing the behavior of general-purpose hosts - It makes things look safer .
So if I need ConfigureServices What about the internal service ?
Although we can't configure the service as before , But we still need an alternative way to meet the needs of some scenes !
One of the most common scenarios is to inject services into Startup, in the light of Startup.ConfigureServices Method to control the state of other services registered in the . for example , The following is a very basic example .
public class Startup
{
public Startup(IdentitySettings identitySettings)
{
IdentitySettings = identitySettings;
}
public IdentitySettings IdentitySettings {
get; }
public void ConfigureServices(IServiceCollection services)
{
if(IdentitySettings.UseFakeIdentity)
{
services.AddScoped<IIdentityService, FakeIdentityService>();
}
else
{
services.AddScoped<IIdentityService, RealIdentityService>();
}
}
public void Configure(IApplicationBuilder app)
{
// ...
}
}
In this case , The code is injected by checking IdentitySettings Boolean property in object , To determine the IIdentityService Which implementation does the interface use to register : Or use fake Services , Or use services .
By transforming the static service registration into a factory function , Can make it necessary to inject IdentitySetting The implementation of the object is compatible with the general host . for example :
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration {
get; }
public void ConfigureServices(IServiceCollection services)
{
// Inject containers for dependencies , To configure IdentitySetting
services.Configure<IdentitySettings>(Configuration.GetSection("Identity"));
// Register different implementations
services.AddScoped<FakeIdentityService>();
services.AddScoped<RealIdentityService>();
// according to IdentitySetting To configure , Return a correct implementation at runtime
services.AddScoped<IIdentityService>(ctx =>
{
var identitySettings = ctx.GetRequiredService<IdentitySettings>();
return identitySettings.UseFakeIdentity ? ctx.GetRequiredService<FakeIdentityService>()
: ctx.GetRequiredService<RealIdentityService>();
}
});
}
public void Configure(IApplicationBuilder app)
{
// ...
}
}
This implementation is obviously much more complex than previous versions , But at least it can be compatible with the general-purpose host .
actually , If only one strongly typed option is required , Then this method is a little too much . Contrary , Here I may just rebind the configuration :
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration {
get; }
public void ConfigureServices(IServiceCollection services)
{
// Inject containers for dependencies , To configure IdentitySetting
services.Configure<IdentitySettings>(Configuration.GetSection("Identity"));
// Recreate strongly typed option objects , And bind the
var identitySettings = new IdentitySettings();
Configuration.GetSection("Identity").Bind(identitySettings)
// Configure the correct service according to the conditions
if(identitySettings.UseFakeIdentity)
{
services.AddScoped<IIdentityService, FakeIdentityService>();
}
else
{
services.AddScoped<IIdentityService, RealIdentityService>();
}
}
public void Configure(IApplicationBuilder app)
{
// ...
}
}
besides , If you only need to load a string from the configuration file , I may not use strongly typed options at all . This is a .NET Core Configuration in the default template ASP.NET Core The method of identity system - Directly through IConfiguration Instance to retrieve the connection string .
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration {
get; }
public void ConfigureServices(IServiceCollection services)
{
// For dependency injection containers , To configure ConnectionStrings
services.Configure<ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));
// Get the configuration directly , Do not use strongly typed options
var connectionString = Configuration["ConnectionString:BloggingDatabase"];
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
}
public void Configure(IApplicationBuilder app)
{
// ...
}
}
This implementation is not the best , But they can all meet our needs , And most of the scenes . If you didn't know before Startup Service injection features , Then you must have used one of the above methods .
Use IConfigureOptions Come on IdentityServer To configure
Another common scenario for using injection configuration is configuration IdentityServer Validation of the .
public class Startup
{
public Startup(IdentitySettings identitySettings)
{
IdentitySettings = identitySettings;
}
public IdentitySettings IdentitySettings {
get; }
public void ConfigureServices(IServiceCollection services)
{
// To configure IdentityServer Verification method of
services
.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// Use the strongly typed option to configure the authentication processor
options.Authority = identitySettings.ServerFullPath;
options.ApiName = identitySettings.ApiName;
});
}
public void Configure(IApplicationBuilder app)
{
// ...
}
}
In this case ,IdentityServer The basic address and address of the instance API Resource names are all through strongly typed options IdentitySettings Set up . This implementation is in .NET Core 3.0 Is no longer applicable , So we need an alternative solution . We can use the way mentioned before - Rebind strongly typed options or use directly IConfiguration Object retrieval configuration .
besides , The third option is to use IConfigureOptions, This is how I check AddIdentityServerAuthentication The underlying code of the method is found .
The fact proved that ,AddIdentityServerAuthentication() Methods can do different things . First , It's equipped with JWT Bearer verification , And the way of verification is specified through the strongly typed option . We can use it to delay the configuration of naming options (named options), Change to use IConfigureOptions example .
IConfigureOptions The interface allows you to use Service Provider Other dependencies in delay configuring strongly typed option objects . for example , If you want to configure my TestSettings The service , I need to call TestService A method in class , I can create one IConfigureOptions Object instances , The code is as follows :
public class MyTestSettingsConfigureOptions : IConfigureOptions<TestSettings>
{
private readonly TestService _testService;
public MyTestSettingsConfigureOptions(TestService testService)
{
_testService = testService;
}
public void Configure(TestSettings options)
{
options.MyTestValue = _testService.GetValue();
}
}
TestService and IConfigureOptions<TestSettings> It's all in Startup.ConfigureServices Method .
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<TestService>();
services.ConfigureOptions<MyTestSettingsConfigureOptions>();
}
The most important point here is , You can inject a dependency using the standard constructor IOptions<TestSettings> object . There is no need to be in ConfigureServices In the method “ Partial construction ”Service Provider, You can configure TestSettings. Contrary , We registered the configuration TestSettings The intent of the , But the real configuration will be postponed until the configuration object is used .
So this is for our configuration IdentityServer, What's the help ?
AddIdentityServerAuthentication A variant of the strongly typed option is used , We call this naming option (named options). This method is very common when verifying the configuration , Like the example above .
In short , You can use IConfigureOptions Method will validate the naming options used by the handler IdentityServerAuthenticationOptions Configuration delay . therefore , You can create one that will IdentitySettings As a construction parameter ConfigureIdentityServerOptions object .
public class ConfigureIdentityServerOptions : IConfigureNamedOptions<IdentityServerAuthenticationOptions>
{
readonly IdentitySettings _identitySettings;
public ConfigureIdentityServerOptions(IdentitySettings identitySettings)
{
_identitySettings = identitySettings;
_hostingEnvironment = hostingEnvironment;
}
public void Configure(string name, IdentityServerAuthenticationOptions options)
{
// Only configure the options if this is the correct instance
if (name == IdentityServerAuthenticationDefaults.AuthenticationScheme)
{
// Use strong type IdentitySettings Value in object
options.Authority = _identitySettings.ServerFullPath;
options.ApiName = _identitySettings.ApiName;
}
}
// This won't be called, but is required for the IConfigureNamedOptions interface
public void Configure(IdentityServerAuthenticationOptions options) => Configure(Options.DefaultName, options);
}
stay Startup.cs In file , You need to configure strong typing IdentitySettings object , Add the required IdentityServer service , And register ConfigureIdentityServerOptions class , So that when needed , It can be configured IdentityServerAuthenticationOptions.
public void ConfigureServices(IServiceCollection services)
{
// Configure strong typing IdentitySettings Options
services.Configure<IdentitySettings>(Configuration.GetSection("Identity"));
// To configure IdentityServer Verification mode
services
.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication();
// Add other configurations
services.ConfigureOptions<ConfigureIdentityServerOptions>();
}
here , We don't have to ask Startup Inject anything into the class , But you can still get the benefits of strong typing options . So here we get a win-win result .
summary
In this paper , I described upgrading to ASP.NET Core 3.0 when , Yes, you need to be right Startup Class . I passed the Startup Class , It describes ASP.NET Core 2.x Problems in , And how to ASP.NET Core 3.0 Remove this feature from . Finally, I showed , Change how to do it when you need this implementation .
边栏推荐
- Redis optimization
- 在同花顺开户证券安全吗,买股票怎么网上开户
- 【LeetCode】69. x 的平方根
- How to perform disaster recovery and recovery for kubernetes cluster? (22)
- 42岁大厂高管,给30岁-39岁人提个醒:这6个让你变强的习惯,要尽快养成
- 设计消息队列存储消息数据的 MySQL 表格
- Global and Chinese Melamine Industry Development Research and prospect trend report 2022-2028
- [machine learning] learning notes 01- introduction
- 【LeetCode】53.最大子数组和
- 数字藏品的发展趋势!
猜你喜欢

C language: how to give an alias to a global variable?

JVM foundation > CMS garbage collector
![[Part 7] source code analysis and application details of cyclicbarrier [key]](/img/bc/8ba2b86e599539a29683a63d02f0f7.jpg)
[Part 7] source code analysis and application details of cyclicbarrier [key]

【数据分析】基于 kmeans实现数据聚类分组含Matlab源码

管线中的坐标变换

Hostvars in ansible

JVM foundation - > three ⾊ mark

leetcodeSQL:574. Elected

NoSQL - redis configuration and optimization (II) high availability, persistence and performance management

iShot
随机推荐
[proteus simulation] simple digital tube timer clock
[Part 7] source code analysis and application details of cyclicbarrier [key]
A 42 year old senior executive of a large factory reminds people aged 30-39 that these six habits that make you stronger should be developed as soon as possible
Research and Analysis on the development of China's Melamine Industry from 2022 to 2028 and market prospect forecast report
【LeetCode】103. 二叉树的锯齿形层序遍历
JVM foundation > GC generation: minorgc majorgc fullgc mixed GC
JVM foundation - what is the process of loading > objects into the JVM, and then clearing them by GC?
Market trend report, technical innovation and market forecast of Chinese stump crusher
Design a MySQL table for message queue to store message data
接口测试工具apipost3.0版本对于流程测试和引用参数变量
認識的幾比特清華同學都離職了……
Is it safe to open an account in tonghuashun? How to open an account for securities
[web technology] 1348- talk about several ways to implement watermarking
IPhone: save Boolean into core data - iphone: save Boolean into core data
Su embedded training day13 - file IO
【LeetCode】剑指 Offer II 020. 回文子字符串的个数
Analysis report on production and marketing demand and investment forecast of China's Melamine Industry from 2022 to 2028
C语言:如何给全局变量起一个别名?
Afraid to write documents? AI plug-in for automatically generating code documents
Qt Quick 3D学习:鼠标拾取物体