乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - 从ASP.NET Core 3.1到6.0,深入浅出ASP.NET Core启动过程
什么是ASP.NET Core
https://docs.microsoft.com/zh-cn/aspnet/core/introduction-to-aspnet-core
ASP.NET Core是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用。
使用ASP.NET Core,您可以:
- 生成Web应用和服务、物联网(IoT)应用和移动后端。
- 在Windows、macOS和Linux上使用喜爱的开发工具。
- 部署到云或本地。
- 在.NET Core上运行。
为何选择ASP.NET Core?
数百万开发人员在使用或使用过ASP.NET 4.x创建Web应用。ASP.NET Core是对ASP.NET 4.x的重新设计,其中包括体系结构上的更改,产生了更精简、更模块化的框架。
ASP.NET Core具有如下优点:
- 生成WebUI和WebAPI的统一场景。
- 针对可测试性进行构建。
- RazorPages可以使基于页面的编码方式更简单高效。
- Blazor允许在浏览器中使用C#和JavaScript。共享全部使用.NET编写的服务器端和客户端应用逻辑。
- 能够在Windows、macOS和Linux上进行开发和运行。
- 开放源代码和以社区为中心。
- 集成新式客户端框架和开发工作流。
- 支持使用gRPC托管远程过程调用(RPC)。
- 基于环境的云就绪配置系统。
- 内置依赖项注入。
- 轻型的高性能模块化HTTP请求管道。
- 能够托管于以下各项:
- Kestrel
- IIS
- HTTP.sys
- Nginx
- Apache
- Docker
- 并行版本控制。
- 简化新式Web开发的工具。
使用ASP.NET Core MVC生成WebAPI和WebUI
ASP.NET Core MVC提供生成WebAPI和Web应用所需的功能:
- Model-View-Controller(MVC)模式使WebAPI和Web应用可测试。
- RazorPages是基于页面的编程模型,它让WebUI的生成更加简单高效。
- Razor标记提供了适用于RazorPages和MVC视图的高效语法。
- 标记帮助程序使服务器端代码可以在Razor文件中参与创建和呈现HTML元素。
- 内置的多数据格式和内容协商支持使WebAPI可访问多种客户端,包括浏览器和移动设备。
- 模型绑定自动将HTTP请求中的数据映射到操作方法参数。
- 模型验证自动执行客户端和服务器端验证。
ASP.NET Core对比ASP.NET 4.x
ASP.NET 4.x是一个成熟的框架,提供在Windows上生成基于服务器的企业级 Web 应用所需的服务。
ASP.NET Core | ASP.NET 4.x |
---|---|
针对Windows、macOS或Linux进行生成 | 针对Windows进行生成 |
RazorPages是在ASP.NET Core 2.x及更高版本中创建WebUI时建议使用的方法。 | 使用WebForms SignalR、MVC WebAPI WebHooks或网页 |
每个计算机多个版本 | 每个计算机一个版本 |
使用C#或F#通过Visual Studio、Visual Studio for Mac或Visual Studio Code进行开发 | 使用C#、VB或F#通过Visual Studio进行开发 |
比ASP.NET 4.x性能更高 | 良好的性能 |
使用.NET Core运行时 | 使用.NET Framework运行时 |
安装
通过Visual Studio Installer选择ASP.NET和Web开发
这项。
如果你还要额外安装其他版本的ASP.NET Core SDK可以单独安装。
左边是集成的,右边是拆开的。
创建
创建ASP.NET Core应用项目(3.1 & 6.0)
方式一,Visual Studio 2022中手动创建,选择ASP.NET Core Web API
,命名为demoForWebApi6First
。
方式二,DotNet CLI命令行创建。
dotnet new webapi -o demoForWebApi6Second -f net6.0
并且加到当前项目中
dotnet sln add .\demoForWebApi6Second\demoForWebApi6Second.csproj
因为.Net 6和.Net Core 3.1模板已经发生了比较大变化,这里多创建一组.Net Core 3.1的项目来对照。
dotnet new webapi -o demoForWebApi31First -f netcoreapp3.1
dotnet sln add .\demoForWebApi31First\demoForWebApi31First.csproj
这里我们会发现,之前3.1是有Startup.cs
文件的,到了6.0已经没有了,这个文件被简化了。
在3.1中,Startup.cs
和Program.cs
文件内容是:
namespace demoForWebApi31First
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
namespace demoForWebApi31First
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
到了6.0合并两个文件,变成:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
启动顺序和启动阶段
在3.1的Program.cs
中有个main
入口,在它里面调用了CreateHostBuilder
,这个函数在下面就有定义,它返回了一个IHostBuilder
接口类型。
我们来看看这个IHostBuilder
接口类型定义。
//
// 摘要:
// A program initialization abstraction.
public interface IHostBuilder
{
//
// 摘要:
// A central location for sharing state between components during the host building
// process.
IDictionary<object, object> Properties { get; }
//
// 摘要:
// Run the given actions to initialize the host. This can only be called once.
//
// 返回结果:
// An initialized Microsoft.Extensions.Hosting.IHost.
IHost Build();
//
// 摘要:
// Sets up the configuration for the remainder of the build process and application.
// This can be called multiple times and the results will be additive. The results
// will be available at Microsoft.Extensions.Hosting.HostBuilderContext.Configuration
// for subsequent operations, as well as in Microsoft.Extensions.Hosting.IHost.Services.
//
// 参数:
// configureDelegate:
// The delegate for configuring the Microsoft.Extensions.Configuration.IConfigurationBuilder
// that will be used to construct the Microsoft.Extensions.Configuration.IConfiguration
// for the application.
//
// 返回结果:
// The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.
IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate);
//
// 摘要:
// Enables configuring the instantiated dependency container. This can be called
// multiple times and the results will be additive.
//
// 参数:
// configureDelegate:
// The delegate which configures the builder.
//
// 类型参数:
// TContainerBuilder:
// The type of builder.
//
// 返回结果:
// The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.
IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate);
//
// 摘要:
// Set up the configuration for the builder itself. This will be used to initialize
// the Microsoft.Extensions.Hosting.IHostEnvironment for use later in the build
// process. This can be called multiple times and the results will be additive.
//
// 参数:
// configureDelegate:
// The delegate for configuring the Microsoft.Extensions.Configuration.IConfigurationBuilder
// that will be used to construct the Microsoft.Extensions.Configuration.IConfiguration
// for the host.
//
// 返回结果:
// The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.
IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate);
//
// 摘要:
// Adds services to the container. This can be called multiple times and the results
// will be additive.
//
// 参数:
// configureDelegate:
// The delegate for configuring the Microsoft.Extensions.DependencyInjection.IServiceCollection
// that will be used to construct the System.IServiceProvider.
//
// 返回结果:
// The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.
IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate);
//
// 摘要:
// Overrides the factory used to create the service provider.
//
// 参数:
// factory:
// The factory to register.
//
// 类型参数:
// TContainerBuilder:
// The type of builder.
//
// 返回结果:
// The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.
IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
//
// 摘要:
// Overrides the factory used to create the service provider.
//
// 类型参数:
// TContainerBuilder:
// The type of builder.
//
// 返回结果:
// The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.
IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory);
}
这里我们需要关注的是ConfigureAppConfiguration
、ConfigureHostConfiguration
、ConfigureServices
这三个,我们看到他们的入口都是一个委托(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate
)。
我们增加一些代码,以便分析函数执行顺序,在Program.cs
namespace demoForWebApi31First
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder => {
Console.WriteLine("ConfigureAppConfiguration");
})
.ConfigureHostConfiguration(builder =>
{
Console.WriteLine("ConfigureHostConfiguration");
})
.ConfigureServices(builder =>
{
Console.WriteLine("ConfigureServices");
})
.ConfigureWebHostDefaults(webBuilder =>
{
Console.WriteLine("ConfigureWebHostDefaults");
webBuilder.UseStartup<Startup>();
});
}
}
namespace demoForWebApi31First
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Console.WriteLine("Startup");
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
Console.WriteLine("Startup.ConfigureServices");
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
Console.WriteLine("Startup.Configure");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
启动后查看下输出
ConfigureWebHostDefaults
ConfigureHostConfiguration
ConfigureAppConfiguration
ConfigureServices
Startup
Startup.ConfigureServices
Startup.Configure
我们调整ConfigureWebHostDefaults
和ConfigureServices
的顺序,让它写在前面。
namespace demoForWebApi31First
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
Console.WriteLine("ConfigureWebHostDefaults");
webBuilder.UseStartup<Startup>();
})
.ConfigureServices(builder =>
{
Console.WriteLine("ConfigureServices");
})
.ConfigureAppConfiguration(builder => {
Console.WriteLine("ConfigureAppConfiguration");
})
.ConfigureHostConfiguration(builder =>
{
Console.WriteLine("ConfigureHostConfiguration");
});
}
}
然后看到的输出是
ConfigureWebHostDefaults
ConfigureHostConfiguration
ConfigureAppConfiguration
Startup
Startup.ConfigureServices
ConfigureServices
Startup.Configure
对比之前的,也就是ConfigureServices
挪到Startup.ConfigureServices
后面来了。
我们会发现,它的启动顺序规律是
整个启动过程分为五个阶段
- ConfigureWebHostDefaults,这个阶段注册了应用程序必要的组件,比如配置的组件、容器的组件。
- ConfigureHostConfiguration,这个阶段配置应用程序启动时必要组件,比如应用程序启动时要监听的端口、需要监听的URL地址,在这个过程中我们可以嵌入一些我们自己的配置内容注入到配置的框架中。
- ConfigureAppConfiguration,这个阶段,可以让我们来嵌入我们自己的配置文件,供应用程序来读取,以后可以在应用程序每个组件执行过程中读取。
- ConfigureServices->Startup.ConfigureServices,这个阶段,往容器里面注入我们应用的组件。
- Startup.Configure,这个阶段用来注入我们的中间件,处理HttpContext整个的请求过程。
从Startup.Configure
代码可以看到,这里我们使用了不少中间件。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
Console.WriteLine("Startup.Configure");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
通常,我们会在Startup.ConfigureServices
里面去注册我们的类,在Startup.Configure
去注册我们的中间件。
在.Net 6中逐渐消失的Startup.cs
实际上,在整个启动过程中,Startup.cs
这个类是非必要的。
我们把Program.cs
中webBuilder.UseStartup<Startup>();
掉,把Startup.cs
的内容迁移进来。
namespace demoForWebApi31First
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
Console.WriteLine("ConfigureWebHostDefaults");
// webBuilder.UseStartup<Startup>();
webBuilder.ConfigureServices(services =>
{
Console.WriteLine("webBuilder.ConfigureServices");
services.AddControllers();
});
webBuilder.Configure(app =>
{
Console.WriteLine("webBuilder.Configure");
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
})
.ConfigureServices(builder =>
{
Console.WriteLine("ConfigureServices");
})
.ConfigureAppConfiguration(builder => {
Console.WriteLine("ConfigureAppConfiguration");
})
.ConfigureHostConfiguration(builder =>
{
Console.WriteLine("ConfigureHostConfiguration");
});
}
}
启动运行
ConfigureWebHostDefaults
ConfigureHostConfiguration
ConfigureAppConfiguration
webBuilder.ConfigureServices
ConfigureServices
webBuilder.Configure
发现效果是一样的。
这也不难理解,为什么在.Net 6中,Startup.cs
文件为什么没了。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
实际上,在.Net 6中,新的Program.cs
内容就是原来的webBuilder.ConfigureServices
和webBuilder.Configure
的内容。