深入研究 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 中都復制外觀和菜單結構?;蛘?#xff0c;我們可以讓單個 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 項目
請確保以下軟件都已安裝(全部是免費的):
Visual Studio Code
Node.js
Angular CLI。為了安裝這個,請轉到命令提示符并執行以下命令:
復制代碼
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 以運行該項目?,F在,我們應該在瀏覽器窗口看到當前時間。
在 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/ --publicHosthttp://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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Core 3.0 使用Nswa
- 下一篇: 网易裁员背后,芸芸众生,相煎何急