当前位置:网站首页>Multiple solutions to one problem, asp Net core application startup initialization n schemes [Part 1]

Multiple solutions to one problem, asp Net core application startup initialization n schemes [Part 1]

2022-07-05 09:04:00 Artech

ASP.NET Core Application is essentially a pipeline composed of middleware , The hosting system will host the application in a managed process to run , Its core task is to build this pipeline . stay ASP.NET Core In the history of development, there have been three programming methods of application hosting , And the latter programming mode provides full or partial compatibility for the previous programming mode , This leads to a phenomenon : The same can have N Type of implementation . Readers who are not particularly familiar with this development process will have many questions ? Why are so many different programming modes doing the same thing ? What is the difference between them ? Why there are API In the latest Minimal API It doesn't work anymore ?[ Part of the content of this paper comes from 《ASP.NET Core 6 The framework reveals 》 The first 15 Chapter ]

Catalog
One 、 What initialization work is required in the application hosting process ?
Two 、 The first generation application bearing model
     Basic programming mode
     Use environment variables and command line parameters
    Hosting environment setting method
    Use Startup type
3、 ... and 、 The second generation application bearing model
     Basic programming mode
     Hosting environment setting method
     in the light of IWebHostBuilder The adaptation of
    Startup Limitations of constructor injection

One 、 What initialization work is required in the application hosting process ?

What we call application hosting (Hosting) This is a ASP.NET Core Applied in a specific process (Self-Host process 、IIS Work process or Windows Service Process, etc ) The process started in , In this process, we need to use the provided API Complete some necessary initialization . because ASP.NET Core Application is essentially a pipeline composed of middleware , The purpose of the whole initialization process is to build this middleware pipeline , It's no exaggeration to say , The built middleware pipeline is “ application ” In itself , therefore “ Middleware registration ” Is the core initialization work . Due to the wide application of dependency injection , The functions of middleware basically depend on the injected Services , So registering dependent services to the dependency injection framework is another core initialization .

As with any type of application ,ASP.NET Core It also needs to dynamically change its runtime behavior through configuration , Therefore, the settings for configuration are also indispensable . One ASP.NET Core The configuration of the application is divided into two categories , One is used in the process of building middleware pipelines , That is, in the process of application hosting , Let's call this “ Bearer configuration (Hosting Configuration)”. Another kind of configuration is used to control the behavior of the middleware pipeline to process requests , As mentioned above , The middleware pipeline is the application itself , So this kind of configuration is called application configuration (App Configuration). There is an important component in the bearer configuration , That is to describe the current hosting environment (Hosting Environment), For example, the logo of the application 、 The name of the deployment environment 、 Store content files and Web Directory of resources, etc . The hosting configuration will eventually be incorporated into the application configuration .

To sum up ,ASP.NET Core The application hosted programming model mainly completes the following initialization work , These jobs all have N There are ways to do it . In the following content , We will introduce three different application hosting methods one by one , What are the implementation methods of these functions .

  • Middleware registration
  • Service registration
  • Settings of hosting configuration
  • Apply the configured settings
  • Settings of hosting environment

Two 、 The first generation application bearing model

ASP.NET Core 1.X/2.X The adopted bearing model is shown in the figure below IWebHostBuilder and IWebHost At the core .IWebHost Object represents the bearer Web Host of application (Host), Pipeline with IWebHost The startup of the object is built .IWebHostBuilder Object as the builder of the host object , Our settings for pipeline construction are applied to it .

image

Basic programming mode

Now we will focus on the above 5 Three initialization settings are put in a simple demonstration example . The demo example will register the following FoobarMiddleware middleware , The latter utilizes injected IHandler The service completes the processing of the request . As IHandler The default implementation type of the interface ,Handler Using constructor injection IOptions<FoobarbazOptions> Object gets configuration options FoobarbazOptions, And take its contents as the response to the request .

public class FoobarMiddleware
{
    private readonly RequestDelegate _next;
    public FoobarMiddleware(RequestDelegate _) { }
    public Task InvokeAsync(HttpContext httpContext, IHandler handler) => handler.InvokeAsync(httpContext);
}

public interface IHandler
{
    Task InvokeAsync(HttpContext httpContext);
}

public class Handler : IHandler
{
    private readonly FoobarbazOptions _options;
    private readonly IWebHostEnvironment _environment;

    public Handler(IOptions<FoobarbazOptions> optionsAccessor, IWebHostEnvironment environment)
    {
        _options = optionsAccessor.Value;
        _environment = environment;
    }

    public Task InvokeAsync(HttpContext httpContext)
    {
        var payload = @$"
Environment.ApplicationName: {_environment.ApplicationName}
Environment.EnvironmentName: {_environment.EnvironmentName}
Environment.ContentRootPath: {_environment.ContentRootPath}
Environment.WebRootPath: {_environment.WebRootPath}
Foo: {_options.Foo}
Bar: {_options.Bar}
Baz: {_options.Baz}
";
        return httpContext.Response.WriteAsync(payload);
    }
}

public class FoobarbazOptions
{
    public string Foo { get; set; } = default!;
    public string Bar { get; set; } = default!;
    public string Baz { get; set; } = default!;
}

We will make use of the current “ Hosting environment ” Bind the configuration options according to the configuration FoobarbazOptions, The three properties of the latter are derived from three independent configuration files . among settings.json Shared by all environments ,settings.dev.json For a “dev” Development environment of . We provide higher requirements for the hosting environment , Divide sub environments on the basis of environment ,settings.dev.dev1.json It's aimed at dev Sub environment under dev1. Setting the sub environment requires the use of the above-mentioned bearer configuration .

image

The following is the content of the above three configuration files . If the current environment and sub environment are dev and dev1, Then configuration options FoobarbazOptions The content of will come from these three configuration files . Careful friends may also notice : We didn't put it under the default root directory , But put it in the created resources Under the table of contents , This is because we need to take advantage of setting changes for the hosting environment ASP.NET Core Applications store content files and Web The root directory of the resource file .

settings.json
{
  "Foo": "123"
}

settings.dev.json
{
  "Bar": "abc"
}

settings.dev.dev1.json
{
  "Baz": "xyz"
}

The following application hosting program covers the above 5 Initialization operation . The registration of middleware is realized by calling IWebHostBuilder Of Configure Method to accomplish , The parameter type of this method is Action<IApplicationBuilder>, Middleware is through calling UseMiddleware<TMiddleware> Method to IApplicationBuilder On the object .IWebHostBuilder There is no specific method defined for the bearer configuration , But we can use it UseSettings Method to set it in the form of key value pairs , Here we use this method to complete the “ Environmental Science ”、“ Content file root directory ”、“Web Resource file root directory ” and “ Sub environment ” Set up , The first three are “ Hosting environment ” Three important attributes of . The hosting configuration will eventually be reflected in the WebHostBuilderContext On the object .

using App;
new WebHostBuilder()
    .UseKestrel()
    .UseSetting(WebHostDefaults.EnvironmentKey,"dev")
    .UseSetting(WebHostDefaults.ContentRootKey , Path.Combine(Directory.GetCurrentDirectory(), "resources"))
    .UseSetting(WebHostDefaults.WebRootKey, Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"))
    .UseSetting("SubEnvironment", "dev1")
    .ConfigureAppConfiguration((context, configBuilder) => configBuilder
            .AddJsonFile(path: "settings.json", optional: false)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true))
    .ConfigureServices((context, services) => services
        .AddSingleton<IHandler, Handler>()
        .Configure<FoobarbazOptions>(context.Configuration))
    .Configure(app => app.UseMiddleware<FoobarMiddleware>())
    .Build()
    .Run();

Rely on service utilization IWebHostBuilder Of ConfigureServices Method to register , The parameter type of this method is Action<WebHostBuilderContext, IServiceCollection>, It means that we can configure the bearer for the previously provided ( For example, the hosting environment ) Register targeted services . Here we not only register dependent services Handler, Also use the current configuration to configure options FoobarbazOptions Implemented binding . Application configuration through special methods ConfigureAppConfiguration Set it up , The parameter type of this method is Action<WebHostBuilderContext, IConfigurationBuilder>, It means that the bearer configuration can still be used WebHostBuilderContext Context acquisition , Here we use it to get three configuration files matching the current environment . After the program starts , The request can get the following response .

image

Use environment variables and command line parameters

because ASP.NET Core When the application starts, it will use the prefix “ASPNETCORE_” The environment variable of is used as the bearer configuration , So the use of UseSettings Methods the settings for the bearer configuration can be replaced by environment variables in the following way .

Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "dev");
Environment.SetEnvironmentVariable("ASPNETCORE_SUBENVIRONMENT", "dev1");
Environment.SetEnvironmentVariable("ASPNETCORE_CONTENTROOT", Path.Combine(Directory.GetCurrentDirectory(), "resources"));
Environment.SetEnvironmentVariable("ASPNETCORE_WEBROOT", Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"));

WebHost.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((context, configBuilder) => configBuilder
            .AddJsonFile(path: "settings.json", optional: false)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true))
    .ConfigureServices((context, services) => services
        .AddSingleton<IHandler, Handler>()
        .Configure<FoobarbazOptions>(context.Configuration))
    .Configure(app => app.UseMiddleware<FoobarMiddleware>())
    .Build()
    .Run();

The above code snippet is not created directly WebHostBuilder object , But the call WebHost Static method of CreateDefaultBuilder Method creates a IWebHostBuilder object . Because the method passes in command line parameters args, It will use command line parameters as one of the hosting configuration sources , Therefore, the four load configuration options in the program can also be completed by using command line parameters .

Hosting environment setting method

In fact, the hosting environment ( Name of the environment 、 Content file root directory and Web Resource file root directory ) Have a special method , So the most convenient way is to call these methods directly in the following way to set them . For our example , For environment name 、 Content files and Web The setting of resource file root directory can be directly called IWebHostBuilder Of UseEnvironment、UseContentRoot and UseWebRoot Extend the method to complete .

WebHost.CreateDefaultBuilder(args)
    .UseEnvironment("dev")
    .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources"))
    .UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"))
    .UseSetting("SubEnvironment", "dev1")
    .ConfigureAppConfiguration((context, configBuilder) => configBuilder
            .AddJsonFile(path: "settings.json", optional: false)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true))
    .ConfigureServices((context, services) => services
        .AddSingleton<IHandler, Handler>()
        .Configure<FoobarbazOptions>(context.Configuration))
    .Configure(app => app.UseMiddleware<FoobarMiddleware>())
    .Build()
    .Run();

Use Startup type

In order not to make the application hosting program code appear too bloated , We usually move the service registration and middleware registration to the one defined according to the Convention Startup Type in the . This is shown in the following code snippet , Middleware and service registration are implemented in Startup Type of ConfigureServices and Configure In the method , We inject directly into the constructor IConfiguration Object gets the hosting configuration object . It is worth mentioning , For the first generation of application bearer , We can do it in Startup The constructor of type is injected by calling IWebHostBuilder Of ConfigureServices Method to register any service ( Include ASP.NET Core Services registered internally by calling this method , For example, in this case IConfiguration object ).Startup Type only needs to call IWebHostBuilder Of UseStartup<TStartup> The extension method can be registered .

WebHost.CreateDefaultBuilder(args)
    .UseEnvironment("dev")
    .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources"))
    .UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"))
    .UseSetting("SubEnvironment", "dev1")
    .ConfigureAppConfiguration((context, configBuilder) => configBuilder
            .AddJsonFile(path: "settings.json", optional: false)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true))
    .UseStartup<Startup>()
    .Build()
    .Run();

public class Startup
{
    public IConfiguration Configuration { get; }
    public Startup(IConfiguration configuration) => Configuration = configuration;
    public void ConfigureServices(IServiceCollection services) => services
        .AddSingleton<IHandler, Handler>()
        .Configure<FoobarbazOptions>(Configuration);
    public void Configure(IApplicationBuilder app) => app.UseMiddleware<FoobarMiddleware>();
}

3、 ... and 、 The second generation application bearing model

In addition to carrying Web application , We also have a lot of background services ( For example, many batch tasks ) The bearing requirements of , To this end, Microsoft has launched a series of IHostBuilder/IHost For the core service hosting system .Web The application itself is actually a long-running background service , We can define an application as IHostedService service , This type is shown in the following figure GenericWebHostService. If the above is called the first generation application bearer mode , This is the second generation bearer mode .

image

Basic programming mode

And all the Builder Model as , most API All fall in the role of the builder IHostBuilder On the interface , Service registration 、 Bearer configuration 、 Application configurations have corresponding methods . Because the middleware belongs to GenericWebHostService This single hosting service , So I can only remember and IWebHostBuilder. If the second generation application bearing model is adopted , The program demonstrated above can be rewritten into the following form .

Host.CreateDefaultBuilder()
    .ConfigureHostConfiguration(config => config.AddInMemoryCollection(new Dictionary<string, string> {
        [WebHostDefaults.EnvironmentKey] = "dev",
        [WebHostDefaults.ContentRootKey] = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
        [WebHostDefaults.WebRootKey] = Path.Combine(Directory.GetCurrentDirectory(), "resources","web"),
        ["SubEnvironment"] = "dev1"
    }))
    .ConfigureAppConfiguration((context, configBuilder) => configBuilder
            .AddJsonFile(path: "settings.json", optional: false)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true))
    .ConfigureServices((context,services) => services
        .AddSingleton<IHandler, Handler>()
        .Configure<FoobarbazOptions>(context.Configuration))
    .ConfigureWebHost(webHostBuilder => webHostBuilder.Configure(app=>app.UseMiddleware<FoobarMiddleware>()))
    .Build()
    .Run();

This is shown in the code snippet above , We call Host Static method of CreateDefaultBuilder Method to create a with default configuration IHostBuidler object .IHostBuilder It provides independent ConfigureHostConfiguration Method , The parameter type of this method is Action<IConfigurationBuilder>, The example we demonstrate uses this method to register a configuration source based on memory dictionary , Hosting environment ( Name of the environment 、 Content files and Web Resource file root directory ) And sub environment names are set here . The settings for application configuration are set through ConfigureAppConfiguration Method to accomplish , The parameter type of this method is Action<HostBuilderContext, IConfigurationBuilder>, Represents HostBuilderContext You can get the preset hosting environment and hosting configuration , Our example takes advantage of locating the configuration file that matches the current environment .

IHostBuilder It's also defined ConfigureServices Method , The parameter type of this method is Action<HostBuilderContext, IServiceCollection>, It means that the service can still be registered for the hosting environment and hosting configuration . Because the registration of middleware still falls in IWebHostBuilder On , therefore IHostBuilder Provides ConfigureWebHost/ConfigureWebHostDefaults These two extension methods are adapted , They have a type of Action<IWebHostBuilder> Parameters of .

Hosting environment setting method

and IWebHostBuilder equally ,IHostBuidler It also provides a method for directly setting up the hosting environment . For our example , For environment name 、 Content files and Web The setting of resource file root directory can be directly called IHostBuidler Of UseEnvironment、UseContentRoot and UseWebRoot Extend the method to complete . because Web The resource file does not “ Service bearing ” The category of , So for Web The setting of resource file root directory must also adopt the method of directly setting the hosting configuration ( Or call IWebHostBuilder Of UseWebRoot Extension method ).

Host.CreateDefaultBuilder()
    .UseEnvironment("dev")
    .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources"))
    .ConfigureHostConfiguration(config => config.AddInMemoryCollection(new Dictionary<string, string> {
        [WebHostDefaults.WebRootKey] = Path.Combine(Directory.GetCurrentDirectory(), "resources","web"),
        ["SubEnvironment"] = "dev1"
    }))
    .ConfigureAppConfiguration((context, configBuilder) => configBuilder
            .AddJsonFile(path: "settings.json", optional: false)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true))
    .ConfigureServices((context,services) => services
        .AddSingleton<IHandler, Handler>()
        .Configure<FoobarbazOptions>(context.Configuration))
    .ConfigureWebHost(webHostBuilder => webHostBuilder.Configure(app=>app.UseMiddleware<FoobarMiddleware>()))
    .Build()
    .Run();

in the light of IWebHostBuilder The adaptation of

because IHostBuilder Use the extension method ConfigureWebHost/ConfigureWebHostDefaults For IWebHostBuilder The adaptation of , It means that the code previously written using the first generation application hosting method can be directly transplanted . This is shown in the following code snippet , Static methods ConfigureWebHost Completely still use IWebHostBuilder Complete all initialization work , We just need to point to the method Action<IWebHostBuilder> Delegate incoming IHostBuilder Of ConfigureWebHostDefaults Just extend the method .

Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(ConfigureWebHost)
    .Build()
    .Run();

static void ConfigureWebHost(IWebHostBuilder webHostBuilder)
{
    webHostBuilder.UseEnvironment("dev")
        .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources"))
        .UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"))
        .UseSetting("SubEnvironment", "dev1")
        .ConfigureAppConfiguration((context, configBuilder) => configBuilder
            .AddJsonFile(path: "settings.json", optional: false)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true))
        .UseStartup<Startup>();
}

public class Startup
{
    public IConfiguration Configuration { get; }
    public Startup(IConfiguration configuration) => Configuration = configuration;
    public void ConfigureServices(IServiceCollection services) => services
        .AddSingleton<IHandler, Handler>()
        .Configure<FoobarbazOptions>(Configuration);
    public void Configure(IApplicationBuilder app) => app.UseMiddleware<FoobarMiddleware>();
}

Startup Limitations of constructor injection

The second generation application bearer model utilizes ConfigureWebHost/ConfigureWebHostDefaults The extension method pair was previously defined in IWebHostBuilder Upper API( Most of them are extension methods ) Provides 100% Support for ( except Build Method ), But for Startup The services injected into the constructor are no longer so free . If based on IWebHostBuilder/IWebHost Application bearing mode , By calling IWebHostBuilder Of ConfigureServices Methods registered services can be injected Startup In the constructor of , If based on IHostBuilder/IHost Application bearing mode , Only with “ Bearer configuration ( The hosting environment is part of the hosting configuration )” The following three related services can be injected into Startup In the constructor of .

  • IHostingEnvironment
  • IWebHostEnvironment
  • IHostEnvironment
  • IConfiguration

For the following code , Although injected Startup Constructor's Foobar At the same time, by calling IHostBuilder and IWebHostBuilder Of ConfigureServices Method , But creating Startup The instance will still throw an exception .

Host.CreateDefaultBuilder(args)
    .ConfigureServices(sevices=>sevices.AddSingleton<Foobar>())
    .ConfigureWebHostDefaults(ConfigureWebHost)
    .Build()
    .Run();

static void ConfigureWebHost(IWebHostBuilder webHostBuilder)
{
    webHostBuilder.UseEnvironment("dev")
        .ConfigureServices(sevices => sevices.AddSingleton<Foobar>())
        .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources"))
        .UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"))
        .UseSetting("SubEnvironment", "dev1")
        .ConfigureAppConfiguration((context, configBuilder) => configBuilder
            .AddJsonFile(path: "settings.json", optional: false)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
            .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true))
        .UseStartup<Startup>();
}

public class Startup
{
    public IConfiguration Configuration { get; }
    public Startup(IConfiguration configuration,Foobar foobar) => Configuration = configuration;
    public void ConfigureServices(IServiceCollection services) => services
        .AddSingleton<IHandler, Handler>()
        .Configure<FoobarbazOptions>(Configuration);
    public void Configure(IApplicationBuilder app) => app.UseMiddleware<FoobarMiddleware>();
}

public class Foobar
{ }

in summary , The original version of ASP.NET Core Because only considering Web The hosting of the application itself , So the design is based on IWebHostBuilder/IWebHost Model . Later, there was a demand based on background service bearer , Therefore, based on IHostBuilder/IHost Service hosting model , The original Web Apply as a “ Background services (GenericWebHostService.)” Carry on the load . Because there were a lot of API All fall on IWebHostBuilder( There are mainly countless extension methods ), For compatibility , A group called GenericWebHostBuilder The implementation type of is defined , It will target IWebHostBuilder Method call to IHostBuilder/IHost In the service hosting model of .

.NET 6 stay IHostBuilder/IHost Based on the service bearer model, a more concise Minimal API, At this time, we face the same “ Choose ”. This time it not only needs to be compatible IWebHostBuilder, It has to be compatible with IHostBuilder, On the plus Minimal API Self provided API, therefore “ Solve more than one problem ” There are more phenomena . If you are right about ASP.NET Core I don't know much about the history of , Will feel very confused . When you are more confused , This is defined in IWebHostBuilder and IHostBuilder Of API Not all available , The next part of this article will solve your doubts one by one .

原网站

版权声明
本文为[Artech]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/186/202207050902105930.html