使用 ASP.NET Core 2 部署 Angular 6 应用程序






4.96/5 (18投票s)
使用 Microsoft ASP.NET Core 2.1 进行跨平台开发
引言
没有什么能比得上丰盛的周日早午餐自助餐了。你可以尽情享用,从豪华酒店的吃到饱自助餐,到无穷无尽的美味佳肴。各种早餐和午餐美食正等着你;含羞草鸡尾酒和血腥玛丽鸡尾酒、班尼迪克蛋、煎蛋卷、炒蛋、法式吐司、比利时华夫饼、百吉饼、意大利面、鸡肉、海鲜和烤特级肋排。这份清单是无穷无尽的。唯一的限制是你的预算以及你能吃多少和想吃多少。
作为一名软件开发人员,你也有一个技术自助餐可供选择;特别是如果你正在开发基于网络的应用程序。你是用最新的 JavaScript 框架(Angular、React 或 VueJS)开发你的应用程序前端,还是决定使用 Microsoft ASP.NET MVC 开发你的前端。除了 Microsoft 的技术栈之外,还有几种完整的端到端技术和设计模式可供选择,包括 MEAN 栈、LAMP 栈或 Java 技术栈。当然,你也可以选择其他技术,如 Python 或 Ruby On Rails 来构建你的网络应用程序。
有了所有这些可用的技术选择,现在是成为一名软件开发人员的最佳时机,正如摇滚乐队 U2 在他们最新的专辑中唱的那样;现在是活着的最佳时机。与周日早午餐自助餐类似,技术也有其局限性,包括时间和金钱。你的时间通常有限,你必须战略性地决定如何利用你的时间。在选择学习和/或构建应用程序的技术时,还有其他因素;包括公司预算、上市时间、技术栈的成熟度、你的技能组合和开发团队的技能组合,以及技术的整体生态系统、公共支持和市场需求。
示例应用程序的目标
如果你是 Microsoft 开发商店中 Agile/Scrum 团队的成员,你很可能正在使用 Microsoft Visual Studio 的某个版本以及 Microsoft Team Foundation Services (TFS) 或 Visual Studio Team Services (VSTS)。这些工具允许你战略性地开发、测试和部署你的应用程序到不同的环境,包括将你的应用程序部署到 Azure 云。
我本人既是 Angular 又是 Microsoft .NET 开发人员,自然而然地,我将本文重点放在描述 Web 应用程序的初始开发上,该应用程序将部署最新版本的 Angular(版本 6)以及最新版本的 Microsoft ASP.NET Core 2.1 技术栈,并同时使用 Visual Studio 2017 和 Visual Studio Code。
我喜欢将 Angular 应用程序托管在 ASP.NET 项目和应用程序中的其中一件事是,它允许你将配置设置存储在 web.config 文件中,其中包含不同的配置设置,这些设置可以发布到不同的环境。当应用程序由 ASP.NET 启动时,这些配置设置可以在运行时注入到 Angular 应用程序中。它在配置、发布和启动 Angular 应用程序时提供了很大的灵活性。

选择 Microsoft ASP.NET Core 2.1
将 Angular 与 ASP.NET 集成时,你必须做出的第一个决定之一就是集成本身。你是将 Angular 与传统的 ASP.NET MVC 框架集成,还是跳入新的 ASP.NET Core 平台。Angular 前端可以最初通过手动构建 Angular 组件、服务和模块来创建,或者通过使用 Angular CLI 脚手架初始项目,或者通过使用现在包含 Angular 项目的最新 Microsoft .NET Core 2 项目模板来创建。
在经历了所有这些替代方案并试用了 ASP.NET Core 2.1 中包含的 Angular 模板之后,我并不是很喜欢在 ASP.NET Core 项目中看到的为了使其与 Angular CLI 一起工作所需的所有钩子和 SPA 中间件。
我还注意到,随着 Angular 的每个新版本发布,更新项目模板到最新发布的 Angular 版本都会有一个时间延迟。总的来说,我希望在独立于 ASP.NET Core 和 Visual Studio 2017 的情况下开发和测试我的应用程序具有更大的灵活性。
为了保持技术独立性,我决定将 Angular 6 应用程序与其与 Visual Studio 2017 的集成松散耦合。这将使我能够灵活地使用 Visual Studio 2017 或 Visual Studio Code,并使用 IIS Express 或 Webpack 开发 Web 服务器通过 Angular CLI 开发和测试应用程序,并使用 ASP.NET Core 和 Visual Studio 2017 和 TFS 将应用程序发布到生产 IIS Web 服务器。
ASP.NET Core Razor Pages
随着 ASP.NET Core 2 的发布,引入了一个新的框架来构建 ASP.NET MVC Web 项目。我们现在有两个选择:我们可以选择传统的模型、视图和控制器的文件夹结构和约定,或者实现新的 Web 框架来创建“页面”,而无需 ASP.NET MVC 的全部复杂性。
Razor Pages 的引入代表了 ASP.NET MVC 框架的精简版本。Razor 页面与 ASP.NET MVC 开发人员习惯的视图组件非常相似。它具有所有相同的语法和功能。关键区别在于,模型和控制器代码也包含在 Razor 页面本身的 code-behind 文件中。它实现了双向数据绑定和更简单的开发体验,而无需为所有模型、视图和控制器提供独立的复杂文件夹。
在ASP.NET Razor Pages上工作了一段时间后,由于其更精简的实现,很明显Razor Pages是集成和启动托管在ASP.NET Core中的Angular应用程序的完美解决方案。
入门 - ASP.NET Core 2.1 Web 项目
为了开始工作,我使用默认的 Web 应用程序模板创建了一个 ASP.NET Core 2.1 Web 项目。此模板实现了 Razor Pages,与传统的 Web 应用程序模型-视图-控制器模板相比,它将是与 Angular 6 集成的一个更简单的解决方案。
我使用点符号语法将项目命名为 CodeProject.AngularCore.Portal。
入门 - Angular 6 CLI 项目
要开始使用 Angular CLI,你需要安装 NodeJS 和 Node Package Manager (NPM)。转到 https://node.org.cn。一旦安装了 NodeJS,你就可以从 Windows 命令提示符安装 Angular CLI。
npm install -g @angular/cli
完成此操作后,你可以从命令行创建新的 Angular CLI 项目,如下所示:
ng new CodeProjectAngularCorePortal
最新版本的 Angular CLI 不支持项目名称中的点符号,因此我将 Angular 6 项目命名为 CodeProjectAngularCorePortal。这是一个小细节,因为下一步是进入根项目文件夹,然后简单地将此项目的所有内容和子文件夹剪切并粘贴到 ASP.NET Core 项目的根文件夹中。
在 Visual Studio 2017 中打开 ASP.NET Core 项目,你将看到包含两个项目内容的以下文件夹。
在项目中,我们将使用三个重要的文件夹
- src - 包含 Angular 6 源代码
- Pages - 包含 ASP.NET Razor Pages
- wwwroot - 将包含 Angular 应用程序的构建输出
设置 ASP.NET Core 环境变量
首次使用 ASP.NET Core 时,你会注意到项目文件中没有 web.config 文件。你将开始提问,我将所有应用程序设置和连接字符串放在哪里,以及如何为不同的环境和服务器设置这些配置。
在 ASP.NET Core 中,有一个“环境”的概念,你可以在机器级别或代码级别设置你所处的“环境”。环境可能包括开发、测试、暂存、生产或任何你想要的名称。以前在 ASP.NET MVC 框架中,你会使用 web.config 文件并使用 XML 转换在 构建和发布时 转换配置,而 ASP.NET Core 则在运行时确定其配置。
在 ASP.NET Core 中,环境设置由一个名为 ASPNETCORE_ENVIRONMENT 的变量驱动。默认情况下,ASP.NET Core 项目将此值设置为“Development”。“Development”设置将出现在 Visual Studio 2017 中运行按钮旁边的下拉菜单中。
当然,我想做的第一件事就是在调试模式下启动 Visual Studio 2017 时为多个环境创建并测试此环境变量。Visual Studio 2017 和 ASP.NET Core 项目包含一个名为 launchSettings.json 的新文件,它位于项目的 Properties 文件夹下。在此文件中,你可以设置 Visual Studio 2017 可以使用的不同启动环境。
在下面的 launchSettings.json 文件中,我直接编辑了该文件,并在 JSON 格式的文件中创建了额外的配置文件,以设置我想要的各种环境的 ASPNETCORE_ENVIRONMENT 变量。每个环境设置还告诉 Visual Studio 2017 在 Visual Studio 2017 中运行应用程序时,使用 IIS Express 启动应用程序。这些额外的环境设置将自动出现在 Visual Studio 2017 调试下拉菜单中,你可以在启动应用程序之前选择它们。
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://:64984",
"sslPort": 44326
}
},
"profiles": {
"Development": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"QA": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "QA"
}
},
"Staging": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
}
}
}
应用程序设置
现在你可以在 Visual Studio 2017 中启动应用程序时选择并设置 ASP.NET Core 的环境,接下来你需要有一个地方来存储应用程序所需的实际应用程序设置和/或连接字符串,以便在所需的每个环境下运行。
作为 web.config 文件的替代,ASP.NET Core 使用一个名为 appsettings.json 的 JSON 格式配置文件。要按环境保存应用程序设置,你可以创建额外的 appsettings 文件,每个环境一个,命名约定为 appsettings.<environment>.json。创建后,Visual Studio 2017 将自动将这些文件放在 appsettings.json 文件下,类似于以前的 web.config 约定。
对于本文的示例应用程序,我创建了以下应用程序设置文件
- appsettings.development.json
- appsettings.qa.json
- appsettings.staging.json
- appsettings.production.json
在下面的 appsettings.production.json 文件中,我创建了一个 AppSettings 部分,它为 Web API 端点设置了一个属性。最终,示例应用程序需要为每个环境调用一个 Web API 端点。你可以根据需要在此文件中创建额外的部分,例如创建一个 ConnectionStrings 部分来存储每个环境的数据库连接字符串。
{
"AppSettings": {
"WebApiUrl": "https://production.com/api/"
}
}
发布到不同的环境和 IIS
到目前为止,我们所做的只是设置 Visual Studio 2017 和应用程序,以便在从 Visual Studio 2017 启动应用程序时,在 IIS Express 下以不同的环境设置运行。完成所有这些操作后,下一个挑战是弄清楚如何将应用程序发布到另一个环境,例如 QA、暂存或生产环境,并在应用程序在 IIS Web 服务器下运行时使用该特定环境的相应应用程序设置。
通常,你仍然可以使用 Visual Studio 2017 通过设置不同的发布配置文件和配置来将应用程序发布到不同的环境,以用于发布目的。
于是我使用 QA 发布配置文件将我的应用程序发布到 QA 服务器。我在 QA 服务器上发现,应用程序仍然根据 ASPNETCORE_ENVIRONMENT 变量在“开发”模式下运行,并且所有 appsettings 文件都作为单独的文件发布。查看 QA 网站的根文件夹;我注意到 Visual Studio 2017 也自动生成并发布了一个 web.config 文件。当你打开 web.config 文件时,你会注意到该配置文件实现了 ASP.NET Core 模块,用于处理 IIS 和 ASP.NET Core 之间的 HTTP 请求,尽管 ASP.NET Core 本身不使用 web.config 文件。
事实证明,我们仍然可以使用 web.config 文件进行应用程序,并在发布应用程序时执行 XML 转换。基本上,我们只需要一种方法来覆盖 ASPNETCORE_ENVIRONMENT 变量并为其目标环境设置值。现在的问题是弄清楚如何进行 XML web.config 转换,因为 ASP.NET Core 项目模板中不提供此功能。
经过一番研究,事实证明 .NET Core 附带了一个命令行接口 (.NET Core CLI) 工具箱,可以从命令行运行。所需的 NuGet 包名为 Microsoft.DotNet.Xdt.Tools;它是一个用于 .NET Core CLI 的 XML 文档转换 (XDT) 发布工具。此包包含名为 dotnet-transform-xdt 的实用命令,用于在发布时转换 XML 文件。
你可以从 Visual Studio 2017 的 NuGet 控制台安装此包
Install-Package Microsoft.DotNet.Xdt.Tools -Version 2.0.0
接下来要做的是添加四个 web.config 文件;一个主 web.config 文件,以及每个环境一个。
- web.config
- web.qa.config
- web.staging.config
- web.production.config
主 web.config 文件是使用以下内容创建的
<?xml version="1.0" encoding="utf-8"?> <configuration> <!-- To customize the asp.net core module uncomment and edit the following section. For more info see https://go.microsoft.com/fwlink/?linkid=838655 --> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout"> <environmentVariables> <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" /> </environmentVariables> </aspNetCore> </system.webServer> </configuration>
这些 web.config 文件和使用转换的主要目标是简单地为每个环境设置和覆盖 ASP.NET Core 环境变量 ASPNETCORE_ENVIRONMENT。
接下来我们需要做的是使用 XDT 将 XML 转换信息添加到每个环境的 web.config 文件中。
下面是 web.release.config 文件,它将在发布到生产发布环境时转换 ASPNETCORE_ENVIRONMENT 变量并将其设置为“Production”值。
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <system.webServer> <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout"> <environmentVariables> <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/> </environmentVariables> </aspNetCore> </system.webServer> </configuration>
现在我们只需要告诉 Visual Studio 2017 在发布应用程序时运行 XML 转换。为此,我们需要编辑项目文件并向其中添加一个任务。现在酷的是,你无需卸载项目即可编辑项目文件。你只需右键单击项目名称并选择编辑。
查看项目文件时,你会发现它比标准 .NET 框架项目的项目文件更简洁易读。为了告诉 Visual Studio 2017 运行 XML 转换,我在项目文件底部添加了以下代码片段。我们可以通过执行 dotnet transform-xdt .NET Core CLI 命令 来执行 XML 转换。
我创建了一个名为 ApplyXdtConfigTransform 的自定义目标。我设置了任务,告诉 Visual Studio 2017 在构建管道 _TransformWebConfig 任务运行之前运行该任务。自定义任务还告诉 Visual Studio 2017 仅当不在调试配置中时才运行 XML 转换任务。
<Project Sdk="Microsoft.NET.Sdk.Web"> <Target Name="ApplyXdtConfigTransform" BeforeTargets="_TransformWebConfig" Condition="'$(Configuration)'!='Debug'"> <PropertyGroup> <_SourceWebConfig>$(MSBuildThisFileDirectory)Web.config</_SourceWebConfig> <_XdtTransform>$(MSBuildThisFileDirectory)Web.$(Configuration).config</_XdtTransform> <_TargetWebConfig>$(PublishDir)Web.config</_TargetWebConfig> </PropertyGroup> <Exec Command="dotnet transform-xdt --xml '$(_SourceWebConfig)' --transform '$(_XdtTransform)' --output '$(_TargetWebConfig)'" Condition="Exists('$(_XdtTransform)')" /> </Target> </Project>
构建和捆绑 Angular 6 TypeScript 代码
在设置所有这些环境设置之前,我们真正需要做的第一件事是编译和捆绑 Angular 6 TypeScript 代码,并让构建将内容推送到项目中的 wwwroot 文件夹。
wwwroot 文件夹在 ASP.NET 5.0 和 ASP.NET Core 中是新的。项目中的所有静态文件都放在此文件夹中。这些是应用程序将直接提供给客户端的资产,包括 HTML 文件、CSS 文件、图像文件和 JavaScript 文件。wwwroot 文件夹是你的网站根目录。
要编译和捆绑 Angular TypeScript 代码,我们需要从 Angular CLI 执行 ng build。我们可以通过向项目文件添加目标任务来让 Visual Studio 2017 自动为我们执行此任务。为每个配置创建一个单独的任务。我们告诉 Visual Studio 2017 在标准构建任务运行之前运行这些任务。
对于 QA、暂存和生产环境,我们可以使用 --prod 选项运行 ng build,以便使用以下功能优化 Angular 构建
- 捆绑:将应用程序代码和库文件连接成捆绑包。
- 最小化:删除多余的空格、注释和可选标记。
- 混淆:重写代码以使用简短、隐晦的变量和函数名。
- 死代码消除:删除未引用的模块和任何未使用的代码。
- 预编译 (AOT) 编译:预编译 Angular 组件 HTML 模板
下面是添加到项目文件中用于编译和捆绑 Angular 代码的代码片段。
<Target Name="Build Angular Debug" Condition="'$(Configuration)'=='Debug'" BeforeTargets="Build">
<Message Text="* * * * * * Building Debug Angular App * * * * * *" Importance="high" />
<Exec Command="ng build" />
</Target>
<Target Name="Build Angular Release" Condition="'$(Configuration)'=='Release'" BeforeTargets="Build">
<Message Text="* * * * * * Building Release Angular App * * * * * *" Importance="high" />
<Exec Command="ng build --prod --aot" />
</Target>
<Target Name="Build Angular QA" Condition="'$(Configuration)'=='QA'" BeforeTargets="Build">
<Message Text="* * * * * * Building QA Angular App * * * * * *" Importance="high" />
<Exec Command="ng build --prod --aot" />
</Target>
<Target Name="Build Angular Staging" Condition="'$(Configuration)'=='Staging'" BeforeTargets="Build">
<Message Text="* * * * * * Building Staging Angular App * * * * * *" Importance="high" />
<Exec Command="ng build --prod --aot" />
</Target>
为 WWWROOT 配置 Angular CLI
熟悉 wwwroot 文件夹后,你会开始意识到这个文件夹对于放置 Angular CLI 构建输出有多么方便。我们只需要告诉 Angular CLI 使用 wwwroot 作为构建的输出文件夹。
为此,我们只需要编辑 Angular CLI 用于其配置设置的 angular.json 文件,并将 outputPath 属性更改为“wwwroot”。
"outputPath": "wwwroot",
服务 Angular 应用程序 - Index Razor Page
当我们通过 Visual Studio 2017 启动示例应用程序时,将执行默认的根 Index Razor 页面。Index.cshtml 页面位于 Pages 文件夹中。这是我们在 ASP.NET Core 中托管和运行 Angular 应用程序所需的唯一页面。
Index Razor 页面由一个视图页面和一个作为视图控制器的代码隐藏文件组成。基本上,Razor Pages 被整合到两个文件中,形成一个组合的模型-视图-控制器,它们位于同一个文件夹中。控制器、视图和模型不会像 Razor Pages 那样被分离到不同的文件夹中。
Pages 文件夹的文件夹结构决定了 Razor Pages 的 ASP.NET Core 路由。ASP.NET Core 路由器根据 URL 与文件路径的匹配,从根 Razor Pages 文件夹(默认名为 Pages)开始,将 URL 匹配到 Razor Pages。
索引 Razor 页面代码隐藏控制器
当访问示例应用程序的根路径时,将执行 Index Razor 页面中的控制器,该控制器由一个构造函数和一个 OnGet 方法组成。
在构造函数中,我们获取托管环境配置对象,其中包含我们当前运行的应用程序可能需要的所有环境信息。
构造函数执行后,OnGet 方法将执行。在下面的代码片段中,OnGet 方法填充了两个属性:_wwwroot 和 _currentRoutePath。将这些属性创建为类 IndexModel 的公共属性,最终为视图创建了可访问的模型属性。
Index Razor 视图将引用这两个属性。来自托管环境对象的 WebRootPath 属性将返回 wwwroot 文件夹的当前物理路径。此属性的值将用于遍历 wwwroot 文件夹,以便视图可以动态注入引用捆绑的 Angular JavaScript 文件的脚本标签。捆绑的 JavaScript 文件在开发过程中具有静态文件名,但在生产环境中将具有动态文件名,以便在创建优化的生产捆绑包时用于缓存清除目的。
当用户在包含浏览器中完整路由的 Angular 页面上刷新页面时,ASP.NET Core 将无法找到该路由的任何 Razor 页面,并且将发生 IIS 服务器错误。为了克服这个问题,路由将传递给 OnGet 方法,并且该值映射到 currentRoutePath 属性,以便 Razor Page 可以正确处理请求。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using CodeProject.AngularCore.Models.Models;
using Microsoft.Extensions.Options;
namespace CodeProject.AngularCore.Portal.Pages
{
public class IndexModel : PageModel
{
private Microsoft.AspNetCore.Hosting.IHostingEnvironment _hostingEnvironment;
public string _wwwroot { get; private set; }
public string _currentRoutePath { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="hostingEnvironment"></param>
public IndexModel(Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
/// <summary>
/// On Get
/// </summary>
public void OnGet(string currentRoutePath)
{
_wwwroot = _hostingEnvironment.WebRootPath;
_currentRoutePath = "/";
if (currentRoutePath != null)
{
_currentRoutePath = currentRoutePath;
}
}
}
}
索引 Razor 页面视图
要在 Index Razor 视图中启动 Angular 应用程序;我们真正需要的只是对应用程序根组件的引用。
<app-root></app-root>
但是这个示例应用程序在启动应用程序时有一些它想做的事情,包括
- 将应用程序设置注入到 Angular 应用程序中
- 将当前 URL 路由设置到浏览器历史记录中,以支持 Angular 应用程序的页面刷新
- 将脚本标签注入页面以引用生成的 Angular JavaScript 捆绑包
@page
@using Microsoft.Extensions.Configuration;
@using System.Net;
@using CodeProject.AngularCore.Models.Models;
@using System.IO;
@inject IConfiguration configuration
@inject Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnv
@model IndexModel
@{
AppSettings appSettings = new AppSettings();
configuration.GetSection("AppSettings").Bind(appSettings);
string settings = Convert.ToString(Json.Serialize(appSettings));
IEnumerable<string> fileEntries = Enumerable.Empty<string>();
List<string> bundles = new List<string>();
bundles.Add("runtime.");
bundles.Add("polyfills");
bundles.Add("styles.");
bundles.Add("vendor.");
bundles.Add("main.");
fileEntries = Directory.EnumerateFiles(@Model._wwwroot);
}
<p> ASPNETCORE_ENVIRONMENT = @hostingEnv.EnvironmentName</p>
<p> CURRENT ROUTE = @Model._currentRoutePath</p>
@{
<script>
history.pushState({}, null, "@Model._currentRoutePath");
</script>
<app-root settings="@settings"></app-root>
<div style="visibility:hidden; display:none">
@WebUtility.HtmlEncode(settings)
</div>
foreach (string bundleName in bundles)
{
foreach (string fileName in fileEntries)
{
FileInfo fileInformation = new FileInfo(fileName);
if (fileInformation.Name.Contains(bundleName) == true &&
fileInformation.Name.Contains(".js") == true &&
fileInformation.Name.Contains(".js.map") == false)
{
<script type="text/javascript"src="@fileInformation.Name">/script>
}
}
}
}
将应用程序设置注入 Angular
appsettings.json 结构可以绑定到一个类,并作为常规类对象引用。对于 AppSettings 部分,创建了以下类
namespace CodeProject.AngularCore.Models.Models
{
public class AppSettings
{
public string WebApiUrl { get; set; }
}
}
我们可以在 Index Razor 视图中使用以下语句将 AppSettings 部分绑定到这个类,并将对象序列化为 json 字符串。
@inject IConfiguration configuration
AppSettings appSettings = new AppSettings();
configuration.GetSection("AppSettings").Bind(appSettings);
string settings = Convert.ToString(Json.Serialize(appSettings));
然后可以将序列化的 json 字符串注入到 Angular 根组件中,如下所示
<app-root settings="@settings"></app-root>
根 Angular 组件 app.component.ts 使用 ElementRef 引用设置输入变量,并将字符串解析为 json 对象,然后将其存储到一个 Angular 单例会话服务对象中,该对象可以在整个 Angular 应用程序中引用。AppSettings 最初将只包含示例应用程序需要调用的 web api 端点。将来,AppSettings 对象可以扩展以满足其他应用程序设置需求。
import { Component, ElementRef } from '@angular/core';
import { AppSettings } from './shared/models/appsettings.model.';
import { SessionService } from './shared/services/session.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private elementRef: ElementRef, private sessionService: SessionService) {
let native = this.elementRef.nativeElement;
let settings = native.getAttribute("settings");
let appSettings = new AppSettings();
appSettings = JSON.parse(settings);
sessionService.setAppSettings(appSettings);
}
}
处理页面刷新 (F5)
当用户在浏览器中点击 F5 或刷新 Angular 页面时,请求将返回服务器,ASP.NET Core 将尝试将浏览器路由映射到 Pages 文件夹中的 Razor 页面。如果找不到路由,将生成服务器错误。由于 Angular 路由仅在 Angular 中定义,而不是在服务器端定义,因此我们需要一种方法来处理这个问题。
我们可以做的一件事是在 ASP.NET Core 尝试路由请求之前拦截 HTTP 请求。为此,我们可以编辑 ASP.NET Core 项目中的启动类来配置 HTTP 请求管道。
startup.cs 文件中的 Configure 方法设置了 ASP.NET Core 管道。ASP.NET Core 是轻量级的,我们必须配置我们想要包含在应用程序中的功能,包括告诉 ASP.NET Core 使用 MVC、静态文件、cookie 等。
为了拦截页面请求,在启动文件中添加了一个 app.use 语句,用于异步监听 Web 请求。当 Web 请求进来时,会检查请求路径,如果传入了完整路径,请求将重定向回 Index Razor 页面。Index Razor 页面将是示例 ASP.NET Core 应用程序中唯一的页面。
在启动文件的以下 Configure 方法中,当前路径作为查询字符串附加到根路由,并且请求被重定向到 Index Razor 页面。Razor 页面控制器将查询字符串参数值映射到 OnGet 方法中的 currentRoutePath 属性,如本文前面所述。
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
app.Use(async (context, next) =>
{
var request = context.Request;
if (request.Path != "/")
{
context.Response.Redirect("/" + "?currentRoutePath=" + request.Path);
}
else
{
await next.Invoke();
}
});
}
Index Razor 页面视图可以通过 Razor Page 模型访问 _currentRoutePath 属性。
在 Index Razor 页面视图中使用 _currentRoutePath 设置浏览器历史记录状态,可以保留浏览器中的 URL,并允许 Angular 正常处理路由。基本上,此功能将使用户停留在他们刷新的同一 Angular 页面上。
<script>
history.pushState({}, null, "@Model._currentRoutePath");
</script>
将 JavaScript 标签注入 Index Razor Page
最后,Index Razor 页面需要在运行时将 JavaScript 脚本标签注入页面,以引用 Angular CLI 构建生成的 JavaScript 捆绑包。在开发模式下,这些 JavaScript 标签理论上可以硬编码,因为 JavaScript 文件名将始终相同。
发布生产版本时,情况变得有点复杂。Angular CLI 将生成带有动态唯一后缀的捆绑包,该后缀附加到捆绑包文件名,用于在用户客户端进行浏览器缓存清除。我们永远不知道这些文件名最终会是什么。
这就是 wwwroot 文件夹派上用场的地方。我们可以简单地遍历 wwwroot 文件夹以查找 Angular JavaScript 捆绑包,并将这些捆绑包的 JavaScript 脚本标签动态注入到 Index Razor 视图中。
使用 .NET Core Directory 库,我们可以在 wwwroot 文件夹中创建可枚举的文件列表。
为了注入 JavaScript 脚本标签,我们只想为启动应用程序所需的核心 Angular 应用程序捆绑包注入脚本标签。所有其他 Angular 捆绑包将在用户进入应用程序的不同模块时根据需要自动惰性加载。
创建了一个已知捆绑包名称的通用列表,并循环遍历此列表以查找 JavaScript 文件,并使用每个文件名在所需的注入脚本标签中设置 src 属性。下面的捆绑包列表是有序的,以便每个文件都按照正确的模块依赖顺序注入。
@{
IEnumerable<string> fileEntries = Enumerable.Empty<string>();
fileEntries = Directory.EnumerateFiles(@Model._wwwroot);
List<string> bundles = new List<string>();
bundles.Add("runtime.");
bundles.Add("polyfills");
bundles.Add("styles.");
bundles.Add("vendor.");
bundles.Add("main.");
foreach (string bundleName in bundles)
{
foreach (string fileName in fileEntries)
{
FileInfo fileInformation = new FileInfo(fileName);
if (fileInformation.Name.Contains(bundleName) == true
&& fileInformation.Name.Contains(".js") == true
&& fileInformation.Name.Contains(".js.map") == false)
{
<script type="text/javascript" src="@fileInformation.Name"></script>
}
}
}
}
Angular CLI 和实时重载
Angular CLI 是一个命令行界面 (CLI),用于自动化你的开发工作流。它允许你
- 创建一个新的 Angular 应用程序
- 运行一个支持实时重载的开发服务器,以便在开发过程中预览你的应用程序
- 向你现有的 Angular 应用程序添加功能
- 运行你的应用程序的单元测试
- 运行你的应用程序的端到端 (E2E) 测试
- 构建你的应用程序以部署到生产环境。
要使用 Angular CLI 启动 Angular 应用程序,你只需在项目根文件夹内的命令窗口中运行以下命令
ng serve
执行此命令后,你可以导航到 https://:4200 在浏览器中启动你的应用程序。Angular CLI 附带 Webpack 捆绑工具,并使用 Webpack 编译、构建和捆绑所有 TypeScript 代码,并启动一个 Webpack 开发 Web 服务器,该服务器默认在端口 4200 上运行并监听请求。
当然,Angular CLI 真正的强大之处在于它对应用程序实时重新加载的支持。在仍然运行的情况下,Webpack 进程正在积极监视 src 文件夹以查找文件更改。当检测到文件更改时,应用程序会自动重新构建并在浏览器中重新加载。
使用 Visual Studio Code 进行开发
我不想使用 Visual Studio 2017 最新版本随附的 Angular 模板来开发 ASP.NET Core Web 应用程序的原因之一是,我不想局限于使用 Visual Studio 2017 进行 Angular 开发,相反,我更喜欢使用 Visual Studio Code IDE。
Visual Studio Code 正在迅速成为前端 Web 开发工作的首选工具。它轻量级,并附带一个庞大的扩展市场,可以让你为编辑器添加开箱即用的工具和功能。
Visual Studio Code 有一些很棒的扩展,可以极大地增强 Angular 开发,包括 TypeScript Linting、自动导入、代码片段和文件切换等。
要开始使用 Visual Studio Code,你只需打开 Web 应用程序项目的根文件夹。

Visual Studio Code 调试
Visual Studio Code 我最喜欢的功能之一是能够直接在编辑器中调试 TypeScript 代码并设置断点,同时应用程序在浏览器中运行。
Angular CLI 处理所有 TypeScript 代码和模块的编译和捆绑,所以我们只需要配置 Visual Studio Code 进行调试。
配置 Visual Studio Code 进行调试需要三件事
- 为 Chrome 浏览器创建一个快捷方式,该快捷方式通过启用远程调试的扩展命令行选项启动 Chrome
- 在 Visual Studio Code 中安装 Chrome 调试扩展
- 在 Visual Studio Code 中创建一个包含调试配置设置的 launch.json 文件
在 Chrome 浏览器命令行中添加 --remote-debugging-port=9222 将告诉 Chrome 在端口 9222 上侦听任何调试通知。要使用 Chrome 调试,你将需要关闭所有 Chrome 浏览器,并始终使用此快捷方式启动 Chrome 浏览器,并将端口号附加到命令行。
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
接下来,在 Visual Studio Code 中,你可以进入 Visual Studio Code 扩展(左侧菜单上的第五个按钮)并安装 Debugger for Chrome 扩展。你可以在“市场搜索扩展”窗口中输入名称来找到它。
最后,你需要创建一个名为 launch.json 的配置文件,它告诉 Visual Studio Code 附加到 Chrome 浏览器以开始调试。你可以在调试窗口中选择调试按钮(左侧菜单中的第四个按钮)并添加配置设置来创建此配置文件。
配置文件将保存到名为 .vscode 的文件夹中,该文件夹位于根项目文件夹下一级。在配置文件中,关键设置是 url,它告诉调试器查找引用 localhost:4200 的 Chrome 窗口/标签页。绝对 URL https://:4200 是示例应用程序启动时的默认 URL。调试器在尝试附加到浏览器时会查找此精确 URL。
请求属性可以设置为 attach 或 launch。将属性设置为 launch 将允许 Visual Studio Code 直接从 IDE 启动 Chrome 浏览器。对于此演示,Visual Studio Code 将简单地附加到已运行的 Chrome 浏览器。
{ "version": "0.2.0",
"configurations":
[{
"name": "Attach to Chrome, with sourcemaps",
"type": "chrome",
"request": "attach",
"url""https://:4200",
"port": 9222,
"sourceMaps": true,
"trace": true,
"webRoot": "${workspaceRoot}"
}]
}
当 ng serve 在后台从 Windows 命令提示符运行并提供应用程序时,你可以从桌面上创建的快捷方式启动 Chrome 浏览器,并将远程调试端口附加到命令行。
在 Visual Studio Code 中,你可以开始调试应用程序并设置断点。要开始调试,你可以按下调试按钮并点击带有启动配置(名为 Attach to Chrome, with sourcemaps)的绿色箭头按钮,该配置在调试窗口中设置并引用 launch.json 文件。
在下面 Visual Studio Code 的示例中,我直接在 TypeScript 代码中第 30 行设置了一个断点,当组件在浏览器中执行时,应用程序将在此行中断。一旦命中断点,你可以通过点击 F10(类似于 Visual Studio 2017)逐步执行代码,黄色线条表示你接下来要执行的行。你将在 Visual Studio Code 中获得完整的调试功能,包括监视和调试控制台窗口。直接在 IDE 中调试 TypeScript 代码是 Angular 和 TypeScript 开发人员的绝佳工具。
Angular CLI Index.Html 页面
Angular CLI 附带一个 index.html 文件,该文件在从 CLI ng serve 命令运行应用程序时启动 Angular 应用程序。由于 Angular 应用程序期望将应用程序设置注入到应用程序根目录中,因此我不得不将 JSON 字符串复制并粘贴到 Index Razor 页面正在生成的页面中。在这种情况下,JSON 字符串是 HTML 编码的。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Code Project</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root settings="{"webApiUrl":"https://:44305/api/"}"></app-root>
</body>
</html>
在某个时候,我会研究一种方法,当 JSON 设置的结构和值发生变化时,自动动态更新此静态页面。
ASP.NET Core 2.1 IIS 集成
创建新的 ASP.NET Core 项目时,你会注意到项目现在包含一个 Program.cs 文件。ASP.NET Core Web 应用程序实际上是控制台应用程序。控制台应用程序架构促进了 ASP.NET Core 的跨平台可移植性目标。
ASP.NET Core 还附带了一个新的 Kestrel Web 服务器。Kestrel Web 服务器是一个轻量级 Web 服务器,可在 Windows 上运行,并附带新的请求管道和新的 HTTP 处理程序。
Internet Information Services (IIS) 和现有 ASP.NET 管道的一个大问题是其性能。对于大多数实际应用程序,性能完全正常。然而,与其它 Web 服务器相比,它在基准测试中远远落后。
如果你像我一样,已经开发 ASP.NET 应用程序一段时间了,你可能对 IIS 很熟悉。作为一个 Web 服务器,它实际上可以做任何事情。为了本文的目的,我决定将示例应用程序部署到 IIS。接下来,研究 Kestrel 等其他 Web 服务器将是值得的。
当然,将 ASP.NET Core Web 应用程序发布到 IIS 并非完全无缝。如 web.config 中所述,ASP.NET Core 将 ASP.NET Core 模块注册为 HTTP 处理程序。此模块处理所有进入 IIS 的流量,并充当反向代理,知道如何将流量传递给你的 ASP.NET Core 应用程序。它是 IIS 和 ASP.NET Core 之间的桥梁。
在部署应用程序之前,你需要执行以下操作
- 下载 .NET Core Windows Hosting SDK 2.1
- 安装 IIS 的 .NET Core 托管包。这将安装 .NET Core 运行时、库和 IIS 的 ASP.NET Core 模块。
- 重新启动 IIS Web 服务器
- 创建一个新的 IIS 应用程序池。你需要创建一个在 .NET CLR 版本为“无托管代码”下的应用程序池。由于此模块充当反向代理,因此它实际上不执行任何 .NET 代码。
- 在现有 IIS 站点下创建新应用程序,或创建新 IIS 站点。无论哪种方式,你都将选择新的 IIS 应用程序池,并将新站点指向你将发布应用程序的文件夹。
摘要
本文重点介绍了如何设置和配置 Visual Studio 2017,以便在 ASP.NET Core Web 应用程序中托管和部署 Angular 6 Web 应用程序,同时还讨论了如何利用 Visual Studio Code 调试 TypeScript 代码和设置断点,以及使用 Angular CLI 的实时重载功能。ASP.NET Core 中引入的新 Razor Pages 架构代表了 ASP.NET MVC 模式的精简实现,似乎非常适合在 ASP.NET Core Web 应用程序中提供 Angular 应用程序。
本文中包含的示例应用程序只是一个没有任何当前功能的 shell 应用程序。在后续文章中,此应用程序将构建为包含与 ASP.NET Core Web API 项目交互的功能,同时还将结合 Entity Framework (EF) Core 的功能来更新后端数据库。
Microsoft .NET Core 框架轻量级,支持在 Windows、Linux、Unix 和 MacOs 上跨平台运行服务器应用程序。ASP.NET Core 技术栈前景广阔,随着它的不断成熟,它可能成为 Microsoft 开发人员的下一个重大突破。
要求
要运行本文随附的示例应用程序,你需要安装以下内容
- Visual Studio 2017 15.7.3 或更高版本
- .NET Core 跨平台开发(随 Visual Studio 2017 安装)
- .NET Core Windows Hosting SDK 2.1
- Visual Studio Code 1.24 或更高版本(可选)
- NodeJs(最新版本)
- Angular CLI(最新版本)
node_modules 文件夹已从本文附带的源代码下载中排除。下载并安装上述项目后,在 Visual Studio 2017 中打开项目解决方案文件,项目的 npm 依赖项应自动开始恢复到 node_modules 文件夹中。如果恢复未自动发生,你可以通过右键单击 Visual Studio 2017 中 Dependencies/npm 文件夹并选择 Restore Packages 来手动启动恢复过程。