兰州网站建设设计,网站建设不开单,wordpress 浏览次数,企业seo外包公司ASP.NET Core应用本质上就是一个由中间件构成的管道#xff0c;承载系统将应用承载于一个托管进程中运行起来#xff0c;其核心任务就是将这个管道构建起来。在ASP.NET Core的发展历史上先后出现了三种应用承载的编程方式#xff0c;而且后一种编程模式都提供了针对之前编程…ASP.NET Core应用本质上就是一个由中间件构成的管道承载系统将应用承载于一个托管进程中运行起来其核心任务就是将这个管道构建起来。在ASP.NET Core的发展历史上先后出现了三种应用承载的编程方式而且后一种编程模式都提供了针对之前编程模式的全部或者部分兼容这就导致了一种现象相同的功能具有N种实现方式。对这个发展历程不是特别了解的读者会有很多疑问为什么这么多不同的编程模式都在做同一件事它们之间的有什么差别之处为什么有的API在最新的Minimal API又不能用了呢[本文部分内容来源于《ASP.NET Core 6框架揭秘》第15章]目录一、应用承载过程中需要哪些初始化工作二、第一代应用承载模型 基本编程模式 利用环境变量和命令行参数 承载环境设置方法 使用Startup类型三、第二代应用承载模型 基本编程模式 承载环境设置方法 针对IWebHostBuilder的适配 Startup构造函数注入的限制一、应用承载过程中需要哪些初始化工作我们所谓的应用承载Hosting本就是将一个ASP.NET Core应用在一个具体的进程Self-Host进程、IIS工作进程或者Windows Service进程等中被启动的过程在这个过程中需要利用提供的API完成一些必要的初始化工作。由于ASP.NET Core应用本质上就是一个由中间件构成的管道所有整个初始化过程的目的就是为了构建这一中间件管道毫不夸张地说构建的中间件管道就是“应用”本身所以“中间件注册”是最为核心的初始化工作。由于依赖注入的广泛应用中间件的功能基本都依赖于注入的服务来完成所以将依赖服务注册到依赖注入框架是另一项核心的初始化工作。和任何类型的应用一样ASP.NET Core同样需要通过配置来动态改变其运行时行为所以针对配置的设置也是并不可少的。一个ASP.NET Core应用的配置分为两类一种是用在中间件管道构建过程中也就是应用承载过程中我们将其称为“承载配置Hosting Configuration”。另一类配置则被用来控制中间件管道处理请求的行为正如上面所说中间件管道就是应用本身所以这类配置被称为应用配置App Configuration。承载配置中有一个重要的组成部分那就是描述当前的承载环境Hosting Environment,比如应用的标识、部署环境的名称、存放内容文件和Web资源的目录等。承载配置最终会合并到应用配置中。综上所示ASP.NET Core应用承载的编程模型主要完成如下几种初始化工作这些工作都具有N种实现方法。在接下来的内容中我们将逐个介绍在三种不同的应用承载方式中这些功能都有哪些实现方式。中间件注册服务注册承载配置的设置应用配置的设置承载环境的设置二、第一代应用承载模型ASP.NET Core 1.X/2.X采用的承载模型以如下图所示的IWebHostBuilder和IWebHost为核心。IWebHost对象代表承载Web应用的宿主Host管道随着IWebHost对象的启动被构建出来。IWebHostBuilder对象作为宿主对象的构建者我们针对管道构建的设置都应用在它上面。基本编程模式现在我们将针对上述5种初始化设置放在一个简单的演示实例中。该演示实例会注册如下这个FoobarMiddleware中间件后者利用注入的IHandler服务完成请求的处理工作。作为IHandler接口的默认实现类型Handler利用构造函数注入的IOptionsFoobarbazOptions对象得到配置选项FoobarbazOptions并将其内容作为请求的响应。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(IOptionsFoobarbazOptions 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!;
}我们会利用与当前“承载环境”对应配置来绑定配置选项FoobarbazOptions后者的三个属性分别来源于三个独立的配置文件。其中settings.json被所有环境共享settings.dev.json针对名为“dev”的开发环境。我们为承载环境提供更高的要求在环境基础上进步划分子环境settings.dev.dev1.json针对的就是dev下的子环境dev1。针对子环境的设置需要利用上述的承载配置来提供。如下所示的就是上述三个配置文件的内容。如果当前环境和子环境分别为dev和dev1那么配置选项FoobarbazOptions的内容将来源于这三个配置文件。细心的朋友可能还注意到了我们并没有放在默认的根目录下而是放在创建的resources目录下这是因为我们需要利用针对承载环境的设置改变ASP.NET Core应用存放内容文件和Web资源文件的根目录。settings.json
{Foo: 123
}settings.dev.json
{Bar: abc
}settings.dev.dev1.json
{Baz: xyz
}如下的应用承载程序涵盖了上述的5种初始化操作。中间件的注册通过调用IWebHostBuilder的Configure方法来完成该方法的参数类型为ActionIApplicationBuilder中间件就是通过调用UseMiddlewareTMiddleware方法注册到IApplicationBuilder对象上。IWebHostBuilder并未对承载配置定义专门的方法但是我们可以利用UseSettings方法以键值对的形式对其进行设置这里我们采用这种方式完成了针对“环境”、“内容文件根目录”、“Web资源文件根目录”和“子环境”的设置前三个是“承载环境”的三个重要属性。承载配置最终会体现到表示承载上下文的WebHostBuilderContext对象上。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.AddSingletonIHandler, Handler().ConfigureFoobarbazOptions(context.Configuration)).Configure(app app.UseMiddlewareFoobarMiddleware()).Build().Run();依赖服务利用IWebHostBuilder的ConfigureServices方法进行注册该方法的参数类型为ActionWebHostBuilderContext, IServiceCollection意味着我们可以针对之前提供的承载配置比如承载环境进行针对性的服务注册。在这里我们不仅注册了依赖服务Handler还利用当前配置对配置选项FoobarbazOptions实施了绑定。应用配置通过专门的方法ConfigureAppConfiguration进行设置该方法的参数类型为ActionWebHostBuilderContext, IConfigurationBuilder意味着承载配置依然可以利用WebHostBuilderContext上下文获取到这里我们这是利用它得到对当前环境匹配的三个配置文件。程序启动后请求可以得到如下的响应内容。利用环境变量和命令行参数由于ASP.NET Core应用在启动时会使用前缀为“ASPNETCORE_”的环境变量作为承载配置所以上述利用UseSettings方法针对承载配置的设置都可以按照如下的方式利用环境变量代替。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.AddSingletonIHandler, Handler().ConfigureFoobarbazOptions(context.Configuration)).Configure(app app.UseMiddlewareFoobarMiddleware()).Build().Run();上面的代码片段并没有直接创建WebHostBuilder对象而是调用WebHost的静态方法CreateDefaultBuilder方法创建了一个具有默认配置的IWebHostBuilder对象。由于该方法传入了命令行参数args它会将命令行参数作为承载配置源之一所以程序中四个针对承载配置选项也可以利用命令行参数来完成。承载环境设置方法其实承载环境环境名称、内容文件根目录和Web资源文件根目录具有专门的方法所以最方便的还是直接按照如下的方式调用这些方法对它们进行设置。对于我们演示的实例来说针对环境名称、内容文件和Web资源文件根目录的设置可以直接调用IWebHostBuilder的UseEnvironment、UseContentRoot和UseWebRoot扩展方法来完成。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.AddSingletonIHandler, Handler().ConfigureFoobarbazOptions(context.Configuration)).Configure(app app.UseMiddlewareFoobarMiddleware()).Build().Run();使用Startup类型为了不让应用承载程序代码显得过于臃肿我们一般都会将服务注册和中间件注册移到按照约定定义的Startup类型中。如下面的代码片段所示中间件和服务注册分别实现在Startup类型的ConfigureServices和Configure方法中我们直接在构造函数中注入IConfiguration对象得到承载配置对象。值得一提对于第一代应用承载方式我们可以在Startup类型的构造函数中注入通过调用IWebHostBuilder的ConfigureServices方法注册的任何服务包括ASP.NET Core内部通过调用这个方法注册的服务比如本例的IConfiguration对象。Startup类型只需要调用IWebHostBuilder的UseStartupTStartup扩展方法进行注册即可。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)).UseStartupStartup().Build().Run();public class Startup
{public IConfiguration Configuration { get; }public Startup(IConfiguration configuration) Configuration configuration;public void ConfigureServices(IServiceCollection services) services.AddSingletonIHandler, Handler().ConfigureFoobarbazOptions(Configuration);public void Configure(IApplicationBuilder app) app.UseMiddlewareFoobarMiddleware();
}三、第二代应用承载模型除了承载Web应用我们还有很多针对后台服务比如很多批处理任务的承载需求为此微软推出了以IHostBuilder/IHost为核心的服务承载系统。Web应用本身实际上就是一个长时间运行的后台服务我们完全可以将应用定义成一个IHostedService服务该类型就是下图所示的GenericWebHostService。如果将上面介绍的称为第一代应用承载模式的话这就是第二代承载模式。基本编程模式和所有的Builder模式一样绝大部分API都落在作为构建者的IHostBuilder接口上服务注册、承载配置、应用配置都具有对应的方法。由于中间件隶属于GenericWebHostService这一单一的承载服务所以只能记住与IWebHostBuilder。如果采用第二代应用承载模型上面演示的程序可以改写成如下的形式。Host.CreateDefaultBuilder().ConfigureHostConfiguration(config config.AddInMemoryCollection(new Dictionarystring, 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.AddSingletonIHandler, Handler().ConfigureFoobarbazOptions(context.Configuration)).ConfigureWebHost(webHostBuilder webHostBuilder.Configure(appapp.UseMiddlewareFoobarMiddleware())).Build().Run();如上面的代码片段所示我们通过调用Host的静态方法CreateDefaultBuilder方法创建一个具有默认配置的IHostBuidler对象。IHostBuilder为承载配置的设置提供了独立的ConfigureHostConfiguration方法该方法的参数类型为ActionIConfigurationBuilder我们演示的例子利用这个方法注册了一个基于内存字典的配置源承载环境环境名称、内容文件和Web资源文件根目录和子环境名称在这里进行了设置。针对应用配置的设置通过ConfigureAppConfiguration方法来完成该方法的参数类型为ActionHostBuilderContext, IConfigurationBuilder代表承载上下文的HostBuilderContext可以得到预先设定的承载环境和承载配置我们的例子利用到定位与当前环境相匹配的配置文件。IHostBuilder同样定义了ConfigureServices方法该方法的参数类型为ActionHostBuilderContext, IServiceCollection意味着服务依然可以针对承载环境和承载配置进行注册。由于中间件的注册依然落在IWebHostBuilder上所以IHostBuilder提供了ConfigureWebHost/ConfigureWebHostDefaults这两个扩展方法予以适配它们具有一个类型为ActionIWebHostBuilder的参数。承载环境设置方法和IWebHostBuilder一样IHostBuidler同样提供了用来直接设置承载环境的方法。对于我们演示的实例来说针对环境名称、内容文件和Web资源文件根目录的设置可以直接调用IHostBuidler的UseEnvironment、UseContentRoot和UseWebRoot扩展方法来完成。由于Web资源文件并未“服务承载”的范畴所以针对Web资源文件根目录的设置还得采用直接设置承载配置的方式或者调用IWebHostBuilder的UseWebRoot扩展方法。Host.CreateDefaultBuilder().UseEnvironment(dev).UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), resources)).ConfigureHostConfiguration(config config.AddInMemoryCollection(new Dictionarystring, 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.AddSingletonIHandler, Handler().ConfigureFoobarbazOptions(context.Configuration)).ConfigureWebHost(webHostBuilder webHostBuilder.Configure(appapp.UseMiddlewareFoobarMiddleware())).Build().Run();针对IWebHostBuilder的适配由于IHostBuilder利用扩展方法ConfigureWebHost/ConfigureWebHostDefaults提供了针对IWebHostBuilder的适配意味着前面采用第一代应用承载方法编写的代码可以直接移植过来。如下面的代码片段所示静态方法ConfigureWebHost完全依然利用IWebHostBuilder完成所有的初始化工作我们只需要将指向该方法的ActionIWebHostBuilder委托传入IHostBuilder的ConfigureWebHostDefaults扩展方法就可以了。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)).UseStartupStartup();
}public class Startup
{public IConfiguration Configuration { get; }public Startup(IConfiguration configuration) Configuration configuration;public void ConfigureServices(IServiceCollection services) services.AddSingletonIHandler, Handler().ConfigureFoobarbazOptions(Configuration);public void Configure(IApplicationBuilder app) app.UseMiddlewareFoobarMiddleware();
}Startup构造函数注入的限制第二代应用承载模型利用ConfigureWebHost/ConfigureWebHostDefaults扩展方法对之前定义在IWebHostBuilder上的API绝大部分是扩展方法提供了100%的支持除了Build方法但是针对Startup构造函数中注入的服务则不再那么自由。如果采用基于IWebHostBuilder/IWebHost的应用承载方式通过调用IWebHostBuilder的ConfigureServices方法注册的服务都可以注入Startup的构造函数中如果采用基于IHostBuilder/IHost的应用承载方式只有与“承载配置承载环境属于承载配置的一部分”相关的如下三个服务能够注入到Startup的构造函数中。IHostingEnvironmentIWebHostEnvironmentIHostEnvironmentIConfiguration对于如下这段代码虽然注入Startup构造函数的Foobar同时通过调用IHostBuilder和IWebHostBuilder的ConfigureServices方法中进行了注册但是在创建Startup实例的时候依然会抛出异常。Host.CreateDefaultBuilder(args).ConfigureServices(sevicessevices.AddSingletonFoobar()).ConfigureWebHostDefaults(ConfigureWebHost).Build().Run();static void ConfigureWebHost(IWebHostBuilder webHostBuilder)
{webHostBuilder.UseEnvironment(dev).ConfigureServices(sevices sevices.AddSingletonFoobar()).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)).UseStartupStartup();
}public class Startup
{public IConfiguration Configuration { get; }public Startup(IConfiguration configuration,Foobar foobar) Configuration configuration;public void ConfigureServices(IServiceCollection services) services.AddSingletonIHandler, Handler().ConfigureFoobarbazOptions(Configuration);public void Configure(IApplicationBuilder app) app.UseMiddlewareFoobarMiddleware();
}public class Foobar
{ }综上所述最初版本的ASP.NET Core由于只考虑到Web应用自身的承载所以设计出了基于IWebHostBuilder/IWebHost模型。后来产生了基于后台服务承载的需求所以推出了基于IHostBuilder/IHost的服务承载模型原本的Web应用作为一个“后台服务GenericWebHostService.”进行承载。由于之前很多API都落在IWebHostBuilder主要无数的扩展方法出于兼容性的需求一个名为GenericWebHostBuilder的实现类型被定义出来它将针对IWebHostBuilder的方法调用转移到IHostBuilder/IHost的服务承载模型中。.NET 6在IHostBuilder/IHost服务承载模型基础上推出了更加简洁的Minimal API此时又面临相同的“抉择”。这次它不仅需要兼容IWebHostBuilder还得兼容IHostBuilder再加上Minimal API自身提供的API所以“一题多解”的现象就更多了。如果你对ASP.NET Core的历史不甚了解将会感到非常困惑。令你们更加感到困惑的时此时定义在IWebHostBuilder和IHostBuilder的API并非全部可用本文的下篇将为你一一解惑。