使用 Web API ASP.NET Core 2.2 部署 Angular 8 应用程序





5.00/5 (2投票s)
这是一篇实用的文章,它提供了一个分步指南,描述了在部署带有 ASP.NET Core 后端和 Angular 前端的应用程序时遇到的问题和解决方案,面向开发者和其他初学者。
部署场景概述
在开始这个部署过程(一个带有 Angular 应用的 ASP.NET Core Web 应用程序)时,我通过试错法度过了几天,发现了后端和前端在虚拟机(Microsoft Azure 虚拟机)中协同工作所需的许多东西。部署这两种技术(ASP.NET Core 和 Angular)需要很多细节。因此,我决定创建一些主题,以便我能记住并在未来的项目中复制。这些主题不断增加,我对这些技术协同工作的挫败感也随之增加。于是,我萌生了写一篇帖子的想法,旨在以一种所有人都易于理解的方式组织所有已执行的任务,并在此处尽可能多地提供相关信息。本文包含了我(和我一样)这类初学者部署类似应用程序所需的基本活动。
有很多文档需要阅读才能对这里执行的实践有一个概念性的理解,本文的目标不是解释它们。在本文中,将提供一些链接,以提供更大的技术支持,因为确实出现了一些问题。
引用这篇帖子没有什么特别之处或创新之处,我相信它的价值在于将有关提到的技术的各种信息和测试汇集在一个地方。
我是一名有几年经验的开发人员,但并非基础设施领域的人员。因此,一些专家(DevOps)在看到我采取的措施时可能会“皱眉头”,这些措施遵循问题出现时的时间顺序。尽管我已经部署过几个其他 ASP.NET 应用程序,但我低估了 ASP.NET Core 的部署过程,它比旧版本的 ASP.NET 复杂得多。我相信有很多开发人员,像我一样,希望并需要了解构建应用程序的端到端工作流程。这些专业人员面临的一个大问题是 **知道哪些配置职责属于后端安装,哪些属于前端**。很多时候,人们不知道问题应该在后端修复,还是在前端通过 Web 服务器配置(在本例中为 IIS)修复。这种情况经常发生,非常难以处理,不知道问题的根源从何开始。
从理论到实践
这是一个相对简单的应用程序,从一开始就采取的策略是 **使用最简单的安装方式进行部署。** 随着我需要一些额外的配置,我会根据技术文档和其他专业人士的意见采取行动。但是,尽管这是一个简单的应用程序,它并不是一个“Hello World
”,因此在通过 UI 安全地访问数据、访问数据库、用户身份验证等方面存在各种问题和疑虑,但最主要的是 **我们有两个独立的应用程序在互相通信。** 因此,过程始于 Web API 的发布和已发布文件的复制到 Windows 2012 R2 服务器,以及 Angular 构建和软件包的复制到同一服务器。
Web API 应用程序(后端)使用 ASP.NET Core 2.2 实现,使用 EntityFramework Core 2.2.6 进行数据持久化,在一个 SQL Server Express 数据库中。还实现了 Json Web Tokens - JWT 来对用户进行 API 身份验证,以及使用 CORS,它防止其他未经授权的 Web 页面对我们应用程序页面域发出请求。
前端使用 Angular 8.2.3 实现,使用 rxjs 6.4.0 实现可观察对象以订阅 Web API 的端点(GET
、POST
、PUT
、DELETE
、PATCH
方法)。这是一个用 typescript 3.5 实现的简单应用程序,带有 4.3.1 bootstrap 和其他界面插件(ngx-bootstrap、Angular material、npmjs 等),甚至没有使用懒加载来优化应用程序加载。要部署 Angular 界面,按照 文档 的建议,执行了 ng build --prod 命令并将其文件复制到服务器。Angular 构建将文件打包成优化格式,对文件进行最小化处理,禁用特定的开发检查以加速应用程序等。
要使用 Microsoft Azure 云的 VM,在开始测试之前,需要打开两个端口以连接到应用程序。一个端口用于访问 Web API,另一个端口用于用户界面。因此,必须创建两个端点,您可以创建网络过滤器或子网或网络适配器。您可以将控制传入和传出流量的过滤器放置在附加到接收流量的资源的网络安全组中。要了解更多关于为 Azure 门户中的虚拟机打开端口的信息
或者有关 Azure VM 端点配置的更多详细信息
在完成这些服务器设置并已将后端和前端文件妥善复制到服务器(在服务器上的单独文件夹中)后,就可以为每个应用程序创建网站了。我们不会详细介绍在 IIS 中创建网站的过程,只需遵循 文档 中的建议,这个过程通常不是问题来源。但是,在 IIS 8 中创建两个站点后,出现了关于前端文件的问题。因为当尝试在 Google Chrome 中加载前端时,应用程序不渲染主页,显示消息
404 - File or directory not found. The resource you are looking for might have been removed,
had its name changed, or is temporarily unavailable.
此错误表示缺少 web.config 来将 Angular 实现的用户界面与 Web 服务器集成。在实际意义上,web.config 是执行 IIS 配置的文件,用于应用程序在 Web 服务器上使用的参数定义。Angular 的构建 **不是针对特定服务器** 的,在本例中是 Internet Information Server - IIS,因为同一个 Angular 应用程序也可以使用 Apache、Nginx、Golang、Github Pages 等进行部署。因此,在生成部署文件后,必须手动创建一个 web.config 文件放在包含前端文件的同一文件夹中。
以下是摘自 Angular 文档中用于此目的的 web.config 代码。
<!--?xml version="1.0" encoding="UTF-8"?-->
<configuration>
<system.webserver>
<security>
<authorization>
<remove roles="" users="*" verbs="">
<add accesstype="Allow" users="?">
</add></remove></authorization>
</security>
<staticcontent>
<remove fileextension=".woff">
<mimemap fileextension=".woff2" mimetype="application/font-woff2">
<mimemap fileextension=".woff" mimetype="application/x-font-woff">
</mimemap></mimemap></remove></staticcontent>
<rewrite>
<rules>
<rule name="Angular Routes" stopprocessing="true">
<match url=".*">
<conditions logicalgrouping="MatchAll">
<add input="{REQUEST_FILENAME}"
matchtype="IsFile" negate="true">
<add input="{REQUEST_FILENAME}"
matchtype="IsDirectory" negate="true">
</add></add></conditions>
<action type="Rewrite" url="/index.html">
</action></match></rule>
</rules>
</rewrite>
</system.webserver>
</configuration>
(*) 此代码的第一行已被 CodeProject 编辑注释掉。
文档中提供的原始 web.config 文件通过 <rewrite>
和 <rules>
标签建立了一些规则,以定义 IIS 内路由的行为。这对应用程序的正常运行至关重要,因为通过重写请求的 URL 地址和 HTTP 响应,web.config 还添加了 <staticContent>
标签,以便 IIS 能够识别具有 .woff 和 .woff2 扩展名的文件。还有其他方法可以实现这些选项,但在本例中,我们将通过插入 web.config 中的代码来使用这些资源。
关于前端部署,一些帖子建议修改 Angular 构建生成的 index.html 文件。index.html 有一个标签指定了用于解析与应用程序和资产(静态文件、图片、脚本等)相关的 URL 的基本路径 base href='/'
。在某些情况下,例如生产环境在其他应用程序内有子文件夹进行安装以及其他特殊情况,可以更改此路径。在某些时候,为了尝试解决页面加载问题,我更改了这个标签以尝试其他可能性,结果应用程序完全停止工作。斜杠表示应用程序的根目录,对于未链接到其他应用程序的应用程序,在这些情况下不应更改。 **只有在特定情况下,才应更改此标签。**
后端和 CORS,另一章
了解 Web API 项目的类型对于在部署时进行正确的设置非常重要。我将再次不详细介绍,因为 ASP.NET Core 文档非常直观。在此示例的后端中,它是 FDD(依赖于框架的部署),应用程序使用 Windows Server 2012 R2 中存在的 .NET Core 版本进行部署。有关这方面的更多信息,请查看此链接,并了解有关 ASP.NET Core API Web 服务部署类型的更多信息,请点击此链接。
InProcess
是本项目中使用的 Web API 托管模型,它已经在 Visual Studio 的创建模板中配置好。
<propertygroup>
<targetframework>netcoreapp2.2</targetframework>
<aspnetcorehostingmodel>InProcess</aspnetcorehostingmodel>
</propertygroup>
然而,了解这种托管模型的一些特性很重要,这样就不会认为它仅仅是因为它是 Visual Studio 的项目模板而被采用的。InProcess
模型非常适合本项目使用的架构,因为它使用已经配置好的 IIS HTTP 服务器(IISHttpServer
),而不是 Kestrel 服务器。它使用 CreateDefaultBuilder
调用 UseIIS()
方法来注册 IISHttpServer
。这样,服务的 Program
启动类就可以保持不变,无需额外配置,即可在生产环境中直接使用。下面的代码片段是位于项目根目录的 Program.cs 文件的一部分。
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup Startup();
}
通过浏览 HTML 页面启动应用程序,代码布局、格式和图像都会显示出来。这表明前端安装已正确完成。但是,当开始使用访问后端的功能时,在本例中是登录页面。插入调用后端身份验证控制器端点的用户名和密码,会出现错误
main-es2015.fbdfd9b26a7e658d49a6.js:1 Http failure response for
http://server-sample.cloudapp.net:95/api/auth/login/: 0 Unknown Error.
这种类型的错误一开始很难识别,因为浏览器本身的消息指的是 **未知错误**。在本例中,它是由 CORS(跨域资源共享)配置不正确引起的,但也可能由于缺少其配置而发生。
使用 Google Chrome 的分析工具(Resource Timing)可以尝试诊断问题。Resource Timing 显示的阶段允许查看客户端请求,直到服务器对其发送的每个请求做出响应并发送回来。还可以获得与此数据处理相关的各种性能问题指示器。您可以查看此链接。
Resource Timing 在每个请求中都有多个阶段,从“排队请求”(如果需要)开始,经过“已停止/阻止”、“代理协商”、“DNS 查找”、“连接”、“SSL 请求发送”、“等待和下载”,并以毫秒为单位显示每个阶段的执行时间。对登录请求生成的 POST 请求的分析在第一个“已停止”阶段结束,没有显示任何其他阶段,表明到服务器的请求过程已中断。当请求成功时,会显示一个图表,显示上述每个阶段以及它们各自的持续时间。
CORS 的主要功能是验证应用程序通过应用程序向特定 Internet 资源(后端端点)发出的每个请求,并验证请求是否被允许。如果没有 CORS 或其他类似设备,任何应用程序都可以访问网络上部署的任何服务资源,例如银行、商店、政府机构,只需要知道所需的 URI 即可访问。当然,网络上还有其他安全障碍,例如用户身份验证本身。我们可以将 CORS 理解为浏览器执行的第一个访问障碍,以防止访问未经授权的网络域。同样,是浏览器执行 CORS。因为即使没有配置 CORS,当通过 Postman 或其他工具执行后端端点连接测试时,请求可以正常工作,但通过浏览器执行时,则不行。
在一个前端和后端没有分离的应用程序中,不需要 CORS。因为对资源的请求将在同一域内,使用相同的基本 URL 作为整个应用程序,没有限制。这方面的文档 https://mdn.org.cn/en-US/docs/ Web / HTTP / CORS 比这个简单的解释要丰富得多,并且提供了更详细的信息,因此建议阅读。
在 ASP.NET Core 中,CORS 在 Web API 项目的 Startup
类中配置,位于项目根目录。有多种配置方法,只需在搜索引擎中进行一些查询即可查看。在此项目中采用的方法是将以下代码插入 ConfigureServices
方法中,与其他代码一起。
services.AddCors(options =>
{
options.AddPolicy(_AppCorsPolicy,
builder =>
{
builder.WithOrigins("https://:4200")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
这是通过声明和使用局部变量 _AppCorsPolicy
来完成的
readonly string _AppCorsPolicy = "AppCorsPolicy";
_AppCorsPolicy
可以赋值给 UseCors()
扩展,该扩展插入到 Startup
类的 Configure
方法(IApplicationBuilder
app, IHostingEnvironment
env)中。
app.UseCors(_AppCorsPolicy);
使用 CORS 的 WithOrigins
和 AllowAnyOrigins
扩展时存在一个问题。WithOrigins
扩展通过配置选项列表限制对后端的访问,而 AllowAnyOrigins
允许来自任何源的访问,这是一种不安全的做法。实际上,在某些情况下,这种选择不起作用,因为 AllowAnyOrigins
和 Credentials
的规范会返回无效的浏览器响应。有很多帖子引起了这类错误,它们似乎表明这两个扩展可以一起正常工作。但 AllowAnyOrigin
会影响预检请求和 Access-Control-Allow-Origin 标头/标头,如下文所示。所以,避免懒惰,映射将访问后端的设备的 URL 并使用 WithOrigins
扩展。
在 Startup
类的 Configure
方法中,还可以添加 app.UseDefaultFiles()
和 app.UseStaticFiles()
方法,以允许 IIS 通过后端访问用户界面资产文件。另外,检查 IIS 应用程序网站上是否安装了 CORS,如果没有安装,则进行安装。
ASP.NET Core 文档中建议在应用程序的控制器上使用 EnableCors 装饰器,请查看此链接。我看到文档中的这个建议时吓了一跳,因为在生产环境中无法让应用程序正常工作,我以为我必须重写我所有的控制器,**但这不是强制性的**。它的使用允许使用多种 CORS 使用策略。在这种情况下,在控制器基类中实现它,可以大大减少工作量。只需使用 Microsoft.AspNetCore.Cors
包并装饰所需的类。
引用前端没有关于 CORS 的配置,所有配置都在后端完成,后端负责阻止对其自身资源的未经授权访问。
在这一点上,一些建议和提示很重要,在一个测试场景中,问题可能持续了几个小时甚至几天。因此,**您应该坚持只构建应用程序并只复制少量文件到服务器,而是始终发布应用程序并重新复制所有文件。** 存在 APP_NAME.deps.json 文件的情况,根据 launchSettings.json 属性或 Startup
类的更改,它可能由构建重新生成,影响安装(如果未更新)。通过发布应用程序项目,它会生成一个 web.config 文件。我看到许多建议修改 web.config,我甚至做了一些修改解决了某些问题。但在某些情况下,修改 web.config 只会导致问题,请尽量保持它简洁。所以,**不要在您的应用程序中创建 web.config,让发布为您完成这项工作,然后添加您的应用程序所需的子句。** Visual Studio 的发布似乎非常胜任这项工作,至少对于我这个不那么复杂的应用程序来说是这样。
以下代码片段(从网络上的某个出版物复制)被插入到我的 web.config 中
<httpprotocol>
<customheaders>
<add name="Access-Control-Allow-Origin" value="https://:4200">
<add name="Access-Control-Allow-Credentials" value="true">
<add name="Access-Control-Allow-Headers" value="*">
<add name="Access-Control-Allow-Methods" value="*">
</add></add></add></add></customheaders>
</httpprotocol>
在本项目(不一定,这不会发生在具有不同 IIS 配置的其他应用程序中)中,返回了以下错误
Access to XMLHttpRequest at 'http://server/api;auth/login/' from origin
'https://:4200' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
The 'Access-Control-Allow-Origin' header contains multiple values
'https://:4200, https://:4200/', but only one is allowed.
澄清一下,CORS 有两种不同的配置,一种在应用程序(Startup
类)中,另一种在 web.config 中。当从 web.config 中删除此配置,只留下 Startup
类对 CORS 的配置时,应用程序就可以正常工作了,这表明 CORS 不需要配置在其他地方。如果您决定采用类似这种的配置,请查看 IIS 中的 HTTP Response Header,它必须为空,如下图所示
Google Chrome 控制台中没有看到任何错误指示 URL 地址有多余的斜杠或类似内容。错误通常很笼统,常常导致我们做很多其他事情。因此,请使用 Postman 或 Fiddler 检查您的 URL,尤其是在它与环境变量连接时。我花了很长时间,仅仅是因为我在输入时意外地输入了一个分号而不是一个斜杠。
遵循这些建议,一个测试应用程序可以毫无问题地测试其端点。但正如我们之前所说,这是一个真实的应用程序,可以访问数据库,具有用户身份验证和授权,带有日志控制以及一个包含多个第三方工具的架构。因此,在解决了 CORS 设置后,仍然出现了一个描述中包含 ERR_UNSAFE_PORT
的错误。此错误是由于 Google Chrome 为特殊服务保留了一些端口,如此链接中所列,我正好使用了其中一个为这些服务保留的端口。
这是一次有点困难但有益的部署体验。正如我从一开始所说,目的是为感兴趣的读者提供一个关于他们一些问题的指南,通过这篇帖子提供尽可能多的信息。当然,有很多情况我们无法在这篇帖子中涵盖,但请随时通过更正或添加更多信息来贡献您的意见。这只是一个起点,旨在为这类工作提供便利。
历史
- 2020 年 1 月 21 日:初始版本