当前位置:网站首页>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
Startup
Class 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 ”
ServiceProvider
when , Created once , And aim atStartup
Injected - When using " complete "
ServiceProvider
when , 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 .
边栏推荐
- 同花顺股票账户开户安全吗
- 【LeetCode】69. Square root of X
- 证券开户有风险吗?怎么开户安全呢?
- Report on the "fourteenth five year plan" and strategic strategy recommendations for China's intellectual property protection industry 2022 ~ 2028
- 【LeetCode】300. Longest ascending subsequence
- Qt Quick 3D学习:使用鼠标键盘控制节点位置和方向
- 数字藏品的发展趋势!
- Flutter库推荐Sizer 可帮助您轻松创建响应式 UI
- 【Web技术】1348- 聊聊水印实现的几种方式
- 设计消息队列存储消息数据的 MySQL 表格
猜你喜欢
JVM foundation - > talk about class loader two parent delegation model
反走样/抗锯齿技术
C#读取word中表格数据
The development trend of digital collections!
管线中的坐标变换
设计消息队列存储消息数据的 MySQL 表格
LNMP platform docking redis service
Qrcodejs2 QR code generation JS
Hostvars in ansible
NoSQL - redis configuration and optimization (II) high availability, persistence and performance management
随机推荐
Leetcode: the maximum number of building change requests that can be reached (if you see the amount of data, you should be mindless)
JVM Basics - > What are the JVM parameters?
Research Report on truffle fungus industry - market status analysis and development prospect forecast
Shardingsphere-proxy-5.0.0 deployment table implementation (I)
Pat grade A - 1167 Cartesian tree (30 points) (buildtree + level traversal)
[machine learning] learning notes 01- introduction
【LeetCode】102. 二叉树的层序遍历
LNMP platform docking redis service
【Web技术】1348- 聊聊水印实现的几种方式
Afraid to write documents? AI plug-in for automatically generating code documents
年薪50万是一条线,年薪100万又是一条线…...
Analysis report on business model innovation path and operation status of China's app store industry from 2022 to 2028
2022-02-28 incluxdb high availability planning
iShot
[Part 8] semaphore source code analysis and application details [key points]
Generate the chrysanthemum code of the applet (generate the chrysanthemum code, change the middle logo, change the image size, and add text)
【LeetCode】69. Square root of X
【LeetCode】300.最长上升子序列
be careful! Your Navicat may have been poisoned
How to perform disaster recovery and recovery for kubernetes cluster? (22)