写点什么

深入研究 Angular 和 ASP.NET Core 3.0

  • 2019-11-25
  • 本文字数:20558 字

    阅读完需:约 67 分钟

深入研究Angular和ASP.NET Core 3.0

本文要点:

  • 可以把多个 Angular 应用程序集成到 ASP.NET 网站中

  • 把 Angular 代码打包成 Web 组件是引导 Angular 应用程序的好方法

  • 可以把用 Angular 编写的 Web 组件轻松地集成到 ASP.NET 视图中

  • 把 Angular 解决方案构造成 Angular 应用程序的集合以实现更好的代码重用

  • ASP.NET 结合使用 Angular 是创建 web 应用程序的强大平台


本文是我们的.NET 教育系列文章的一部分,这个系列探索了该技术的优势,设法做到不仅有助于传统的.NET 开发人员,而且有助于所有需要把健壮、高性能和经济的解决方案推向市场的技术人员。


随着.NET Core 3.0 的发布,微软拥有了通用、模块化、跨平台和开源平台的下一个重要版本,.NET Core 最初发布于 2016 年。创建.NET Core 的最初目的是为了下一代的 ASP.NET 解决方案,但是现在,其驱动并成为很多其他场景的基础,这些场景包括物联网、云和下一代移动解决方案。.NET Core 3.0 版添加了大量经常需要功能,如对 WinForms、WPF 和 Entity Framework 6 的支持。

挑战

开始使用 Angular 和 ASP.NET Core 的最简单的方法是,使用微软提供的 Visual Studio 模板。该模板可以让我们迅速启动并运行,但有一个很大的限制——Angular 接管了 UI,把 ASP.NET 保留在后台,并提供 API。如果我们希望.NET 服务某些页面而 Angular 服务其他页面,那么,我们需要在 ASP.NET Core 和 Angular 中都复制外观和菜单结构。或者,我们可以让单个 Angular 应用程序服务整个 UI,但接下来,我们必须在 Angular SPA 中实现所有的页面,包括琐碎的静态内容,如 Contact As、Licensing 等等。


我想要的设置是一个充当门户的 ASP.NET 站点,然后把 Angular 工件嵌入 ASP.NET 页面。一般来说,有两种架构设计模式。在第一种设计模式下,我有一个带路由的 Angular 应用程序,想嵌入到 ASP.NET 视图中,其中具有 Angular 提供的子菜单和提供顶层菜单的 ASP.NET 站点。在第二种设计模式下,我有 Angular 组件,这些组件不一定是成熟的 Angular 应用程序,但是,仍然需要把它们嵌入 ASP.NET 视图。例如,假设我想在一个 ASP.NET 视图中嵌入一个组件,以显示当前时间。在 Angular 中开发这么一个组件很容易,但把它嵌入 MVC 视图就比较难。最后,我希望实现尽可能多的代码重用,我希望能够在 Angular 应用程序中重用组件,并能够把相同的组件嵌入 ASP.NET 视图。本文演示了如何引导 ASP.NET 和 Angular 项目以适应这些架构设计模式。如果要看最终的代码,请参考 GitHub 上的Multi App Demo存储库。


总结一下,这就是我们要构建的:


  • 一个 ASP.NET Core web 网站,该网站作为门户,具有菜单结构,其中每个菜单都打开一个 MVC 视图。

  • 能够在网站中托管一个或更多 Angular SPA

  • 能够在 ASP.NET 视图中重用这些 SPA 的某些组件

实现概述

  • 我们将使用.NET Core(在撰写本文时,版本号为 3.0)创建一个 ASP.NET MVC 网站

  • 我们将使用 Angular CLI 创建一个 Angular 项目,并集成(开发工作流、产品构建、发布等)该项目到 ASP.NET 项目中。

  • 我们将把 Angular 项目作为 Web 组件(也称为自定义元素,也叫 Angular 元素)进行引导。

  • 我们将使用 Angular CLI 生成应用程序。这些应用程序将引用根 Angular 项目的可重用组件。

  • 我们将使用指向同一域的 iframes,在 ASP.NET 视图中嵌入 Angular 应用程序。

  • IFrames 将使用 JavaScript 根据内容调整大小,并且,我们将把 iframed Angular 应用程序和 ASP.NET 路由集成在一起,因此,把书签放入路由过的 Angular 视图是可行的。

  • 为了在 ASP.NET 视图中托管 Angular 组件,我们将把这些组件打包成 Web 组件。

创建 ASP.NET Core MVC 项目

我用的是 Visual Studio 2019 社区版,可以从微软那里免费下载。从 2019 版开始,用于选择模板的向导和以前的版本有所不同,但无论使用哪个版本,步骤基本上是一样的。


  • 转到创建一个新的项目。

  • 选择 ASP.NET Core Web Application。

  • 为项目选择名字和位置(我称之为我的 MultiAppDemo)。

  • 选择 ASP.NET Core(在我的情况下,是 3.0 版)。

  • 选择 ASP.NET Model-View-Controller(为了简单起见,选择 No Authentication,这样,VS 不会为这个演练生成不相关的工件)。


在 Solution Explorer 中,我们的项目视图应如下所示:



由于我们使用了 ASP.NET MVC 模板而不是 SPA 模板,因此,我们需要添加 Microsoft.AspNetCore.SpaServices.Extensions NuGet 包。为了安装这个包,请打开软件包管理器控制台(Package Manager Console)并运行以下语句:


Install-Package Microsoft.AspNetCore.SpaServices.Extensions
复制代码

创建 Angular 项目

请确保以下软件都已安装(全部是免费的):



npm install -g @angular/cli
复制代码


我在使用 Angular v8。用更早的版本也可以,只要可以访问 createCustomElement API 就行。 Angular v7 中也有这些功能。


为了创建一个 Angular 解决方案以在我们的 ASP.NET MVC 项目中使用,请打开命令提示符,转到包含用于 MVC 项目的项目文件(扩展名.csproj)所在的文件夹。到达之后,通过命令提示符执行以下命令以创建 Angular 项目:


ng new Apps
复制代码


路由选择 N,样式选 CSS。


把目录改为 Apps,并输入以下内容(包括结尾的点):


code .
复制代码


现在,在 Visual Studio Code 中应该有我们已经打开的 Angular 项目了。

引导 Angular 元素

这里的想法是,把这个根项目做为可重用组件的存储库,其他 Angular 应用程序(我们稍后创建它们)可以把这些可重用组件作为普通 Angular 组件使用,并作为 Web 组件提供给 MVC 视图(也称为 Angular 元素)。


那么,什么是 web component?在webcomponents.org中,是这么定义的:


Web 组件是一组 web 平台 API,这些 API 允许我们创建新的自定义、可重用、封装的 HTML 标记以用于 web 页面和 web 应用程序。


Angular 提供一种方法,通过被称为 Angular 元素的 API 把 Angular 组件打包成 web 组件。例如,如果我们创建一个显示当前时间的 Angular 组件, 并引导该组件作为 Angular 元素当前时间,那么,我们接着可以在纯 HTML 页面中包含这个标签。在Angular官方网站上有更多相关信息。


在 VS Code 中打开我们的 Angular 项目。打开一个终端窗口,输入一个命令以生成时钟组件,并把该组件添加到 app.module.ts 中:


ng g c current-time
复制代码


现在,我们应该在 src/app 下有一个名为 current-time 的文件夹,包含一些组成我们的时钟组件的文件。把 app/current-time/current-time.component.html 改成具有以下标记:


<p>{{ time }}</p>
复制代码


把 app/current-time/current-time.component.ts 改成具有以下代码:


import { Component, OnInit, OnDestroy } from '@angular/core';@Component({  selector: 'app-current-time',  templateUrl: './current-time.component.html',  styleUrls: ['./current-time.component.css']})export class CurrentTimeComponent implements OnInit, OnDestroy {  timer: any;  time: string  constructor() { }  ngOnInit() {    var setTime = () => {      var today = new Date();      this.time = ("0" + today.getHours()).slice(-2) + ":" +                  ("0" + today.getMinutes()).slice(-2) + ":" +                  ("0" + today.getSeconds()).slice(-2);    };    setTime();    this.timer = setInterval(setTime, 500);  }  ngOnDestroy(){    if (this.timer){      clearTimeout(this.timer);    }  }}
复制代码


这个实现相当简单。我们有个每半秒钟触发一次的计时器。该计时器用一个代表当前时间的字符串更新时间属性,并且 HTML 模板绑定到该字符串。


把以下样式粘贴到我们的 app/current-time/current-time.component.css


p {    background-color: darkslategray;    color: lightgreen;    font-weight: bold;    display: inline-block;    padding: 7px;    border: 4px solid black;    border-radius: 5px;    font-family: monospace;}
复制代码


现在,保存所有修改后的文件,让我们把这个时钟组件作为 web 组件引导:


  • 在 Visual Studio Code 中打开一个新的终端窗口

  • 添加 Angular 元素库和 polyfills。在终端窗口输入以下命令实现这个操作:


ng add @angular/elements
复制代码


  • 转到 src/app/app.module.ts

  • 如果那里还没有,那么在 app.module.ts 的顶部添加以下导入语句:


import { createCustomElement } from '@angular/elements';
复制代码


  • 从 @angular/core 给导入添加 Injector:


import { NgModule, Injector } from '@angular/core';
复制代码


  • 用 bootstrap: [AppComponent]替换 entryComponents: [ClockComponent]

  • 最后,给 AppModule 类添加构造函数和 ngDoBootstrap。


constructor(private injector: Injector) {}ngDoBootstrap(){  customElements.define('current-time', createCustomElement(CurrentTimeComponent,                                                      {injector: this.injector}));}
复制代码


现在我们还需要做一件事情,稍后当我们在一个不同的 Angular 应用程序中导入 CurrentTimeComponents 时就会用到。我们需要从这个模块导出该组件。在 providers 上方添加导出属性就可以实现:


exports: [    CurrentTimeComponent],
复制代码


我们的整个 app.module.ts 应如下所示:


import { BrowserModule } from '@angular/platform-browser';import { NgModule, Injector } from '@angular/core';import { AppComponent } from './app.component';import { createCustomElement } from '@angular/elements';import { CurrentTimeComponent } from './current-time/current-time.component';@NgModule({  declarations: [    AppComponent,    CurrentTimeComponent  ],  imports: [    BrowserModule  ],  exports: [    CurrentTimeComponent  ],  providers: [],  entryComponents: [CurrentTimeComponent]})export class AppModule {  constructor(private injector: Injector) {  }  ngDoBootstrap(){    customElements.define('current-time', createCustomElement(CurrentTimeComponent,                                                          {injector: this.injector}));  }}
复制代码


现在,我们来测试一下我们的解决方案是否有用。转到 src\index.html,用替换。在终端窗口输入 ng serve --open 以运行该项目。现在,我们应该在浏览器窗口看到当前时间。


在 ASP.NET 项目中使用 Web 组件

接下来,要让我们的当前时间组件在 ASP.NET MVC Core 项目中可用。在 Visual Studio 中打开 ASP.NET 就可以了。在 Views/Shares/_Layout.cshtml 中的结束标签前粘贴以下代码:


<environment include="Development">    <script type="text/javascript" src="http://localhost:4200/runtime.js"></script>    <script type="text/javascript" src="http://localhost:4200/polyfills.js"></script>    <script type="text/javascript" src="http://localhost:4200/styles.js"></script>    <script type="text/javascript" src="http://localhost:4200/scripts.js"></script>    <script type="text/javascript" src="http://localhost:4200/vendor.js"></script>    <script type="text/javascript" src="http://localhost:4200/main.js"></script></environment><environment exclude="Development">    <script asp-src-include="~/Apps/dist/core/runtime-es2015.*.js" type="module"></script>    <script asp-src-include="~/Apps/dist/core/polyfills-es2015.*.js" type="module"></script>    <script asp-src-include="~/Apps/dist/core/runtime-es5.*.js" nomodule></script>    <script asp-src-include="~/Apps/dist/core/polyfills-es5.*.js" nomodule></script>    <script asp-src-include="~/Apps/dist/core/scripts.*.js"></script>    <script asp-src-include="~/Apps/dist/core/main-es2015.*.js" type="module"></script>   <script asp-src-include="~/Apps/dist/core/main-es5.*.js" nomodule></script></environment>
复制代码


前面的代码段显示了两个块,一个用于开发,一个用于非开发。当我们在开发时,我们托管 web 组件的 Angular 项目将运行于从 VS Code 启动的 4200 端口。当我们投入生产时,该 Angular 项目将被编译到 wwwroot/apps/core 文件夹,并带有附加哈希值命名的 javascript 文件。为了正确地引用这些文件,我们需要使用 asp-src-include 标记助手。


接下来,在_Layout.cshtml 中,在结束标记后面直接添加。


测试我们的开发配置是否有用:


  • 转到打开 Angular 项目的 VS Code,在终端提示符中输入该命令:


     ng serve --liveReload=false
复制代码


  • 转到打开 ASP.NET 项目的 Visual Studio,点击 F5 键运行该项目。我们的 ASP.NET 站点应该打开,并且,我们应该看到在每个页面上显示的当前时间组件。

创建 Angular 应用程序

Web 组件很棒,也许是 web UI 的未来,但是,就今天来说,Angular 项目作为单个页面应用程序(Single Page Applications,简称 SPAs)引导仍有自己的生存空间。


Angular 是围绕着模块的概念设计的,它的一些特性,特别是路由,是与模块而不是组件保持一致的。在混合 Angular 和 ASP.NET 开发时,我的目标是在 MVC 视图中托管 Angular 应用程序。我希望 ASP.NET MVC 提供顶层菜单结构,SPA 提供它们自己的菜单和路由结构,这些都驻留在更大的 MVC 应用程序中 。此外,我希望实现代码重用,这些代码可以在解决方案中的多个 SPAs 中共享,也可以作为 web 组件包含在非 Angular 页面中。


第一步是在 Angular 中创建一个新的应用程序。最简单的实现方法是使用 Angular CLI(命令行接口)。如果还没有这个,在 VS Code 中打开 Angular 项目,并启动一个新的终端窗口。在终端窗口,执行该命令:


ng g application App1 --routing=true
复制代码


这将在配置了路由模块的 Apps\projects\App1 下生成新的 Angular 应用程序。让我们生成两个组件,并设置路由,这样我们可以路由到某处去。从终端窗口执行以下命令:


ng g c Page1 --project=App1
ng g c Page2 --project=App1
复制代码


现在,我们应该在 Apps/Projects/App1/src/app 下看到两个新的组件文件夹 page1 和 page2。


现在,让我们来为这些组件设置路由。把 Apps/Projects/App1/src/app 下的 app.component.html 改成具有这个标记:


<h2>App1</h2>  <a routerLink="/page1" routerLinkActive="active">Page1</a>  <a routerLink="/page2" routerLinkActive="active">Page2</a><router-outlet></router-outlet>
复制代码


并用以下代码更新 Apps/projects/App1/src/app 下的 app-routing.module.ts:


import { NgModule } from '@angular/core';import { Routes, RouterModule } from '@angular/router';import { Page1Component } from './page1/page1.component';import { Page2Component } from './page2/page2.component';const routes: Routes = [  {path: '', redirectTo: 'page1', pathMatch: 'full'},  {path: 'page1', component: Page1Component},  {path: 'page2', component: Page2Component}];@NgModule({  imports: [RouterModule.forRoot(routes)],  exports: [RouterModule]})export class AppRoutingModule { }
复制代码


这只是标准的路由代码。关于 Angular 路由的评论,请访问该页面


现在,让我们来测试我们的新应用程序是否配置得正确。打开一个新的终端窗口,输入以下命令:


ng serve App1 --port 4201 --open
复制代码


我们的浏览器窗口应该打开,我们应该能看到类似以下的内容:



请注意,现在我们在用端口 4201,和我们用于根 Angular 项目的不同。我们创建的每个应用程序都将需要开发环境中一个不同的端口服务于它,但是,在非开发环境中,所有的应用程序、ASP.NET 和 Angular 都将运行于同一个端口上。


现在,该演示的一个目标是实现代码重用。让我们在 App1 中重用来自基础项目的 Angular 组件。为了实现这个目标,要在 App1 的主模块中包含 CurrentTimeComponent 的导入。


转到 Apps/projects/App1/src/app 下的 app.modules.ts,添加以下导入语句:


import { CurrentTimeComponent } from '../../../../src/app/current-time/current-time.component';
复制代码


这里正在发生的事是,我们从根项目中导入 CurrentTimeComponent。或者,我们可以从根项目中导入整个 AppModule。


接下来,把 CurrentTimeComponent 添加到声明列表中:


 declarations: [    AppComponent,    Page1Component,    Page2Component,    CurrentTimeComponent  ],
复制代码


现在,转到 App1 中的 app.component.html,并为当前时间添加标签,就添加在路由器出口的正下方。


<h2>App1</h2><a routerLink="/page1" routerLinkActive="active">Page1</a><a routerLink="/page2" routerLinkActive="active">Page2</a><router-outlet></router-outlet><app-current-time></app-current-time>
复制代码


请注意,我们为这个组件使用了 Angular 标签(app-current-time),而不是 web 组件标签名(current-time)。原因是,我们把该组件作为 Angular 组件包含在内了。App1 完全不知道这个 Angular 组件在其他地方用作 web 组件。


保存所有的文件并检查浏览器。我们的 App1 页面现在应该显示当前时间组件。


把 App1 作为 SPA 集成到 ASP.NET MVC

在这个演练中,我们要做的最后一件事是,把 App1 作为单页面应用程序合并到 ASP.NET MVC 应用程序 。我们希望有以下特性:


  • 该 SPA 应该嵌入 MVC 视图之一。

  • 其应该可以深度链接到一个 SPA 中的页面。

  • 应该支持实时重新加载。


首先,让我们在主控制器上(Home Controller)上设置一个名为 App1 的常规 MVC 视图。


在我们的 MVC 项目中,转到 Controllers/HomeController.cs,并添加以下代码:


[Route("app1/{*url}")]public IActionResult App1(string url){    return View("App1", url);}
复制代码


这个在路由(Route)属性中的{*url}构造告诉 ASP.NET 捕获在 url 变量中/app1/ 段右侧的一切内容。然后,将其传到 Angular 应用程序。


现在,右键单击 View()令牌,然后选择添加视图。调用视图 App1,并点击 Add 按钮。这应该在 Views/Home 中创建一个名为 App1.cshtml 的文件。确保该文件有以下标记:


@{    ViewData["Title"] = "App1";}This is the view for App1.
复制代码


转到 Shared/_Layout.cshtml,并给该视图添加一个链接,就添加在到隐私(Privacy)视图链接的下方。最简单的方法是,复制这个隐私链接标记,并用“App1”这个词替换“Privacy”这个词。


<ul class="navbar-nav flex-grow-1">     <li class="nav-item">          <a class="nav-link text-dark" asp-area="" asp-controller="Home"                    asp-action="Index">Home</a>     </li>     <li class="nav-item">          <a class="nav-link text-dark" asp-area="" asp-controller="Home"                    asp-action="Privacy">Privacy</a>      </li>      <li class="nav-item">            <a class="nav-link text-dark" asp-area="" asp-controller="Home"               asp-action="App1">App1</a>      </li></ul>
复制代码


在_Layout.cshtml 中时,让我们多做一个更改。让我们 web 组件周围添加一些标记,以直观地指明这是一个 web 组件而不是 Angular 组件。添加




和注释就可以做到:


<div class="container">     <partial name="_CookieConsentPartial" />     <main role="main" class="pb-3">         @RenderBody()     </main>     <hr />     This is a web component<br />     <current-time></current-time></div>
复制代码


接下来,我们来测试一下这个应用程序。点击 F5 键,确保可以通过 App1 链接跳转到 App1 视图。



下一步是把 App1 应用程序嵌入 App1 MVC 视图。我们准备使用一个 iframe,它指向在同一个域的 URL。使用 iframe 的好处是可以把 App1 封装在其自身的容器中,但也带来两个挑战:


  • iframe 需要动态地随其内容的变化而调整其大小。

  • 在用户在 Angular 应用程序中跳转时,顶部窗口的地址栏必须改变。


我们将使用 JavaScript 来解决这两个挑战。因为 iframe 指向同一个域,所以,这是唯一可行的方法,从而避免了跨域限制。


但是,在我们这么做之前,我们仍然需要在.NET 代码中做更多的修改。


首先,我们在 Startup 中配置 App1。打开 Startup.cs,并把以下代码添加到配置(Configure)方法中:


app.Map("/apps/app1", builder => {    builder.UseSpa(spa =>    {        if (env.IsDevelopment())        {            spa.UseProxyToSpaDevelopmentServer($"http://localhost:4201/");        }        else        {            var staticPath = Path.Combine(                Directory.GetCurrentDirectory(), $"wwwroot/Apps/dist/app1");            var fileOptions = new StaticFileOptions                { FileProvider = new PhysicalFileProvider(staticPath) };            builder.UseSpaStaticFiles(options: fileOptions);            spa.Options.DefaultPageStaticFileOptions = fileOptions;        }    });});
复制代码


该段代码告诉.NET 核心运行时,把应用程序映射到/apps/app1 路径,以代理到开发中的端口 4201,并期望在非开发环境中的 wwwroot/apps/app1 可用编译后的文件。


但是,我们不希望/apps/app1 的用户使用我们的应用程序。我们希望我们的应用程序在用户转到 App1 视图时可用,App1 视图可以是/home/app1 或只是/app1 URL。


这里是我们打算使用 iframe 的地方。打开 App1.cshtml,并添加以下标记:


<iframe src="/apps/app1/@Model" class="app-container" frameborder="0" scrolling="no"></iframe>
复制代码


请注意 @Model 构造。它被映射到组件中的{*url},我们把路径的一部分从顶部窗口传到 App1 右侧的 iframe,因此,路由在 Angular 应用程序内部进行。


现在,我们可以测试这个应用程序了。转到 VS Code,并从一个可用的终端窗口执行以下 serve 命令:


ng serve App1 --port 4201 --servePath / --baseHref /apps/app1/ --publicHost http://localhost:4201


该命令在 4201 端口启动 App1。由于我们知道准备从 apps/app1 给它提供服务,因此,它设置了基础 HREF,并且,它指示 Angular 使用 localhost:4201 而不是使用相对的 URL 进行实时重载。


转到 Visual Studio,并点击 F5 键。在 ASP.NET 站点出现在浏览器窗口后,转到 App1 菜单。如果看到和下面类似的屏幕,那就意味着该应用程序已经正确地连接上了。



尽管 App1 Angular 应用程序确实出现在 App1 视图中,但是没有内容。如果点击 Page 1 和 Page 2 的链接,可以看到在 Angluar 组件中跳转是正常工作的,但是,在浏览器顶部的地址栏没有反映出跳转的当前状态。让我们来解决这两个问题。


为了在启动时以及 iframe 的内容有变化时调整 iframe 的大小,我们将使用名为 iFrame Resizer 的 JavaScript 组件,iFrame Resizer 是由David Bradshaw创建的。


为了让该组件工作,我们需要执行这三个步骤。


在_Layout.cshtml 中,把以下脚本标签粘贴到指向 site.js 的脚本标签的正上方


<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.min.js"></script>
复制代码


给位于 wwwroot/js 的 site.js 添加以下代码行。


$('.app-container').iFrameResize({ heightCalculationMethod: 'documentElementOffset' });
复制代码


接着,转到 VS Code,并在结束标签的上方给位于 Apps/projects/App1/src 的 Index.html 添加以下脚本标签:


<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.contentWindow.min.js"></script>
复制代码


保存所有的文件,我们来重新测试一下这个应用程序。App1 现在应该如下所示:



请注意,内容不再消失了。iFrame Resizer 的这一点做得很不错,在 iframe 初始加载后,它将不断调整 iframe 的大小以适合内容。


现在,我们来解决这个问题:当点击 Angular 路由器链接时,地址栏没有更新。因为 App1 在 iframe 中运行,因此,地址栏没有更新。iframe 的地址在改变,但是,我们看不到,原因是,我们看到的地址栏是用于顶部浏览器窗口的。


请记住,我们已经有代码可以捕捉/app1 URL 段右侧的路径,并存入{*ulr}变量,再把它传给 iframe。我们需要添加的代码是另一个方式,当路由在 Angular 应用程序中进行时,我们希望把变化传播到顶层地址栏。


我们需要把代码添加到 App1 应用程序中的路由模块中来实现。


打开 Apps/projects/App1/src/app 中的 app-routing.module.ts。在 AppRouting Module 的构造函数中添加以下代码:


constructor(private route:Router){  var topHref = window.top.location.href != window.location.href ?                window.top.location.href.substring(0,                                         window.top.location.href.indexOf('/app1') + 5) :                null;  this.route.events.subscribe(e => {    if(e instanceof NavigationEnd){      if (topHref){        window.top.history.replaceState(window.top.history.state,                                        window.top.document.title, topHref + e.url);      }    }  });}
复制代码


该代码通过比较顶部窗口的 HREF 和当前窗口的 HREF 来确定应用程序是否在 iframe 中运行。如果应用程序在 iframe 中运行,那么,代码把顶部窗口的 HREF 保存在一个局部变量中,但是去掉了指向/app1 段右侧的 HREF 部分。然后,代码进入 NavigationEnd 事件,并把路由过的 URL 追加到顶部窗口的 HREF 的后面。


我们还将需要给导入添加 Router 和 NavigationEnd。整个 app-routing.module.ts 应该如下所示:


import { NgModule } from '@angular/core';import { Routes, RouterModule, Router, NavigationEnd } from '@angular/router';import { Page1Component } from './page1/page1.component';import { Page2Component } from './page2/page2.component';const routes: Routes = [  {path: '', redirectTo: 'page1', pathMatch: 'full'},  {path: 'page1', component: Page1Component},  {path: 'page2', component: Page2Component}];@NgModule({  imports: [RouterModule.forRoot(routes)],  exports: [RouterModule]})export class AppRoutingModule {  constructor(private route:Router){    var topHref = window.top.location.href != window.location.href ?                  window.top.location.href.substring(0,                                     window.top.location.href.indexOf('/app1') + 5) :                  null;    this.route.events.subscribe(e => {      if(e instanceof NavigationEnd){        if (topHref){         window.top.history.replaceState(window.top.history.state,                                         window.top.document.title, topHref + e.url);        }      }    });  }}
复制代码


为了测试该应用程序,请从 Visual Studio 启动它。点击 Page 1 或 Page 2 的链接。观察到顶部 URL 现在在变化。我们还可以复制修改过的 URL,并把它粘贴到一个独立的窗口,App1 将路由到顶部 URL 中指定的组件。

调整发布(Publish)设置

还有最后一件事要做。我们需要修改项目文件,以将 Angular 构建任务纳入发布过程。为此,转到 ASP.NET 项目,右键单击项目文件,选择 Edit .csproj。项目文件应该与如下所示的类似:


<Project Sdk="Microsoft.NET.Sdk.Web">  <PropertyGroup>    <TargetFramework>netcoreapp3.0</TargetFramework>    <TypeScriptToolsVersion>3.3</TypeScriptToolsVersion>    <SpaRoot>Apps\</SpaRoot>  </PropertyGroup>  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">    <WarningLevel>0</WarningLevel>  </PropertyGroup>  <ItemGroup>    <Content Remove="$(SpaRoot)**" />    <None Remove="$(SpaRoot)**" />    <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />  </ItemGroup>  <ItemGroup>    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.0.0" />  </ItemGroup>  <Target Name="PublishApps" AfterTargets="ComputeFilesToPublish">    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod --outputPath=./dist/core" />    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build App1 -- --prod --base-href=/apps/app1/ --outputPath=./dist/app1" />    <ItemGroup>      <DistFiles Include="$(SpaRoot)dist\**" />      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">        <RelativePath>wwwroot\%(DistFiles.Identity)</RelativePath>        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>      </ResolvedFileToPublish>    </ItemGroup>  </Target></Project>
复制代码


这里有个有趣的部分,就是 Target 标签。我们指示构建过程运行 npm 安装,然后构建两个 Angular 项目,接着,复制 dist 文件夹输出到 ASP.NET 站点的 wwwroot 文件夹。


为了测试我们的发布配置是否有用:


  • 在 Visual Studio 中右键单击 ASP.NET 项目名称。

  • 转到 Publish。

  • 在选择(Pick)一个发布目标下,选择文件夹(Folder)。

  • 点击发布(Publish)按钮。


在这个过程的最后,我们应该看到在 Output 窗口中发布的新文件的文件夹的整个路径。为了测试发布的站点:


  • 在命令窗口打开发布文件夹。

  • 输入:dotnet .dll

  • 转到到我们的浏览器,打开 http://localhost:5000

结论

我们创建了一个 ASP.NET 站点,把两个 Angular 项目与它集成在一起,并把 Angular 工件嵌入 MVC 视图。如果我们想试用这个解决方案,建议从 GitHub 中克隆项目。尝试添加 App2,并从不同的 MVC 视图中为它提供服务,或者尝试创建更多的 web 组件。

作者介绍

30 年来,Evgueni Tsygankov 一直在编写软件,从 80 年代的 Commodore 64 一直到如今的云计算。目前,他在 Effita 领导其中的一支开发团队,Effita 是总部在密苏里州圣路易斯的一家软件公司。在空闲的时候,Evgueni 把时间用于陪伴他的两个孩子以及打冰球和踢足球。


原文链接:


Angular & ASP.NET Core 3.0 - Deep Dive


2019-11-25 08:002947

评论 1 条评论

发布
用户头像
期待vue版的出来!
2019-11-29 07:42
回复
没有更多了
发现更多内容

发布仅1小时Github破万赞!这份LeetCode算法刷题手册真是离谱

了不起的程序猿

Java 程序员 LeetCode 数据结构算法

深入思考Schema管理的几个基本问题

HackMSF

完美!华为大佬手码20w字Redis全栈小册,原来Redis性能可压榨到极致

Java全栈架构师

数据库 redis 程序员 面试 后端

【大话 C 语言】春眠不觉晓,函数知多少?

Albert Edison

递归 C语言 函数 开发语言 9月月更

如何成为资深的测试专家

穿过生命散发芬芳

测试 9月月更

中小企业集成AI人工智能的窘境

felix

人工智能 中小企业 开放应用模型

软件复杂性的来源与应对

源字节1号

软件开发 前端开发 后端开发 小程序开发

分布式技术难学?谷歌大神首发纯手撸ZK+Dubbo笔记,网友看完直呼NB

收到请回复

Java zookeeper 架构 分布式 语言 & 开发

数据存储与物联网

CnosDB

IoT 时序数据库 开源社区 CnosDB infra

重学网络系列之(我的名字叫IP)

自然

网络 9月月更

PANAMA: 共享机器学习集群的网内聚合框架

俞凡

大数据 架构 网络

挑战30天学完Python:Day1火力全开-初识Python(含系列大纲)

MegaQi

9月月更 挑战30天学完Python

设计模式的艺术 第十二章装饰设计模式练习(开发一个数据加密模块,可以对字符串进行加密。最简单的加密算法通过对字母移位来实现,同时提供了稍复杂的逆向输出加密和更高级的求模加密。用户先用最简单的算法加密,如果觉得不够,可以使用其他算法进行二次加密和三次加密)

代廉洁

设计模式的艺术

小六六学Netty系列之Java BIO

自然

网络 9月月更 neety

如果你是Java程序员,你会选择Cloud Studio进行云端开发,放弃IDEA吗?

wljslmz

Java Cloud Studio 9月月更

常见的网络安全攻击及防御技术概述

阿泽🧸

网络安全 9月月更

C++后台开发学习路线(已多人拿下腾讯后台开发)

C++后台开发

后台开发 后端开发 C++后台开发 C++开发 腾讯后台开发

设计模式的艺术 第十一章组合设计模式练习(开发一个界面控件库。界面控件分为两大类:一类是单元控件,例如按钮、文本框等;另一类是容器控件,例如窗体、中间面板等。试用组合模式设计该界面控件库)

代廉洁

设计模式的艺术

首次发布!Java面试八股文让569人成功进入大厂,堪称2022最强面试八股文核心知识版!

退休的汤姆

Java 程序员 面经 秋招 Java八股文

设计模式的艺术 第十三章外观设计模式练习(为新开发的智能手机控制与管理软件提供一键备份功能。通过该功能可以将原本存储在手机中的通讯录、短信、照片、歌曲等资料一次性地全部复制到移动存储介质(如MMC卡或SD卡)中。实现过程中需要与多个已有的类进行交互)

代廉洁

设计模式的艺术

数据治理的内核:元数据管理

Taylor

数据治理 数字化 元数据 元数据管理 元数据管理平台

DPDK技术学习路线总结,虚拟化专家之路

C++后台开发

后台开发 DPDK VPP OvS DPDK开发

秋招国内大厂最牛的Java面试八股文合集(全彩版),不接受反驳

退休的汤姆

Java 程序员 面经 Java工程师 秋招

《游戏机图鉴》:发展、继承、崩溃、复兴,游戏机的前世今生

图灵社区

科普 游戏机

腾讯T4整合Spring+Spring MVC+MyBatis+Redis实现

退休的汤姆

Java 程序员 面经 Java工程师 秋招

在世界人工智能大会,看京东AI向产业奔涌

脑极体

Java工程师丨面试必会进程线程问答

陈橘又青

Java 面试 9月月更

程序人生 | 编程的上帝视角应该怎么去找

小明Java问道之路

程序人生 编程思维 如何学习 9月月更 计算机思维

费时3个月啃烂了这份Redis技术笔记,我成功上岸进了字节

收到请回复

redis 架构 语言 & 开发 Java core redis 底层原理

小六六学Netty系列之Java NIO(一)

自然

网络 9月月更 neety

C++学习------cerrno头文件的作用与源码学习

桑榆

c++ 9月月更

深入研究Angular和ASP.NET Core 3.0_大前端_Evgueni Tsygankov_InfoQ精选文章