当前位置:网站首页>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 .
边栏推荐
- Zabbix的功能介绍和常用术语
- Qt Quick 3D学习:鼠标拾取物体
- C语言:如何给全局变量起一个别名?
- C # reading table data in word
- Is it safe to open an account in tonghuashun? How to open an account for securities
- 【LeetCode】33. Search rotation sort array
- Leetcode Yanghui triangle
- Sword finger offer series - 47 Maximum value of gifts
- List of open source alternative projects of world famous Cloud Service SaaS companies
- JVM foundation - > three ⾊ mark
猜你喜欢

Hostvars in ansible

Photoshop:PS如何实现放大图片不模糊

Mysql concat_ws、concat函数使用

基于51单片机的酒精检测仪

Coordinate transformation in pipelines

flutter系列之:flutter中常用的GridView layout详解
![[image denoising] image denoising based on trilateral filter with matlab code](/img/f2/770a0e2938728e731c18c0a66f7c12.png)
[image denoising] image denoising based on trilateral filter with matlab code

IPhone: save Boolean into core data - iphone: save Boolean into core data

Yyds dry inventory insider news: Series high-frequency interview questions, worth a visit!

LNMP platform docking redis service
随机推荐
How to specify your webpage's language so Google Chrome doesn't offer to translate it
QT quick 3D learning: mouse picking up objects
Design a MySQL table for message queue to store message data
China's new generation information technology industry "14th five year plan" special planning and innovation strategic direction report 2022 ~ 2028
Mysql concat_ WS, concat function use
[leetcode] the k-largest element in the array
设计消息队列存储消息数据的 MySQL 表格
LeetCode —— 26. Remove duplicates from an ordered array
细数攻防演练中十大关键防守点
接口测试工具apipost3.0版本对于流程测试和引用参数变量
Create a virtual thread using loom - David
C语言:如何给全局变量起一个别名?
vim利用右下4键
3.5 测试类的setup和teardown
[web technology] 1348- talk about several ways to implement watermarking
Flutter series part: detailed explanation of GridView layout commonly used in flutter
Is there any risk in opening a securities account? How to open an account safely?
【LeetCode】剑指 Offer II 020. 回文子字符串的个数
Leetcode Yanghui triangle
IPhone: save Boolean into core data - iphone: save Boolean into core data