当前位置:网站首页>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 .
边栏推荐
- 项目里面的traceID的设计
- China's elastic belt market trend report, technical dynamic innovation and market forecast
- Anti aliasing / anti aliasing Technology
- [890. find and replace mode]
- Alcohol detector based on 51 single chip microcomputer
- Research Report on market supply and demand and strategy of China's digital camera lens industry
- China barcode decoder market trend report, technical innovation and market forecast
- Several Tsinghua students I know have left
- Is it safe to open an account with new bonds? How should novices operate?
- Shardingsphere-proxy-5.0.0 deployment table implementation (I)
猜你喜欢
![[data analysis] data clustering and grouping based on kmeans, including Matlab source code](/img/76/deec6cf60c0d02e99ebc3e21d3b8a4.png)
[data analysis] data clustering and grouping based on kmeans, including Matlab source code

Database daily question --- day 10: combine two tables

JVM foundation - > talk about class loader two parent delegation model

Inventory of CV neural network models from 2021 to 2022

数字藏品的发展趋势!

Audio and video technology development weekly 𞓜 234

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

LNMP platform docking redis service

(downloadable) Research Report on the development and utilization of government data (2021), a glimpse of the development of Government Office

80 lines of code to realize simple rxjs
随机推荐
Research Report on market supply and demand and strategy of China's digital camera lens industry
QT quick 3D learning: mouse picking up objects
【数据分析】基于 kmeans实现数据聚类分组含Matlab源码
Plusieurs camarades de classe de Tsinghua sont partis...
【890. 查找和替换模式】
Create a virtual thread using loom - David
JVM foundation - > three ⾊ mark
管线中的坐标变换
【LeetCode】103. Zigzag sequence traversal of binary tree
The shutter library recommends sizer to help you easily create a responsive UI
[890. find and replace mode]
数字藏品的发展趋势!
Common rendering pipeline grooming
Database system composition
Is there any risk in opening a securities account? How to open an account safely?
Colab教程(超级详细版)及Colab Pro/Colab Pro+使用评测
[data analysis] data clustering and grouping based on kmeans, including Matlab source code
Analysis report on investment and development trend of gap base of Chinese traditional medicine 2022 ~ 2028
设计消息队列存储消息数据的 MySQL 表格
iShot