通过 ASP.NET Core,可以轻松创建运行速度快且可移植的跨平台 Web 应用程序。本文将逐步介绍如何开发简单的 ASP.NET Core 网站,并说明项目中每个文件的作用。
此外,还介绍了在此过程中出现的 ASP.NET Core 重要概念。本文还将重点关注 ASP.NET Core 2.0 的变化,帮助熟悉 ASP.NET Core 1.0 和 1.1 的读者迁移到 2.0。
1
创建 ASP.NET Core 项目
可以使用 Visual Studio 或 .NET Core 命令行接口 (.NET CLI) 通过模板创建 ASP.NET Core 项目。Visual Studio 2017 提供超棒的 .NET Core 开发体验,具有顶尖的调试、Docker 集成和其他许多功能。
不过,在此演练中,我将使用 .NET CLI 和 Visual Studio Code,以防一些用户要在 Mac 或 Linux 开发计算机上跟着我一起操作。
dotnet new 命令可用于新建 .NET Core 项目。运行不含其他任何参数的 dotnet new 会列出可用的项目模板,如图 1 所示。如果熟悉旧版 .NET CLI,就会发现 2.0 版中有许多新模板。
图 1:新增的 .NET Core 项目模板
Angular 和 React.js SPA 模板:这些模板创建的 ASP.NET Core 应用程序提供单页应用程序(使用 Angular 4 或 React.js)作为前端。这些模板包含前端和后端应用程序,以及用于生成前端的 Webpack 配置(外加在每次生成 ASP.NET Core 项目时启动 Webpack 生成的 csproj 修改)。
包含 Razor 页面的 ASP.NET Core Web 应用程序:Razor 页面是一项 ASP.NET Core 2.0 新功能,支持用户创建能够直接处理请求(无需使用控制器)的页面。如果方案涉及基于页面的编程模型,这是理想之选。
对于本次演练,将从全新的 Razor 页面模板入手,具体方法是执行 dotnet new razor。项目一创建完成,就应该可以通过执行 dotnet run 运行项目。在旧版 .NET Core 中,一直以来都必须先执行 dotnet restore,才能安装必要的 NuGet 包。
不过,自 .NET Core 2.0 起,restore 命令现在由依赖它的 CLI 命令自动运行。开始执行 dotnet run,并转到应用程序在侦听的 URL(很可能是 http://localhost:5000),以测试模板网站。应该会看到所呈现的 Web 应用程序(如图 2 所示)。
图 2:简单的 ASP.NET Core 应用程序正在运行
恭喜!现已发布首个 ASP.NET Core 2.0 应用程序! 至此,已有一个简单的 Web 应用程序正在运行,接下来将探究项目所含内容,以便更好地了解 ASP.NET Core 的工作原理。
2
依赖项、源和资源
要介绍的首个文件是项目文件本身(.csproj 文件)。此文件告知 MSBuild 如何生成项目,即依赖哪些包、定目标到哪一版 .NET Core 等。如果之前探究过 .csproj 文件,就会发现此项目文件要比原来小很多。
我们做出了很大努力,让 .csproj 文件变得更简短、更具可读性。一项有助于缩减项目文件的显著变化是,不再需要明确列出源文件。
相反,.NET Core SDK 会自动编译所有 .cs 文件,无论它们是位于项目文件旁边,还是位于 .csproj 目录下的任意目录中。同样,发现的所有 .resx 文件也都会作为资源嵌入。
如果不想编译所有这些 .cs 文件,可以从“编译项组”中删除它们,也可以将 EnableDefaultCompileItems 属性设置为 false,从而完全禁用默认编译项。
应用程序运行时定目标到的 .NET 版本由 <TargetFramework> 元素指定。为了利用新增的 ASP.NET Core 2.0 功能,并扩大 .NET Core 2.0 外围应用,将此元素设置为 netcoreapp2.0。
.csproj 文件中间的 <PackageReference> 元素指明了项目依赖的 NuGet 包。大家将会发现,在 ASP.NET Core 2.0 中,现在只默认包含一个元包 (Microsoft.AspNetCore.All)。
此包将其他所有 Microsoft.AspNetCore 包添加到一个简明引用中,这让 ASP.NET Core 2.0 项目文件比旧版项目文件小得多。添加其他 NuGet 依赖项的方法为,添加更多 <PackageReference> 元素、使用 Visual Studio NuGet 包管理 UI 或运行 .NET CLI dotnet add 命令。
如果刚才使用的是 SPA 模板(angular、react 或 reactredux),.csproj 文件中也会定义自定义目标,以确保 Webpack 在项目生成时运行。
3
创建并运行 Web 主机
Program.cs 表示应用程序的入口点。由于 ASP.NET Core 应用程序是控制台应用程序,因此与所有控制台应用程序一样,都包含在应用程序执行时启动的 Main 方法。
ASP.NET Core 2.0 模板中 Main 方法的内容非常简单,负责创建 IWebHost 对象,并对它调用 Run。
如果以前使用过 ASP.NET Core 1.0,就会发现此文件要比旧模板中的 program.cs 文件简单一点。这是因为,采用了新方法 WebHost.CreateDefaultBuilder。以前,ASP.NET Core 应用程序的 Main 方法通过配置 WebHostBuilder 来创建 IWebHost 实例。
此配置需要执行一些步骤,如指定 Web 服务器、设置内容根路径和启用 IIS 集成。
新方法 CreateDefaultBuilder 可创建现成的 IWebHost,且最常见配置均已完成,从而简化了此过程。除了指定前面列出的项之外,CreateDefaultBuilder 还负责处理一些设置(设置配置信息,并注册默认日志提供程序),这些以前是在 Startup.cs 中进行处理。
由于 ASP.NET Core 开放源代码,因此如果感兴趣的话,可以查看 GitHub 上的源代码 ( bit.ly/2uR1Dar),全面详细地了解 CreateDefaultBuilder 的运作情况。
接下来,将简单了解一下 CreateDefaultBuilder 中执行的最重要调用及其用途。虽然这些调用全都是由 CreateDefaultBuilder 自动执行,但最好也了解一下幕后运作机制。
UseKestrel 指定应用程序应使用 Kestrel,这是基于 libuv 的跨平台 Web 服务器。另外一种方法是,使用 HttpSys 作为 Web 服务器 (UseHttpSys)。
虽然 HttpSys 仅受 Windows(Windows 7/2008 R2 及更高版本)支持,但具有以下优点:允许进行 Windows 身份验证,可直接在 Internet 上安全运行(相比之下,如果接收 Internet 请求,Kestrel 应使用 IIS、Nginx 或 Apache 等反向代理)。
UseContentRoot 为应用程序指定根目录,以便 ASP.NET Core 可以在其中找到整个网站内都有的内容(如配置文件)。请注意,这不同于 Web 根(其中包含的是静态文件),尽管默认情况下 Web 根是以内容根为依据 ([ContentRoot]/wwwroot)。
ConfigureAppConfiguration 创建配置对象,以便应用程序可以使用此对象读取运行时设置。通过 CreateDefaultBuilder 调用时,它将会从 appsettings.json 文件、环境专属 .json 文件(若有)、环境变量和命令行参数读取应用程序配置设置。如果是在开发环境中,还将使用用户密钥。这是 ASP.NET Core 2.0 中新增的方法,稍后我将深入介绍。
ConfigureLogging 为应用程序设置日志。通过 CreateDefaultBuilder 调用时,将添加控制台和调试日志提供程序。与 ConfigureAppConfiguration 一样,这也是一种新方法,稍后我将深入介绍。
UseIISIntegration 将应用程序配置为在 IIS 中运行。请注意,仍需要使用 UseKestrel。IIS 起到反向代理的作用,而 Kestrel 仍用作主机。此外,如果应用程序没有使用 IIS 作为反向代理,那么 UseIISIntegration 不会有任何效果。
因此,即使应用程序在非 IIS 方案中运行,也可以安全调用这种方法。
在许多情况下,CreateDefaultBuilder 提供的默认配置足够用了。除了调用此方法之外,还需要做的就只是调用 UseStartup<T>(其中 T 为 Startup 类型),为应用程序指定 Startup 类而已。
如果 CreateDefaultBuilder 无法满足方案需求,随时都可以自定义 IWebHost 的创建方式。如果只需进行细微调整,可以调用 CreateDefaultBuilder,再修改返回的 WebHostBuilder(例如,或许可以再次调用 ConfigureAppConfiguration,从而添加更多的配置源)。
如果需要对 IWebHost 执行更大幅度的更改,可以完全跳过调用 CreateDefaultBuilder 这一步,直接自行构造 WebHostBuilder,就像在 ASP.NET Core 1.0 或 1.1 中一样。不过,即使这样做,也仍可以利用新方法 ConfigureAppConfiguration 和 ConfigureLogging。有关 Web 主机配置的更多详细信息,请访问 bit.ly/2uuSwwM。
4
ASP.NET Core 环境
CreateDefaultBuilder 执行的一些操作取决于 ASP.NET Core 应用程序在哪个环境中运行。环境这一概念在 2.0 中并不新鲜,但值得简要回顾一下,因为此概念的出现频率非常高。
在 ASP.NET Core 中,应用程序的运行环境由 ASPNETCORE_ENVIRONMENT 环境变量指定。可以根据需要将此变量设置为任意值,但通常使用的是值 Development、Staging 和 Production。
因此,如果在调用 dotnet run 之前将 ASPNETCORE_ENVIRONMENT 变量设置为 Development(或在 launchSettings.json 文件中设置此环境变量),应用程序会在 Development 模式下运行,而不是 Production 模式(这是不设置任何变量时的默认模式)。
多项 ASP.NET Core 功能都使用此值(我将在后面介绍配置和日志时引用此值)修改运行时行为,可以使用 IHostingEnvironment 服务在自己的代码中访问此值。有关 ASP.NET Core 环境的详细信息,请参阅 ASP.NET Core 文档 ( bit.ly/2eICDMF)。
5
ASP.NET Core 配置
ASP.NET Core 使用 Microsoft.Extensions.Configuration 包的 IConfiguration 接口,提供运行时配置设置。如前所述,CreateDefaultBuilder 将从 .json 文件和环境变量读取设置。不过,配置系统是可扩展的,可以从各种提供程序(.json 文件、.xml 文件、.ini 文件、环境变量、Azure Key Vault 等)读取配置信息。
使用 IConfiguration 和 IConfigurationBuilder 对象时,请务必记住提供程序的添加顺序。后添加的提供程序可能会重写先添加的提供程序的设置。因此,不妨先添加常见的基础提供程序,然后再添加可能会重写一些设置的环境专属提供程序。
ASP.NET Core 中的配置设置是分层的。例如,在新建的项目中,appsettings.json(见图 3)包含一个顶层元素 Logging,其下包含子设置。这些设置指明要记录的消息的最低优先级(通过“LogLevel”设置),以及是否应在记录消息的同时记录应用程序的逻辑范围(通过 IncludeScopes)。
若要检索像这样的嵌套设置,可以使用 IConfiguration.GetSection 方法,检索配置的各个部分,也可以指定特定设置的完整路径(以冒号分隔)。因此,可以按如下所示,在项目中检索 IncludeScopes 的值:
图 3:ASP.NET Core 设置文件
{ "Logging": { "IncludeScopes": false, "Debug": { "LogLevel": { "Default": "Warning"} }, "Console": { "LogLevel": { "Default": "Warning"} } }}
Configuration[ "Logging:IncludeScopes"]
使用环境变量定义配置设置时,环境变量名称应包含层次结构的所有级别,并且可以使用冒号 (:) 或双下划线 (__) 作为分隔符。
例如,环境变量 Logging__IncludeScopes 会重写图 3 示例文件中的 IncludeScopes 设置,假设此环境变量提供程序是在设置文件之后添加的,就像在 CreateDefaultBuilder 中一样。
因为 WebHost.CreateDefaultBuilder 是从 appsettings.json 和环境专属 .json 文件中读取配置,所以大家会发现,日志行为随环境更改(appsettings.Development.json 重写默认 appsettings.json 的 LogLevel 设置,包含更详细的“debug”和“information”级别)而发生变化。
如果在调用 dotnet run 之前将环境设置为 Development,应该会发现有不少的控制台日志(这很好,因为这样对调试非常有用)。
相比之下,如果将环境设置为 Production,除了警告和错误之外,不会生成其他任何控制台日志(这同样也很好,因为控制台日志的速度慢,应在生产中将数量降至最低)。
如果用过 ASP.NET Core 1.0 和 1.1,可能会发现,ConfigureAppConfiguration 是 2.0 中新增的方法。
以前,在创建 Startup 类型期间创建 IConfiguration 是很常见的做法。新增的 ConfigureAppConfiguration 方法可有效替代这种做法,因为它可以更新应用程序依赖关系注入 (DI) 容器中存储的 IConfiguration 对象,以方便日后检索,并能在应用程序生命周期的较早期提供配置设置。
6
ASP.NET Core 日志
与配置设置一样,如果熟悉旧版 ASP.NET Core,可能还记得日志设置是在 Startup.cs(而不是 Program.cs)中完成的。在 ASP.NET Core 2.0 中,现在可以在生成 IWebHost 时通过 ConfigureLogging 方法完成日志设置。
虽然仍可以在 Startup 中设置日志(使用 Startup.ConfigureServices 中的 services.Add-Logging),但在 Web 主机创建时配置日志,可以简化 Startup 类型,并能在应用程序启动过程的较早期使用日志。
与配置一样,ASP.NET Core 日志也可扩展。注册的各个提供程序可以记录到不同的终结点。引用 Microsoft.AspNetCore.All 可以立即获取许多提供程序。若要获取更多提供程序,请访问 .NET 开发者社区。
如 WebHost.CreateDefaultBuilder 的源代码所示,可以调用提供程序专属扩展方法(如 ILoggingBuilder 中的 AddDebug 或 AddConsole),添加日志提供程序。
如果使用的是 WebHost.CreateDefaultBuilder,但仍希望注册除默认 Debug 和 Console 之外的日志提供程序,可以对 CreateDefaultBuilder 返回的 IWebHostBuilder 额外调用 ConfigureLogging。
在日志配置完成且提供程序已注册后,ASP.NET Core 便会自动记录工作状况消息,以处理传入请求。还可以通过依赖关系注入请求获取 ILogger 对象,从而记录自己的诊断消息(下一部分将对此进行详细介绍)。
调用 ILogger.Log 和级别专属变量(如 LogCritical、LogInformation 等)可用于记录消息。
7
Startup 类型
至此,已通过探究 Program.cs 了解了 Web 主机的创建方式,现在可以来看看 Startup.cs 了。创建 IWebHost 时执行的 UseStartup 调用指定了应用程序应使用的 Startup 类型。
ASP.NET Core 2.0 中的 Startup.cs 没有太多变化(只是变得更简单了,因为日志和配置都已改为在 Program.cs 中设置),但我还是要简要回顾一下 Startup 类型的两个重要方法,因为它们对 ASP.NET Core 应用程序非常重要。
依赖关系注入:Startup 类型的 ConfigureServices 方法将服务添加到应用程序的依赖关系注入容器中。所有 ASP.NET Core 应用程序都有一个默认依赖关系注入容器,用于存储服务,以供日后使用。这样一来,服务无需与依赖它们的组件紧密耦合,即可供使用。
已介绍过几个这样的示例,ConfigureAppConfiguration 和 ConfigureLogging 向容器添加服务,以供日后可在应用程序中使用。在运行时,如果类型实例获得调用,那么 ASP.NET Core 会自动从依赖关系注入容器(若有)中检索对象。
例如,ASP.NET Core 2.0 项目 Startup 类的一个构造函数需要使用 IConfiguration 参数。当 IWebHost 开始运行时,此构造函数便会自动获得调用。在这种情况下,ASP.NET Core 会从依赖关系注入容器中提供所需的 IConfiguration 参数。
再例如,若要从 Razor 页面记录消息,可以请求将参数形式的记录器对象获取到页面模型的构造函数中(就像 Startup 请求获取 IConfiguration 对象一样),也可以在 cshtml 中使用 @inject 语法这样做,如下所示:
@ usingMicrosoft.Extensions.Logging@inject ILogger<Index_Page> logger@functions { publicvoidOnGet() { logger.LogInformation( "Beginning GET"); }}
还可以执行一些类似操作,检索 IConfiguration 对象或其他任何已注册为服务的类型。这样一来,Startup 类型、Razor 页面、控制器等与依赖关系注入容器所提供服务的依赖就不会太紧密。
正如本部分开头所提,在 Startup.ConfigureServices 方法中,服务被添加到依赖关系注入容器。用于创建应用程序的项目模板已在 ConfigureServices 中执行了一次调用,即 services.AddMvc。大家可能已猜到,这会注册 MVC 框架需要的服务。
经常在 ConfigureServices 方法中注册的另一种服务是 Entity Framework Core。虽然本示例未使用,但使用 Entity Framework Core 的应用程序通常注册的是 DbContexts,这是调用 services.AddDbContext 以结合使用 Entity Framework 模型时所需的。
还可以调用 services.AddTransient、services.AddScoped 或 services.AddSingleton,注册自己的类型和服务(具体取决于依赖关系注入提供的对象所需的生存期)。
如果注册为单一实例,每当有请求获取服务类型时,就会返回服务的单一实例;如果注册为临时实例,就会为每个请求新建一个实例。如果添加为范围内实例,将在一个 HTTP 请求的整个处理过程中使用一个服务实例。若要更深入地了解 ASP.NET Core 中的依赖关系注入,请访问 bit.ly/2w7XtJI。
HTTP 请求处理管道和中间件:Startup 类型中的另一个重要方法是 Configure 方法。这是设置 ASP.NET Core 应用程序的核心(即 HTTP 请求处理管道)的地方。在此方法中,将注册不同的中间件,用于处理传入的 HTTP 请求,以生成响应。
在 Startup.Configure 中,中间件组件会被添加到 IApplicationBuilder 中,以形成处理管道。若有请求,将调用已注册的首个中间件。
此中间件将执行所需的任何逻辑,然后再调用管道中的下一个中间件,或在已完全处理响应的情况下返回到上一个中间件(若有),这样就可以在响应准备就绪后执行任何所需的逻辑。图 4 展示了这种调用模式,即在有请求时先依序调用中间件组件,然后再在处理完毕后逆序调用中间件组件。
图 4:ASP.NET Core 中间件处理管道
举一个具体示例,图 5 展示了模板项目中的 Configure 方法。当有新请求时,将会先调用 DeveloperExceptionPage 中间件或 ExceptionHandler 中间件,具体取决于环境(如前所述,这是通过 ASPNETCORE_ENVIRONMENT 环境变量进行配置)。
这些中间件组件最初并不会执行多少操作,但在后续中间件运行且请求通过中间件管道返回后,它们将监视并处理异常。
图 5:ASP.NET Core Startup.Configure 方法设置中间件管道
publicvoidConfigure(IApplicationBuilder app, IHostingEnvironment env){ if(env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else{ app.UseExceptionHandler( "/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });}
接下来,将调用 StaticFiles 中间件,它可以提供静态文件(例如,图像或样式表),从而处理请求。如果是这样,它将暂停管道,并将控制权归还给上一个中间件,即异常处理程序。
如果 StaticFiles 中间件无法提供响应,它将调用下一个中间件,即 MVC 中间件。根据指定的路由选项,此中间件会尝试将请求路由到 MVC 控制器(或 Razor 页面),以实现请求履行。
中间件组件的注册顺序非常重要。如果 UseStaticFiles 是在 UseMvc 后面注册,应用程序会先尝试将所有请求路由到 MVC 控制器,然后再检查是否有静态文件。这可能会导致性能大大降低! 如果管道的靠后位置上有异常处理中间件,那么它将无法处理在上一个中间件组件中出现的异常。
8
Razor 页面
除了 .csproj、program.cs 和 startup.cs 文件之外,ASP.NET Core 项目还有一个“页面”文件夹,其中包含应用程序的 Razor 页面。Razor 页面类似于 MVC 视图,不同之处在于可以直接将请求路由到 Razor 页面,而不需要单独使用控制器。
这样一来,便可以简化基于页面的应用程序,并确保视图和视图模型在一起。支持页面的模型可以直接包含在 cshtml 页面中(使用 @functions 指令),也可以包含在单独的代码文件中(使用 @model 指令引用)。
若要详细了解 Razor 页面,请参阅本期中 Steve Smith 撰写的文章“Razor 页面简化了 ASP.NET MVC 应用程序”。
9
总结
希望本次演练能够帮助大家了解如何新建 ASP.NET Core 2.0 Web 应用程序,并很好地揭秘了新项目模板的内容。我回顾了项目内容,包括简化的 .csproj 文件、Program.cs 中的应用程序入口点和 Web 主机配置,以及 Startup.cs 中的服务和中间件注册。
若要更深入发掘 ASP.NET Core 的无限可能,使用其他部分模板(如 Web API 模板或部分新 SPA 模板)新建一些项目可能会有所帮助。
此外,不妨尝试将 ASP.NET Core 应用程序作为应用服务 Web 应用部署到 Azure,也可以将应用程序打包为 Linux 或 Windows Docker 映像。若要详细了解本文涉及的主题和更多内容,当然可以查看 docs.microsoft.com/aspnet/core中的完整文档。
Mike Rousos是 .NET 客户成功团队的首席软件工程师。
自 2004 年以来,Rousos 一直都是 .NET 团队的一员,致力于研究跟踪、安全托管、托管和最新技术 .NET Core。
衷心感谢以下 Microsoft 技术专家对本文的审阅:Glenn Condron 和 Ryan Nowak返回搜狐,查看更多
责任编辑: