第16章 发布和部署应用程序(ASP.NET Core in Action, 2nd Edition)

发布时间 2023-04-11 13:56:25作者: 码农小白修炼记

本章包括

  • 发布 ASP.NET Core 应用程序
  • 在 IIS 中托管 ASP.NET Core 应用程序
  • 自定义 ASP.NET Core 应用程序的 URL
  • 通过捆绑和缩小优化客户端资源

到目前为止,我们在这本书中涵盖了大量的内容。我们已经介绍了构建 ASP.NET Core 应用程序的基本机制,例如配置依赖注入、加载应用程序设置和构建中间件管道。我们已经研究了 UI 端,使用 Razor 模板和布局构建 HTML 响应。我们已经研究了更高级别的抽象,例如 EF Core 和 ASP.NET Core Identity,它们允许您与数据库交互并将用户添加到应用程序中。在本章中,我们采取了稍微不同的路线。我们将专注于部署应用程序以便用户能够访问它意味着什么,而不是寻找构建更大、更好的应用程序的方法。

我们将从第 16.1 节中的 ASP.NET Core 托管模型开始,并检查为什么您可能希望在反向代理之后托管应用程序,而不是将应用程序直接暴露于互联网。我将向您展示在开发中使用 dotnet run 运行 ASP.NET Core 应用程序与在远程服务器上发布应用程序之间的区别。最后,我将描述在决定如何和在何处部署应用程序时可以使用的一些选项。

在第 16.2 节中,我将向您展示如何将应用程序部署到一个这样的选项,即运行 IIS(Internet 信息服务)的 Windows 服务器。对于许多已经熟悉 ASP.NET 的开发人员来说,这是一个典型的部署场景,因此它将作为一个有用的案例研究,但这肯定不是唯一的可能性。我不会详细介绍配置备受尊敬的 IIS 系统的所有技术细节,但我将向您展示启动和运行该系统所需的最低要求。如果您的重点是跨平台开发,那么不要担心,我不会在 IIS 上停留太久。

在第 16.3 节中,我将介绍 Linux 上的托管。您将看到它与在 Windows 上托管应用程序的区别,了解您需要对应用程序进行的更改,并了解一些需要注意的问题。我将描述 Linux 上的反向代理与 IIS 的区别,并向您介绍一些可以用来配置环境的资源,而不是在本书中给出详尽的说明。

如果您没有使用 IIS 托管应用程序,则可能需要设置 ASP.NET Core 应用程序在部署应用程序时使用的 URL。在第 16.4 节中,我将展示两种方法:使用特殊的 ASP.NET Core_URLS 环境变量和使用命令行参数。虽然在开发过程中通常不存在问题,但在需要部署应用程序时,为应用程序设置正确的 URL 至关重要。

在本章的最后一节中,我们将讨论部署应用程序时使用的一个常见优化步骤。捆绑和缩小用于减少浏览器为完全加载页面而必须向应用程序发出的请求的数量和大小。我将向您展示如何在构建应用程序时使用一个简单的工具来创建捆绑包,以及如何在生产时有条件地加载这些捆绑包以优化应用程序的页面大小。

本章涵盖了一系列相对广泛的主题,都与部署应用程序有关。但在我们进入实质之前,我将仔细研究 ASP.NET Core 的托管模型,以便我们在同一页上。这与上一版本 ASP.NET 的托管模型有很大不同,因此如果你来自这个背景,最好尝试忘记你所知道的!

16.1 了解 ASP.NET Core 托管模型

如果你回想第一章,你可能会记得我们讨论过 ASP.NET Core 的托管模型。ASP.NET Core 应用程序本质上是控制台应用程序。它们有一个静态的 void Main 函数,作为应用程序的入口点,就像标准的 .NET 控制台应用程序一样。

应用程序之所以成为 ASP.NET Core 应用程序,是因为它在控制台应用程序进程中运行一个 Web 服务器,通常是 Kestrel。Kestrel 提供了接收请求并向客户端返回响应的 HTTP 功能。Kestrel 将它收到的任何请求传递给应用程序主体以生成响应,如图 16.1 所示。该托管模型将服务器和反向代理与应用程序本身分离,以便同一应用程序可以在多个环境中保持不变。

在本书中,我们重点介绍了图 16.1 的下半部分 —— ASP.NET Core 应用程序本身,但现实情况是,您通常希望将 ASP.NET Core 程序放置在反向代理之后,例如 Windows 上的 IIS,或 Linux 上的 NGINX 或 Apache。反向代理是一个程序,它监听来自互联网的 HTTP 请求,然后向你的应用程序发出请求,就像请求直接来自互联网一样。

图16.1 ASP.NET Core 的托管模型。反向代理接收请求并将其转发到 Kestrel Web 服务器。同一应用程序可以在不同的反向代理后面运行,无需修改。

定义:反向代理是负责接收请求并将其转发到适当的 Web 服务器的软件。反向代理直接向 Internet 公开,而底层 Web 服务器只向代理公开。

如果您正在使用平台即服务(PaaS)产品运行应用程序,例如Azure应用程序服务,那么您也在使用Azure管理的反向代理。使用反向代理有很多好处:

  • 安全性——反向代理是专门设计用于暴露于恶意互联网流量的,因此它们通常经过良好的测试和考验。
  • 性能——您可以配置反向代理,通过积极缓存对请求的响应来提高性能。
  • 进程管理——一个不幸的现实是,应用程序有时会崩溃。一些反向代理可以充当监控器/调度器,以确保如果应用程序崩溃,代理可以自动重新启动它。
  • 支持多个应用程序——在一台服务器上运行多个应用程序是很常见的。通过使用请求的主机名来决定哪个应用程序应该接收请求,使用反向代理可以更容易地支持这种情况。

我不想让人觉得使用反向代理是阳光和玫瑰。它也有一些缺点:

  • 复杂性——最大的抱怨之一是反向代理非常复杂。如果您自己管理代理(而不是依赖 PaaS 实现),可能会有许多特定于代理的陷阱需要注意。
  • 进程间通信——大多数反向代理需要两个进程:反向代理和 Web 应用程序。两者之间的通信通常比直接将 Web 应用程序暴露给 Internet 请求要慢。
  • 限制特性——不是所有的反向代理都支持ASP.NET Core应用程序的所有相同特性。例如,Kestrel 支持 HTTP/2,但如果你的反向代理不支持,你就看不到好处。

无论你是否选择使用反向代理,当你需要托管你的应用程序时,你都不能将你的代码文件直接复制到服务器上。首先,您需要发布您的ASP.NET Core应用程序,以优化它的生产。在16.1.1节中,我们将研究如何构建一个ASP.NET Core应用程序,以便它可以在您的开发机器上运行,而将它发布后可以在服务器上运行。

16.1.1 运行与发布ASP.NET Core应用程序

ASP.NET Core与以前版本的ASP.NET相比的一个关键变化是使用您喜爱的代码编辑器和IDE轻松构建应用程序。以前,ASP.NET开发需要Visual Studio,但通过.NET CLI和OmniSharp插件,您现在可以在任何平台上使用您熟悉的工具构建应用程序。

因此,无论是使用Visual Studio还是.NET CLI进行构建,都会在后台使用相同的工具。Visual Studio为构建应用程序提供了额外的GUI、功能和包装器,但它在幕后执行与.NET CLI相同的命令。

作为复习,到目前为止,您已经使用了四个主要的.NET CLI命令来构建应用程序:

  • dotnet new——从模板创建ASP.NET Core应用程序
  • dotnet restore——为项目下载并安装任何引用的NuGet软件包
  • dotnet build——编译和构建项目
    dotnet运行——执行应用程序,以便向其发送请求

如果您曾经构建过.NET应用程序,无论是ASP.NET应用程序还是.NET Framework控制台应用程序,您都会知道构建过程的输出会写入bin文件夹。ASP.NET Core应用程序也是如此。

如果在调用dotnet build时项目编译成功,则.NET CLI会将其输出写入项目目录中的bin文件夹。在这个bin文件夹中有几个运行应用程序所需的文件,包括一个包含应用程序代码的.dll文件。图16.2显示了ASP.NET Core应用程序bin文件夹的部分输出。

图16.2 运行dotnet构建后ASP.NET Core应用程序的bin文件夹。应用程序被编译为一个.dll文件ExampleApp.dll。

注意:在Windows上,您还将有一个可执行.exe文件ExampleApp.exe。这是一个简单的包装文件,方便您运行ExampleApp.dll中包含的应用程序。

在项目文件夹中调用dotnet run(或使用Visual Studio运行应用程序)时,.NET CLI将使用.dll运行应用程序。但这并不包含部署应用程序所需的所有内容。

要在服务器上托管和部署应用程序,首先需要发布它。您可以使用dotnet publish命令从命令行发布ASP.NET Core应用程序。这将构建和打包应用程序运行所需的所有内容。以下命令将应用程序从当前目录打包,并将其构建到名为publish的子文件夹中。我使用了Release配置,而不是默认的Debug配置,这样输出将被完全优化,以便在生产环境中运行:

dotnet publish --output publish --configuration Release

提示:发布应用程序进行部署时,始终使用发布配置。这确保编译器为应用程序生成优化的代码。

命令完成后,您将在发布文件夹中找到已发布的应用程序,如图16.3所示。

图16.3 运行dotnet发布后应用程序的发布文件夹。该应用程序仍然编译为一个.dll文件,但所有其他文件(如wwwroot和appsettings.json)也会复制到输出中。

如您所见,ExampleApp.dll文件以及一些其他文件仍然存在。最值得注意的是,发布过程在wwwroot文件夹中复制了静态文件。使用dotnet run在本地运行应用程序时,.NET CLI会直接使用应用程序项目文件夹中的这些文件。运行dotnet publish会将文件复制到输出目录,因此当您将应用程序部署到服务器时,这些文件会包含在内。
如果您的第一直觉是尝试使用您已经知道并喜爱的dotnet run命令在发布文件夹中运行应用程序,那么您会感到失望。应用程序不会启动,而是会出现一条有点令人困惑的消息:“找不到要运行的项目。”

要运行已发布的应用程序,需要使用稍微不同的命令。您必须将应用程序的.dll文件路径传递给dotnet命令,而不是调用dotnet run。如果您正在从publish文件夹运行命令,那么对于图16.3中的示例应用程序,它看起来像

dotnet ExampleApp.dll

这是在生产环境中运行应用程序时服务器将运行的命令。

在开发时,dotnet run命令会做大量工作,以使您更轻松:它确保您的应用程序已构建,在当前文件夹中查找项目文件,确定相应的.dll将位于何处(在bin文件夹中),最后运行应用程序。

在生产中,您不需要任何额外的工作。您的应用程序已构建;它只需要运行。dotnet<dll>语法单独执行此操作,因此应用程序启动速度更快。

注意:用于运行已发布应用程序的dotnet命令是.NET运行时的一部分,而用于在开发期间构建和运行应用程序的dotnet命令是.NET SDK的一部分。

依赖于框架的部署与自包含的部署
.NET核心应用程序可以以两种不同的方式部署:运行时相关部署(RDD)和自包含部署(SCD)。
大多数时候,您将使用RDD。这依赖于运行已发布应用程序的目标计算机上安装的.NET 5.0运行时,但您可以在任何平台(Windows、Linux或macOS)上运行应用程序,而无需重新编译。
相比之下,SCD包含运行应用程序所需的所有代码,因此目标计算机不需要安装.NET 5.0。相反,发布应用程序会将.NET 5.0运行时与应用程序的代码和库打包。
每种方法都有其优点和缺点,但在大多数情况下,我倾向于创建RDD。RDD的最终大小要小得多,因为它们只包含应用程序代码,而不是SCD包含的整个.NET5.0框架。此外,您可以将RDD应用程序部署到任何平台,而SCD必须专门针对目标计算机的操作系统进行编译,例如Windows 10 64位或Red Hat Enterprise Linux 64位。
也就是说,SCD部署非常适合于将应用程序与主机上的依赖关系隔离开来。SCD部署不依赖于宿主提供程序上安装的.NET版本,因此,例如,您可以在Azure App Service中使用.NET预览版本,而无需支持预览版本。
在本书中,为了简单起见,我只讨论RDD,但如果您想创建SCD,只需在发布应用程序时提供运行时标识符,在本例中为Windows 10 64位:
dotnet publish -c Release -r win10-x64 -o publish_folder
输出将包含一个.exe文件(这是您的应用程序)和一吨.dlls(对于示例应用程序,大约65 MB的.dlls),它们是.NET5.0框架。您需要将整个文件夹部署到目标计算机以运行应用程序。在.NET5.0中,可以在发布过程中修剪其中的一些程序集,但这在某些情况下会带来风险。有关详细信息,请参阅Microsoft的“.NET Core应用程序发布概述”文档,网址为https://docs.microsoft.com/dotnet/core/deploying/

我们已经确定,发布应用程序对于准备在生产环境中运行非常重要,但您如何部署它?你如何将文件从你的电脑上传到服务器上,以便人们可以访问你的应用程序?你有很多很多选择,所以在下一节中,我会给你一个简单的方法列表。

16.1.2 为应用程序选择部署方法

要将任何应用程序部署到生产环境中,通常有两个基本要求:

  • 可以运行应用程序的服务器
  • 将应用程序加载到服务器的方法

从历史上看,将应用程序投入生产是一个费力且容易出错的过程。对许多人来说,这仍然是事实。如果你在一家近年来没有改变做法的公司工作,你可能需要为你的应用程序请求一个服务器或虚拟机,并将你的应用提供给一个运营团队,由他们为你安装。如果是这种情况,您可能会对如何部署感到困惑。

对于那些接受了持续集成(CI)或持续交付/部署(CD)的人来说,还有更多的可能性。CI/CD是检测版本控制系统(例如,Git、SVN、Mercurial、Team Foundation版本控制)中的更改并自动构建应用程序并将其部署到服务器的过程,几乎无需人工干预。

注:这些术语之间存在重要但微妙的差异。Atlassian在这里有一篇很好的比较文章,“持续集成与持续交付与持续部署”:http://mng.bz/vzp4

你可以在那里找到许多不同的CI/CD系统:Azure DevOps、GitHub Actions、Jenkins、TeamCity、AppVeyor、Travis和Octopus Deploy等等。每个系统都可以管理部分或全部CI/CD过程,并可以与许多不同的系统集成。

我建议尝试一些可用的服务,看看哪种最适合你,而不是推动任何特定的系统。有些更适合于开源项目,有些在部署到云服务时更适合——这取决于您的具体情况。

如果您刚开始使用ASP.NET Core,并且不想经历使CI工作的设置过程,那么您仍然有很多选择。开始使用Visual Studio的最简单方法是使用内置部署选项。可以从Visual Studio通过Build>Publish AppName菜单选项获得这些选项,这将显示如图16.4所示的屏幕。

图16.4 Visual Studio 2019中的发布应用程序屏幕。这提供了将应用程序直接发布到Azure App Service、IIS、FTP站点或本地计算机上的文件夹的简单选项。

从这里,您可以将应用程序直接从Visual Studio发布到许多不同的位置。当你开始使用时,这是很好的,尽管我建议为更大的应用程序寻找一种更自动化和更可控的方法,或者当你有一个完整的团队在处理一个应用程序时。

注:有关选择Visual Studio发布选项的指导,请参阅Microsoft的“将应用程序部署到文件夹、IIS、Azure或其他目标”文档:http://mng.bz/4Z8j

考虑到这个领域中可用的可能性的数量以及这些选项的变化速度,我将在本章中重点介绍一个特定的场景:您已经构建了ASP.NET Core应用程序,您需要部署它。您可以访问已经使用IIS服务(以前版本)ASP.NET应用程序的Windows服务器,你想和他们一起运行ASP.NET Core应用程序。

在下一节中,您将看到使用IIS作为反向代理在生产环境中运行ASP.NET Core应用程序所需的步骤概述。它不会是配置IIS的大师级课程(20年的产品有太多的深度,我不知道从哪里开始!),但我将介绍获取应用程序服务请求所需的基本知识。

16.2 将应用程序发布到IIS

在本节中,我将简要介绍如何将第一个应用程序发布到IIS。您将向IIS添加应用程序池和网站,并确保您的应用程序具有作为反向代理与IIS一起工作所需的配置。部署本身很简单,只需将已发布的应用程序复制到IIS的宿主文件夹即可。

在第16.1节中,您了解了在部署应用程序之前发布应用程序的必要性,以及在生产环境中运行ASP.NET Core应用程序时使用反向代理的好处。如果要将应用程序部署到Windows,IIS将是您的反向代理,并负责管理应用程序。

IIS是一个古老而复杂的庞然大物,我不可能在本书中涵盖与配置它相关的所有内容。你也不想让我这样做——那会很无聊!相反,在本节中,我将概述在IIS之后运行ASP.NET Core的基本要求,以及您可能需要对应用程序进行的更改以支持IIS。

如果您使用的是Windows并希望在本地尝试这些步骤,则需要在开发计算机上手动启用IIS。如果你用旧版本的Windows做了这件事,没有什么变化。您可以在ASP.NET Core文档中找到配置IIS和疑难解答提示的分步指南,网址为 http://mng.bz/6g2R

16.2.1 为ASP.NET Core配置IIS

准备IIS托管ASP.NET Core应用程序的第一步是安装ASP.NET Core Windows Hosting Bundle,您可以从 http://mng.bz/opED 下载ASP.NET Core Windows Hosting Bundle。。这包括运行.NET应用程序所需的几个组件:

  • .NET运行时--运行.NET 5.0应用程序
  • ASP.NET Core运行时--运行ASP.NET Core应用程序所必需的
  • IIS ASP.NET Core模块--提供IIS和应用程序之间的链接,以便IIS可以充当反向代理

如果您要在开发机器上运行IIS,请确保您也安装了捆绑包,否则您会从IIS中收到奇怪的错误。

提示:Windows宿主捆绑包提供了在Windows上运行IIS背后的ASP.NET Core所需的一切。如果您在Linux或Mac上托管应用程序,或者在Windows上不使用IIS,则需要安装.NET运行时和ASP.NET Core运行时以运行依赖于运行时的ASP.NET Core应用程序。

安装捆绑包后,需要在IIS中为ASP.NET Core应用程序配置应用程序池。以前版本的ASP.NET将在使用.NET Framework的托管应用程序池中运行,但对于ASP.NET Core,您应该创建无托管代码池。本机ASP.NET Core模块在池中运行,池将引导.NET 5.0运行时本身。

定义:IIS中的应用程序池表示应用程序进程。您可以在单独的应用程序池中运行IIS中的每个应用程序,以使它们彼此隔离。

要创建非托管应用程序池,请右键单击IIS中的应用程序池并选择添加应用程序池。在结果对话框中提供应用程序池的名称,如NetCore,并将.NET CLR版本设置为无托管代码,如图16.5所示。

图16.5 在IIS中为ASP.NET Core应用程序创建应用程序池。.NET CLR版本应设置为“无托管代码”。

现在您有了应用程序池,您可以向IIS添加新网站。右键单击“站点”节点,然后选择“添加网站”。在AddWebsite对话框中,如图16.6所示,您提供了网站的名称和发布网站的文件夹的路径。我已经创建了一个文件夹,将用于部署前面章节中的Recipe应用程序。将应用程序的应用程序池更改为您创建的新NetCore应用程序池非常重要。在生产环境中,您还可以为应用程序提供主机名,但我现在将其留空,并将端口更改为81,因此应用程序将绑定到URLhttp://localhost:81.

注意:当您将应用程序部署到生产环境时,您需要向域注册商注册主机名,以便Internet上的用户可以访问您的站点。在IIS中配置应用程序时使用该主机名,如图16.6所示。

图16.6 为应用程序向IIS添加新网站。确保将应用程序池更改为上一步骤中创建的无托管代码池。您还可以提供名称、发布应用程序文件的路径以及IIS将用于应用程序的URL。

单击“确定”后,IIS将创建应用程序并尝试启动它。但您尚未将应用程序发布到文件夹中,因此您还无法在浏览器中查看它。

在发布和运行应用程序之前,您需要执行一个更关键的设置步骤:您必须授予NetCore应用程序池访问将发布应用程序的路径的权限。为此,右键单击将在Windows文件资源管理器中托管应用程序的文件夹,然后选择“财产”。在财产对话框中,选择安全>编辑>添加。在文本框中输入IIS AppPool\NetCore,如图16.7所示,其中NetCore是应用程序池的名称,然后单击OK。单击OK关闭所有对话框,即可完成设置。

图16.7 将NetCore应用程序池的权限添加到网站的发布文件夹。

开箱即用,ASP.NET Core模板被配置为与IIS无缝工作,但如果您从头开始创建应用程序,则可能需要进行一些更改。在下一节中,我将简要介绍您需要进行的更改,并解释为什么需要这些更改。

16.2.2 准备应用程序并将其发布到IIS

正如我在第16.1节中所讨论的,IIS充当ASP.NET Core应用程序的反向代理。这意味着IIS需要能够与您的应用程序直接通信,以将传入请求转发到应用程序和从应用程序传出响应。

IIS通过ASP.NET Core模块处理此问题,但IIS和您的应用程序之间需要进行一定程度的协商。要使其正常工作,您需要将应用程序配置为使用IIS集成。

使用默认模板中使用的IHostBuilder.ConfigureWebHostDefaults()帮助器方法时,默认情况下会添加IIS集成。如果您正在自定义自己的HostBuilder,则需要确保使用UseIIS()或UseIISIntegration()扩展方法添加IIS集成。

清单16.1向主机生成器添加IIS集成

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args)
        .ConfigureWebHost(webBuilder =>  //使用自定义生成器,而不是模板中使用的默认ConfigureWebHostDefaults
        {
            webBuilder.UseKestrel(); 
            webBuilder.UseStartup<Startup>(); 
            webBuilder.UseIIS(); //配置应用程序,以便与进程内托管模型的IIS一起使用
            webBuilder.UseIISIntegration(); //配置应用程序以用于具有进程外托管模型的IIS
        });
}

注意:如果您没有将应用程序与IIS一起使用,UseIIS()和UseIISIntegration()方法将不会对应用程序产生任何影响,因此无论如何都可以安全地包含它们。

IIS中的进程内托管与进程外托管
我在第16.1节中给出的反向代理描述假设您的应用程序在与反向代理本身独立的进程中运行。如果您在Linux上运行,并且在ASP.NET Core 3.0之前是IIS的默认设置,则会出现这种情况。
在ASP.NET Core 3.0中,ASP.NET Core默认情况下为部署到IIS的应用程序使用进程内托管模型。在此模型中,IIS将应用程序直接托管在IIS进程中,从而减少了进程间通信并提高了性能。
如果愿意,可以切换到带有IIS的进程外托管模型,这有时对解决问题很有用。Rick Strahl发表了一篇关于托管模型之间的差异、如何在它们之间切换以及每种模型的优点的优秀文章:“ASP.NET Core In-Process hosting on IIS with ASP.NET Core”http://mng.bz/QmEv
通常,您不必担心托管模式之间的差异,但如果您部署到IIS,则需要注意这一点。如果选择使用进程外托管模型,则应使用UseIISIntegration()扩展方法。如果使用进程内模型,请使用UseIIS()。或者,安全地使用这两种方法。正确的扩展方法将根据生产中使用的宿主模型激活。如果不使用IIS,这两个扩展都不起作用。

在IIS后面运行时,这些扩展方法将您的应用程序配置为与IIS配对,以便它可以无缝地接受请求。除其他外,扩展还执行以下操作:

  • 定义IIS用于将请求转发到应用程序的URL,并将应用程序配置为侦听此URL
  • 通过设置标头转发,配置应用程序将来自IIS的请求解释为来自客户端
  • 如果需要,启用Windows身份验证

添加IIS扩展方法是您需要对应用程序进行的唯一更改,以便能够托管在IIS中,但发布应用程序时还有一个需要注意的方面。

与以前版本的ASP.NET一样,IIS依赖Web.config文件来配置它运行的应用程序。应用程序在发布到IIS时包含Web.config文件很重要;否则,您可能会得到破坏行为,甚至暴露不应该暴露的文件。

  注:有关使用Web.config自定义IIS ASP.NET Core模块的详细信息,请参阅Microsoft的“ASP.NET Core模块”文档:http://mng.bz/Xdna

如果ASP.NET Core项目已经包含Web.config文件,则在发布应用程序时,.NET CLI或Visual Studio会将其复制到发布目录。如果应用程序不包含Web.config文件,publish命令将为您创建正确的文件。如果不需要自定义Web.config文件,通常最好不要在项目中包含Web.config文件并让CLI为您创建正确的文件。

通过这些更改,您终于可以将应用程序发布到IIS。通过运行

dotnet publish --output publish_folder --configuration Release

这将将应用程序发布到publish_folder文件夹。然后可以将应用程序复制到IIS中指定的路径,如图16.6所示。此时,如果一切顺利,您应该能够导航到为应用程序指定的 URL(http://localhost:81),如图16.8所示。

图16.8 使用IIS作为反向代理侦听URL的已发布应用程序http://localhost:81。

这就是第一个在反向代理后面运行的应用程序。尽管ASP.NET Core使用的托管模型与以前版本的ASP.NET不同,但配置IIS的过程是相似的。

正如在部署方面经常遇到的情况一样,您的成功在很大程度上取决于您的精确环境和应用程序本身。如果在完成这些步骤后,您发现无法启动应用程序,我强烈建议您查看以下文档:https://docs.microsoft.com/ASP.NET/core/publishing/iis。这包含许多疑难解答步骤,如果IIS决定发出嘶嘶声,可以让您回到正轨。

本节专门为部署到IIS而定制,因为它为已经习惯于部署ASP.NET应用程序并希望部署其第一个ASP.NET Core应用程序的开发人员提供了一个很好的选择。但这并不是说IIS是唯一或最好的宿主应用程序的地方。

在下一节中,我将简要介绍如何在Linux上托管应用程序,并使用反向代理(如NGINX或Apache)。我将不讨论反向代理本身的配置,但我将提供一个需要考虑的事项和可以用于在Linux上运行应用程序的资源的概述。

16.3 在Linux上托管应用程序

ASP.NET Core的一个伟大的新功能是能够跨平台开发和部署应用程序,无论是在Windows、Linux还是macOS上。特别是在Linux上运行的能力,为云主机的部署提供了更便宜的可能性,部署到树莓派(Raspberry Pi)或Docker容器等小型设备。

Linux的一个特点是几乎可以无限配置。尽管这绝对是一个功能,但它也可能非常令人畏惧,特别是如果您来自Windows向导和GUI世界。本节概述了在Linux上运行应用程序所需的内容。它侧重于您需要采取的广泛步骤,而不是配置本身的一些繁琐细节。相反,我将指出您可以根据需要参考的资源。

16.3.1 在Linux上反向代理后运行ASP.NET Core应用程序

您将很高兴听到在Linux上运行应用程序与在Windows上使用IIS运行应用程序大致相同:

  1. 使用dotnet发布发布应用程序。如果要创建RDD,输出与IIS使用的输出相同。对于SCD,必须提供目标平台名字,如第16.1.1节所述。
  2. 在服务器上安装必要的必备组件。对于RDD部署,必须安装.NET 5.0运行时和必要的必备组件。有关详细信息,请参阅Microsoft的“在Linux上安装.NET”文档,网址为 https://docs.Microsoft.com/en-gb/dotnet/core/Install/Linux
  3. 将应用程序复制到服务器。你可以使用任何你喜欢的机制:FTP,USB棒,任何你需要把文件放到服务器上的东西!
  4. 配置反向代理并将其指向应用程序。如您所知,出于第16.1节所述的原因,您可能希望在反向代理之后运行应用程序。在Windows上,您可以使用IIS,但在Linux上,您有更多的选择。NGINX、Apache和HAProxy都是常用的选项。
  5. 为应用程序配置流程管理工具。在Windows上,IIS同时充当反向代理和进程管理器,在应用程序崩溃或停止响应时重新启动应用程序。在Linux上,您通常需要配置一个单独的进程管理器来处理这些任务;反向代理不会为你做这件事。

前三点基本相同,无论您是在Windows上运行IIS还是在Linux上运行,但后两点更有趣。与单一的IIS不同,Linux具有小型应用程序的理念,每个应用程序都有一个单独的责任。

IIS与您的应用程序运行在同一台服务器上,并承担多项任务——代理从互联网到您的应用的流量,但也监控应用程序进程本身。如果应用程序崩溃或停止响应,IIS将重新启动进程,以确保您能够继续处理请求。

在Linux中,反向代理可能与您的应用程序运行在同一台服务器上,但它也很常见,完全运行在不同的服务器上,如图16.9所示。如果您选择将应用程序部署到Docker,这也是同样的道理;您的应用程序通常部署在没有反向代理的容器中,服务器上的反向代理将指向Docker容器。

图16.9 在Linux上,反向代理通常位于与应用程序不同的服务器上。反向代理会将传入的请求转发到应用程序,而进程管理器(如systemd)会监视应用程序是否崩溃,并在适当时重新启动应用程序。

由于反向代理不一定与应用程序位于同一服务器上,因此如果应用程序崩溃,则无法使用它们重新启动应用程序。相反,您需要使用诸如systemd之类的流程管理器来监控应用程序。如果您使用Docker,通常会使用Kubernetes等容器编排器(https://kubernetes.io)以监控容器的健康状况。

在Docker中运行ASP.NET Core应用程序
Docker是最常用的容器化应用程序的引擎。容器就像一个小型、轻量级的虚拟机,特定于您的应用程序。它包含操作系统、应用程序以及应用程序的任何依赖项。然后,这个容器可以在任何运行Docker的机器上运行,无论主机操作系统和上面安装了什么,你的应用程序都将完全相同地运行。这使得部署具有高度可重复性:你可以确信,如果容器在你的机器上,它也会在服务器上运行。
ASP.NET Core非常适合容器部署,但迁移到Docker会导致部署方法的重大转变,可能适合您和您的应用程序,也可能不适合。如果您对Docker提供的可能性感兴趣并想了解更多信息,我建议查看以下资源:

    • Ian Miell和Aidan Hobson Sayers(Manning,2019)的《Docker in Practice》第2版提供了大量实用技术,帮助您充分利用Docker(http://mng.bz/nM8d)。
    • 即使您没有部署到Linux,也可以使用Docker和Docker for Windows。查看免费电子书,John McCabe和Michael Friis的《Introduction to Windows Containers 》(微软出版社,2017)https://aka.ms/containersebook
    • 您可以在.NET文档中找到有关在Docker上构建和运行ASP.NET Core应用程序的大量详细信息,网址为 http://mng.bz/vz5a
    • Steve Gordon在Docker上为ASP.NET Core开发人员撰写了一篇优秀的博客文章 https://www.stevejgordon.co.uk/docker-dotnet-developers

配置这些系统是一项费力的任务,这会导致枯燥乏味的阅读,因此我在这里不再详细介绍它们。相反,我建议查看ASP.NET Core文档。他们有一个NGINX和systemd指南,“使用NGINX的Linux上的主机ASP.NET Core”(http://mng.bz/yYGd),以及使用systemd配置Apache的指南,“Host ASP.NET Core on Linux with Apache”(http://mng.bz/MXVB)。

这两个指南都介绍了各自反向代理和systemd监控器的基本配置,但更重要的是,它们还介绍了如何安全地配置它们。反向代理位于您的应用程序和不受限制的互联网之间,因此正确使用它很重要!

配置反向代理和进程管理器通常是部署到Linux中最复杂的部分,而这并不特定于.NET开发:如果您部署的是Node.js Web应用程序,也是如此。但是当您要部署到Linux时,您需要考虑应用程序中的一些事情,这将在下一节中看到。

16.3.2 准备应用程序以部署到Linux

一般来说,无论是NGINX、Apache还是IIS,您的应用程序都不在乎它位于哪个反向代理后面——您的应用会接收请求并对其做出响应,而反向代理不会影响任何事情。当您在IIS后面托管时,需要添加UseIISIntegration();类似地,当您在Linux上托管时,您需要在应用程序设置中添加一个扩展方法。

当请求到达反向代理时,它包含一些在请求转发到应用程序后丢失的信息。例如,原始请求带有连接到应用程序的客户端/浏览器的IP地址:一旦从反向代理转发请求,IP地址就是反向代理的IP地址,而不是浏览器的IP。此外,如果反向代理用于SSL卸载(参见第18章),那么最初使用HTTPS发出的请求可能会作为HTTP请求到达您的应用程序。

这些问题的标准解决方案是反向代理在将请求转发到应用程序之前添加额外的标头。例如,X-Forwarded-For标头标识原始客户端的IP地址,而X-Forwarded-Proto标头指示请求的原始方案(http或https)。

为了让您的应用程序正常运行,它需要在传入请求中查找这些标头,并根据需要修改请求。请求http://localhost如果X-Forwarded-Proto标头设置为https,则应将其视为请求https://localhost.

您可以在中间件管道中使用ForwardedHeadersMiddleware来实现这一点。此中间件覆盖HttpContext上的Request.Scheme和其他财产,以对应转发的头。如果您使用的是Program.cs中的默认Host.CreateDefaultBuilder()方法,则这是部分为您处理的——中间件将以禁用状态自动添加到管道中。要启用它,请设置环境变量ASP.NET Core_FORWARDEDHEADERS_ENABLED=true。

如果您使用的是自己的HostBuilder实例,而不是默认的构建器,则可以手动将中间件添加到中间件管道的开头,如清单16.2所示,并使用要查找的标头对其进行配置。

警告:在运行依赖于方案的任何中间件之前,将ForwardedHeadersMiddleware提前放置在中间件管道中以更正Request.Scheme是很重要的。

清单16.2 配置应用程序以使用Startup.cs中的转发头

public class Startup
{
    public class Configure(IApplicationBuilder app)
    {
        app.UseForwardedHeaders(new ForwardedHeadersOptions  //在管道早期添加ForwardedHeadersMiddleware
        {
            //配置中间件应查找和使用的标头
            ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
        });
        //转发的头中间件必须放在所有其他中间件之前。 
        app.UseHttpsRedirection(); 
        app.UseRouting();
        app.UseAuthentication(); 
        app.UseMvc();
    }
}

注意:此行为不特定于Linux上的反向代理;当您的应用程序在IIS后面运行时,UseIis()扩展将ForwardedHeadersMiddleware添加到引擎罩下,作为其配置的一部分。

除了考虑转发的标头,在将应用程序部署到Linux时,您还需要考虑一些小问题,如果您习惯于单独部署到Windows,这些问题可能会让您感到困惑:

  • 行尾(Linux上的LF与Windows上的CRLF)——Windows和Linux在文本中使用不同的字符代码来表示行尾。对于ASP.NET Core应用程序来说,这通常不是一个问题,但如果您在一个平台上编写文本文件,并在另一平台上阅读它们,则需要记住这一点。
  • 路径目录分隔符(Windows上为“\”,Linux上为“/”)——这是Windows开发人员迁移到Linux时我看到的最常见的错误之一。每个平台在文件路径中使用不同的分隔符,因此,当使用“subdir\myfile.json”路径加载文件时,在Windows上可以正常工作,但在Linux上则不行。相反,您应该使用Path.Combine为当前平台创建适当的分隔符;例如,Path.Combine(“subdir”,“myfile.json”)。
  • 环境变量不能包含“:”——在某些Linux发行版中,环境变量中不允许使用冒号字符(:)。正如您在第11章中看到的,这个字符通常用于表示ASP.NET Core配置中的不同部分,因此您通常需要在环境变量中使用它。相反,您可以在环境变量()中使用双下划线,ASP.NET Core会像使用冒号一样对待它。
  • 时区和区域性数据可能丢失——Linux发行版并不总是带有时区或区域性数据,这可能会在运行时导致本地化问题和异常。您可以使用发行版的包管理器安装时区数据。我自己也遇到了这个问题。你可以在我的博客上详细了解这个问题以及我是如何解决它的:http://mng.bz/aoem
  • 目录结构不同——Linux发行版使用的文件夹结构与Windows完全不同,因此如果应用程序硬编码路径,您需要记住这一点。特别是,请考虑临时/缓存文件夹中的差异。

前面的列表并非详尽无遗,但只要您设置ForwardedHeadersMiddleware并注意使用Path。Combine这样的跨平台构造,在Linux上运行应用程序就不会有太多问题。但配置反向代理并不是最简单的活动,因此无论您打算托管应用程序,我建议您查看文档以获取指导 https://docs.microsoft.com/ASP.NET/core/publishing

16.4 为应用程序配置URL

此时,您已经部署了一个应用程序,但有一个方面尚未配置:应用程序的URL。当您使用IIS作为反向代理时,您不必在应用程序中担心这一点。IIS与ASP.NET Core模块的集成通过动态创建用于在IIS和应用程序之间转发请求的URL来实现。在IIS中配置的主机名(如图16.6所示)是外部用户看到的应用程序的URL;IIS在转发请求时使用的内部URL从未公开。

如果你没有使用IIS作为反向代理——也许你使用的是NGINX或者将你的应用直接暴露在互联网上——你可能会发现你需要配置你的应用程序直接监听的URL。

默认情况下,ASP.NET Core将侦听URL上的请求http://localhost:5000.有很多方法可以设置此URL,但在本节中,我将描述两种方法:使用环境变量或使用命令行参数。这是我看到的两种最常见的方法(在IIS之外),用于控制应用程序使用的URL。

提示:有关设置应用程序URL的更多方法,请参阅我的“5种方法设置ASP.NET Core应用程序的URL”博客条目:http://mng.bz/go0v

在第10章中,您了解了ASP.NET Core中的配置,特别是托管环境的概念,以便在开发中运行时可以使用与生产环境不同的设置。您可以通过在计算机上设置名为ASP.NET Core_environment的环境变量来选择宿主环境。ASP.NET Core框架在应用程序启动时神奇地获取此变量,并使用它设置宿主环境。

您可以使用类似的特殊环境变量来指定应用程序使用的URL;此变量称为ASP.NET Core_URLS。当应用程序启动时,它会查找此值并将其用作应用程序的URL。通过更改此值,可以更改计算机上所有ASP.NET Core应用程序使用的默认URL。

例如,可以使用

set ASP.NET Core_URLS=http://localhost:8000

如图16.10所示,在同一命令窗口中使用dotnet<app.dll>运行已发布的应用程序,表明应用程序现在正在侦听ASP.NET Core_URLS变量中提供的URL。

图16.10 更改ASP.NET Core_URLS环境变量以更改ASP.NET Core应用程序使用的URL。

您可以通过用分号分隔多个URL来指示应用程序侦听多个URL,也可以在不指定localhost主机名的情况下侦听特定端口。如果将ASP.NET Core_URLS环境变量设置为

http://localhost:5001;http://*:5002

您的ASP.NET Core应用程序将侦听发送到以下位置的请求:

  • http://localhost:5001——此地址只能在您的本地计算机上访问,因此它不会接受来自更广泛互联网的请求。
  • http://*:5002——端口5002上的任何URL。来自互联网的外部请求可以使用映射到计算机的任何URL访问端口5002上的应用程序。

请注意,不能指定其他主机名,例如tastyrecipes.com。ASP.NET Core将侦听给定端口上的所有请求。例外是localhost主机名,它只允许来自您自己计算机的请求。

注意:如果您发现ASP.NET Core_URLS变量工作不正常,请确保目录中没有launchSettings.json文件。如果存在,则此文件中的值优先。默认情况下,launchSettings.json不包含在发布输出中,因此这在生产中通常不会成为问题。

在某些情况下,使用单个环境变量设置应用程序的URL非常有用,尤其是在虚拟机或Docker容器中运行单个应用程序时。

注:ASP.NET Core非常适合在容器中运行,但使用容器本身就是一本单独的书。有关使用Docker托管和发布应用程序的详细信息,请参阅Microsoft的“Docker容器中的Host ASP.NET Core”文档:http://mng.bz/e5GV

如果你没有使用Docker容器,那么你很可能在同一台机器上并行托管多个应用程序。在这种情况下,单个环境变量不适合设置URL,因为它会更改每个应用程序的URL。

在第11章中,您看到可以使用ASP.NET Core_environment变量设置宿主环境,但也可以在调用dotnet run时使用--environment标志设置环境:

dotnet run --no-launch-profile --environment Staging

您可以使用--URLs参数以类似的方式设置应用程序的URL。使用命令行参数可以使多个ASP.NET Core应用程序在同一台计算机上运行,并侦听不同的端口。例如,以下命令将运行配方应用程序,将其设置为侦听端口8081,并将环境设置为staging,如图16.11所示:

dotnet RecipeApplication.dll --urls "http://*:8081" --environment Staging

图16.11 使用命令行参数设置应用程序的宿主环境和URL。在命令行传递的值覆盖从appSettings.json或环境变量提供的值。

记住,如果您使用IIS作为反向代理,则不需要以这种方式设置URL;IIS集成为您处理此问题。只有当您手动配置应用程序监听的URL时,才需要设置URL;例如,如果您正在使用NGINX或直接向客户展示Kestrel。

警告:如果您在没有反向代理的情况下运行ASP.NET Core应用程序,出于安全原因,您应该使用主机筛选,以确保您的应用程序只响应您期望的主机名请求。有关更多详细信息,请参阅我的“在ASP.NET Core中向Kestrel添加主机过滤”博客条目:http://mng.bz/pVXK

继续与部署相关的任务主题,在下一节中,我们将了解如何优化一些客户端资产以用于生产。如果您正在构建一个Web API,这不是您在ASP.NET Core应用程序中需要担心的问题,但对于传统的Web应用程序,这是值得考虑的。

16.5 使用BundlerMinifier优化客户端资产

在本节中,我们将探讨ASP.NET Core应用程序在请求数量和大小方面的性能。您将看到如何使用捆绑和缩小来提高应用程序的性能,但确保在构建应用程序时仍易于调试。最后,我们将介绍一种在生产中提高应用程序性能的常见技术:使用内容交付网络(CDN)。

你是否使用过一个网络应用程序或打开过一个似乎需要很长时间才能加载的网页?一旦你偏离了亚马逊、谷歌或微软的老路,当网页慢慢弹出时,你就会被困在拇指上,这只是时间问题。

下次发生这种情况时,打开浏览器开发工具(例如,在Edge或Chrome中按F12),查看Web应用程序发出的请求数量和大小。在许多情况下,生成大量响应的大量请求将导致网页加载缓慢。

我们将首先通过查看配方应用程序中的一个页面:登录页面来探讨性能问题。这是一个简单的页面,它本身并不慢,但即使这样也足以研究请求大小的影响。

作为用户,当您单击“登录”按钮时,浏览器会向/Identity/Account/Login发送请求。ASP.NET Core应用程序在默认UI中执行Login.cshtml Razor页面,该页面执行Razor模板并在响应中返回生成的HTML,如图16.12所示。这是一个单一的请求响应——一次往返。

图16.12 加载应用程序的完整页面。初始请求返回页面的HTML,但这可能包括指向其他资源的链接,例如CSS和JavaScript文件。在页面完全加载之前,浏览器必须向您的应用程序请求所有未完成的资源。

但这对网页来说并不重要。页面返回的HTML包括指向CSS文件(用于设置页面样式)、JavaScript文件(用于客户端功能——在本例中为客户端表单验证)以及可能的图像和其他资源的链接(尽管此配方应用程序中没有其他资源)。

浏览器必须请求这些附加文件中的每一个,并等待服务器在加载整个页面之前返回它们。当您在本地开发时,这一切都会很快发生,因为请求不远,但一旦您将应用程序部署到生产环境,这就另当别论了。

用户将从服务器的不同距离和不同的网络速度从应用程序请求页面。突然,应用程序请求和响应的数量和大小将对应用程序的整体感知速度产生巨大影响。反过来,这会对用户如何看待您的网站,甚至对电子商务网站,甚至他们花了多少钱产生重大影响。

注:对此进行了大量的研究,包括诸如“页面加载时间延迟0.1秒等于转换损失7%”之类的统计数据 http://mng.bz/4Z2Q。

探索您的应用程序在非最佳网络中的行为方式的一个好方法是使用Chrome和Edge开发工具中的网络节流功能。这模拟了与不同类型网络相关的延迟和网络速度,因此您可以了解应用程序在野外的行为。在图16.13中,我加载了菜谱应用程序的登录页面,但这次网络设置为适度的快速3G速度。

图16.13 探讨网络速度对应用程序加载时间的影响。Chrome和Edge让您模拟一个较慢的网络,这样您就可以在应用程序投入生产后,对用户加载应用程序时的体验产生印象。

注意:我在模板中添加了其他文件——navigation.css和global.js,以使页面更能代表真实的应用程序。

限制网络不会改变页面或请求的数据的任何内容——有10个单独的请求,每个页面几乎加载了1MB——但这会显著影响页面的加载时间。在没有限制的情况下,登录页面在200毫秒内本地加载;通过快速3G节流,登录页面需要5.47秒才能加载!

注意:不要对这些数字过于惊慌。为了强调这一点,我在每次请求时都会重新加载所有文件,而实际上,浏览器会尽可能地缓存文件,以避免发送这么多数据。

完全加载应用程序页面所需的时间主要基于两个方面:

  • 回答的总大小--这是一个直接了当的数学;您只能以一定的速度返回数据,因此需要返回的数据越多,所需的时间就越长。
  • 请求的数量——通常,浏览器必须发出的请求越多,完全加载页面所需的时间就越长。在HTTP/1.0和HTTP/1.1中,您只能向服务器发出六个并发请求,因此第六个请求之后的任何请求都必须等待更早的请求完成,然后才能启动。Kestrel支持的HTTP/2.0没有这个限制,但不能总是依赖使用它的客户端。

假设您所服务的所有文件都是必需的,您如何提高应用程序的速度?一般来说,这是一个有很多可能性的大主题,例如使用CDN来提供静态文件。提高网站性能的两个最简单的方法是使用捆绑和缩小,以减少浏览器加载应用程序所需的请求数量和大小。

16.5.1 使用捆绑和缩小功能加速应用程序

在图16.13中,对于配方应用程序,您总共向服务器发出了10个请求:

  • HTML的一个初始请求
  • CSS文件的三个请求
  • 六个JavaScript文件请求

一些CSS和JavaScript文件是标准的供应商库,如Bootstrap和jQuery,它们作为默认Razor模板的一部分,一些(navigation.CSS、site.CSS、global.js和site.js)是特定于应用程序的文件。在本节中,我们将研究如何优化自定义CSS和JavaScript文件。

如果您正在尝试减少应用程序的请求数量,一个明显的首要考虑是避免首先创建多个文件。例如,您可以使用包含所有css的单个文件,而不是将其分开,而不是创建navigation.css文件和site.css文件。

这是一个有效的解决方案,但将所有代码放在一个文件中可能会使管理和调试更加困难。作为开发人员,我们通常会尽量避免这种怪物文件。更好的解决方案是将代码拆分为所需数量的文件,然后在部署代码时打包这些文件。

定义:捆绑是将多个文件连接到一个文件中以减少请求数量的过程。

类似地,在编写JavaScript时,应该使用描述性变量名称、注释(必要时)和空格来创建易于阅读和可调试的代码。当您开始部署脚本时,您可以处理并优化它们的大小,而不是可读性。这个过程称为缩小。

定义:小型化涉及处理代码以减少其大小而不改变代码的行为。处理有许多不同的级别,通常包括删除注释和空格,并且可以扩展到重命名变量以给它们提供更短的名称,或者如果它们未使用,则完全删除整个代码段。

例如,请查看以下列表中的JavaScript。这个(非常做作的)函数将一些数字相加并返回。它包含(过度)描述性变量名、注释和大量空格,但它代表了您在自己的应用程序中可能找到的JavaScript。

清单16.3 缩小前的JavaScript函数示例

function myFunc() {
// 这个函数实际上什么都不做,它就在这里,这样我们就可以展示缩小效果了!
    function innerFunctionToAddTwoNumbers(thefirstnumber, theSecondNumber) {
        // 我嵌套在myFunc中
        return thefirstnumber + theSecondNumber;
    }
    var shouldAddNumbers = true; 
    var totalOfAllTheNumbers = 0;

    if (shouldAddNumbers == true) {
        for (var index = 0; i < 10; i++) { totalOfAllTheNumbers =
            innerFunctionToAddTwoNumbers(totalOfAllTheNumbers,  index);
        }
    }
    return totalOfAllTheNumbers;
}

这个函数在当前写入时总共需要588个字节,但在缩小后,它减少到了95个字节,即原始大小的16%。代码的行为是相同的,但输出(如下面的列表所示)经过优化以减小其大小。这显然不是你想要调试的:你只会在生产中使用文件的缩小版本;在开发时,您将使用原始源文件。

if(1)for(t=0;i<10;i++)n=r(n,t);return n}

当您将应用程序部署到生产环境时,使用捆绑和缩小优化静态文件可以免费提高应用程序的性能,同时还可以使用易于阅读和分离的文件进行开发。

图16.14显示了绑定和缩小配方应用程序登录页面文件的影响。每个供应商文件都已缩小以减小其大小,自定义资产也已打包并缩小以减小它们的大小和请求数量。这将请求数从10减少到8,数据总量从580 KB减少到270 KB,加载时间从6.45秒减少到3.15秒。

图16.14 通过捆绑和缩减客户端资源,可以减少所需的请求数量和要传输的数据总量,从而显著提高性能。在本例中,捆绑和缩小将完全加载的时间缩短了一半。

注意:如图16.14所示,供应商资产(如jQuery和Bootstrap)未与自定义脚本绑定。这使您可以从CDN加载这些文件,正如我将在第16.5.4节中所述。

这种性能改进可以在您不费吹灰之力的情况下实现,而且不会对您的开发过程产生影响。在下一节中,我将展示如何将捆绑和缩小作为ASP.NET Core构建过程的一部分,以及如何为应用程序定制捆绑和缩小过程。

16.5.2 向应用程序添加BundlerMinifier

捆绑和缩小并不是一个新的想法,因此您有很多方法可以实现相同的结果。以前版本的ASP.NET在托管代码中执行捆绑和缩小,而诸如gulf、Grunt和Webpack之类的JavaScript任务运行器通常用于这些类型的任务。事实上,如果您正在编写SPA,您几乎可以肯定已经在执行捆绑和缩小。

ASP.NET Core支持通过名为BuildBundlerMinifier的NuGet软件包或名为Bundler&Miniifier的Visual Studio扩展版本进行捆绑和缩小。你不必使用这两种工具,如果你已经在使用gulf或Webpack等其他工具,我建议你继续使用它们。但如果你正在开始一个新项目,我建议考虑BundlerMinifier;您可以稍后切换到其他工具。
向项目中添加BundlerMinifier有两个选项:

  • 您可以从“工具”>“扩展和更新”安装Bundler&Minifier Visual Studio扩展。
  • 您可以将BuildBundlerMinifier NuGet包添加到项目中。

无论使用哪种方法,它们都使用相同的底层BundlerMinifier库。我更喜欢使用NuGet包方法,因为它是跨平台的,并且会自动为您绑定资源,但该扩展对于执行临时绑定非常有用。如果您确实使用了VisualStudio扩展,请确保在构建时启用Bundle,如稍后所见。

注:BundlerMinifier库和扩展在GitHub上是开源的:https://github.com/madskristensen/BundlerMinifier/

您可以使用以下命令在项目中安装BuildBundlerMinifier NuGet包:

dotnet add package BuildBundlerMinifier

安装了BuildBundlerMinifier包后,无论何时构建项目,BundlerMinifier都会检查CSS和JavaScript文件的更改。如果发生了变化,将创建新的捆绑和缩小文件,如图16.15所示,我在其中修改了一个JavaScript文件。

图16.15 每当构建项目时,BuildBundlerMinifier工具都会在输入文件中查找更改,并根据需要构建新的包。

如图16.15所示,bundler缩小了JavaScript代码,并在wwwroot/js/site.min.js创建了一个新文件?嗯,因为你在bundleconfig.json中告诉过它。你将这个json文件添加到项目的根文件夹中,它控制BundlerMinifier进程。

清单16.5显示了一个小型应用程序的典型bundleconfig.json配置。这定义了每个捆绑包中要包含的文件、写入每个捆绑包的位置以及要使用的缩小选项。这里配置了两个捆绑包:一个用于CSS,一个用于JavaScript。您可以向JSON数组添加捆绑包,也可以自定义现有提供的捆绑包。

"Development"> //仅在开发环境中运行时渲染这些链接。 <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> //Bootstrap的开发版本 <link rel="stylesheet" href="~/css/navigation.css" /> //您的样式的开发版本 <link rel="stylesheet" href="~/css/site.css" /> </environment> <environment exclude="Development"> //仅在不处于“开发”(如“暂存”或“生产”)时渲染这些链接。 <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> //Bootstrap的缩小版本 <link rel="stylesheet" href="~/css/site.min.css" /> //您的样式的捆绑版和缩小版 </environment>

当应用程序检测到它没有在开发宿主环境中运行时(正如您在第11章中所了解的),它将切换到优化文件的渲染链接。这为您提供了两个方面的最佳选择:生产性能和易于开发。

清单16.6中的示例还包括Bootstrap的缩小版本,尽管您没有将其配置为BundlerMinifier的一部分。CSS和JavaScript库(如Bootstrap)通常会包含文件的预缩小版本,供您在生产中使用。对于那些这样做的人,通常最好将其从捆绑过程中排除,因为这允许您从公共CDN中潜在地提供文件。

16.5.4 从CDN提供通用文件

公共CDN是一个托管常用文件的网站,例如Bootstrap或jQuery,您可以从自己的应用程序中引用这些文件。它们有几个优点:

  • 它们通常速度很快。
  • 它们使您的服务器不必为文件服务,从而节省了带宽。
  • 由于该文件是从不同的服务器提供的,因此它不计入HTTP/1.0和HTTP/1.1.9中允许向服务器发送的六个并发请求,这个限制不是一成不变的,但现代浏览器都使用相同的限制。请参阅Push Technology的“浏览器连接限制”一文:http://mng.bz/OEWw
  • 许多不同的应用程序可以引用同一个文件,因此访问您的应用程序的用户可能已经通过访问不同的网站缓存了该文件,他们可能根本不需要下载该文件。

原则上很容易使用CDN:引用CDN文件而不是自己服务器上的文件。不幸的是,您需要考虑这样一个事实,即与任何服务器一样,CDN有时也会失败。如果您没有从其他位置(如服务器)加载文件的回退机制,这可能会导致应用程序看起来损坏。

幸运的是,ASP.NET Core包括几个标记帮助程序,以使使用CDN和回退更容易。清单16.7显示了如何更新CSS环境标记帮助程序,以便在生产环境中运行时从CDN提供引导,并包括回退。回退测试创建一个临时HTML元素并对其应用Bootstrap样式。如果该元素具有预期的CSS财产,则回退测试通过,因为必须加载Bootstrap。如果它没有所需的财产,则将从替代的本地链接加载Bootstrap。

"Development"> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="~/css/navigation.css" /> <link rel="stylesheet" href="~/css/site.css" /> </environment> <environment exclude="Development"> //默认情况下,Bootstrap是从CDN加载的。 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" asp-fallback-test-class="sr-only" //回退测试将sr-only类应用于元素,并检查元素是否具有绝对的CSS位置。这表明Bootstrap已加载。 asp-fallback-test-property="position" asp-fallback-test-value="absolute" asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> //如果回退检查失败,则CDN肯定失败了,因此从本地链接加载Bootstrap。 <link rel="stylesheet" href="~/css/site.min.css" /> </environment>

在将应用程序投入生产之前,优化静态文件是一个重要的步骤,因为它会对性能产生重大影响。幸运的是,BuildBundlerMinifier包可以轻松优化CSS和JavaScript文件。如果您将其与CDN中的常见文件相结合,您的应用程序将为生产中的用户提供尽可能快的速度。

这就结束了本章关于发布应用程序的内容。应用程序开发的最后一公里是将应用程序部署到用户可以访问的服务器,这是一个众所周知的棘手问题。发布ASP.NET Core应用程序很容易,但大量可用的托管选项使得为每种情况提供简洁的步骤变得困难。

无论您选择哪种托管选项,有一个关键主题经常被忽略,但对于快速解决问题至关重要:日志记录。在下一章中,您将了解ASP.NET Core中的日志抽象,以及在应用程序投入生产后如何使用它们来监视应用程序。

总结

  • ASP.NET Core应用程序是自托管Web服务器的控制台应用程序。在生产中,通常使用反向代理,它处理初始请求并将其传递给应用程序。反向代理可以提供额外的安全性、操作和性能优势,但也会增加部署的复杂性。
  • .NET有两个部分:.NET SDK(也称为.NET CLI)和.NET运行时。在开发应用程序时,可以使用.NET CLI还原、构建和运行应用程序。Visual Studio在IDE中使用相同的.NET CLI命令。
  • 当您想将应用程序部署到生产环境时,需要使用dotnet发布发布应用程序。这将创建一个文件夹,其中包含作为DLL的应用程序及其所有依赖项。
  • 要运行已发布的应用程序,您不需要.NET CLI,因为您不需要构建应用程序。您只需要.NET运行时即可运行已发布的应用程序。您可以使用dotnet app.dll命令运行已发布的应用程序,其中app.dll是由dotnet publish命令创建的application.dll。
  • 要在IIS中托管ASP.NET Core应用程序,必须安装ASP.NET Core模块。这允许IIS充当ASP.NET Core应用程序的反向代理。您还必须安装.NET运行时和ASP.NET Core运行时,它们是作为ASP.NET Core Windows宿主捆绑包的一部分安装的。
  • IIS可以使用两种模式之一托管ASP.NET Core应用程序:进程内和进程外。进程外模式将应用程序作为一个单独的进程运行,这是大多数反向代理的典型模式。进程内模式将应用程序作为IIS进程的一部分运行。这具有性能优势,因为不需要进程间通信。
  • 要准备应用程序以使用ASP.NET Core发布到IIS,请确保在WebHostBuilder上调用UseIISIntegration()和UseIIS()。ConfigureWebHostDefaults静态助手方法会自动执行此操作。
  • 使用.NET CLI发布应用程序时,Web.config文件将添加到输出文件夹中。发布到IIS时,此文件必须与应用程序一起部署,因为它定义了应用程序的运行方式。
  • 默认情况下,应用程序将侦听的URL是使用环境变量ASP.NET Core_URLS指定的。设置此值将更改计算机上所有应用的URL。或者,在运行应用程序时传递--urls命令行参数;例如,dotnet app.dll--urls http://localhost:80。
  • 优化客户端资产以减少页面加载时客户端Web浏览器必须下载的文件的大小和数量非常重要。您可以通过捆绑和缩小资产来实现这一点。
  • 您可以使用BuildBundlerMinifier包将多个JavaScript或CSS文件组合在一起,形成一个称为绑定的过程。您可以在一个称为缩小的过程中减小文件的大小,在该过程中,删除不必要的空格并重命名变量,同时保留文件的功能。
  • 您可以安装Visual Studio扩展来控制绑定和缩小,也可以安装BuildBundlerMinifier包来自动对项目的每个生成执行绑定和缩小。使用该扩展允许您临时缩小,但使用NuGet包允许您自动化该过程。
  • 捆绑和缩小的设置存储在bundleconfig.json文件中,您可以在其中定义不同的输出捆绑包文件,并选择要包含在捆绑包中的文件。您可以显式指定文件,也可以使用通配符使用globbing模式包含多个文件。Globing通常更简单,更不容易出错,但如果必须按特定顺序捆绑文件,则需要明确指定文件。
  • 使用环境标记帮助程序仅在生产环境中有条件地渲染捆绑包。这使您可以优化生产中的性能和开发中的可读性。
  • 对于多个应用程序共享的常见文件,例如jQuery或Bootstrap,您可以从CDN提供文件。这些是托管公共文件的网站,因此您的应用程序不需要自己为它们服务。
  • 使用链接和脚本标记帮助程序检查文件是否已从CDN正确加载。这些功能可以测试客户端是否下载了文件,并确保您的服务器在CDN失败时用作备用。