Pontos Principais
- Várias aplicações Angular podem ser integradas a um website ASP.NET;
- Empacotar o código Angular como Web Components é uma boa maneira de inicializar uma aplicação;
- Os Web Components escritos em Angular podem ser facilmente integrados aos modos de exibição do ASP.NET;
- Estruturar a solução Angular como uma coleção de aplicações leva a uma melhor reutilização de código;
- O uso do Angular com o ASP.NET cria uma plataforma robusta para desenvolvimento de aplicações da Web.
Este artigo faz parte da série educacional .NET que explora os benefícios da tecnologia e como pode ajudar não apenas os desenvolvedores .NET tradicionais, mas também todos àqueles que precisam trazer soluções robustas, com desempenho e econômicas ao mercado.
Com o lançamento do .NET Core 3.0, a Microsoft possui a nova versão da plataforma focada no uso geral, modular, multiplataforma e open source lançada inicialmente em 2016. O .NET Core foi criado inicialmente para permitir a próxima geração das soluções ASP.NET, mas agora direciona e é a base para muitos outros cenários, incluindo IoT, nuvem e soluções móveis. A versão 3 inclui vários recursos frequentemente solicitados, como suporte para WinForms, WPF e Entity Framework 6.
Desafio
A maneira mais fácil de começar a usar o Angular e o ASP.NET Core é usar um template do Visual Studio fornecido pela Microsoft, que coloca tudo em funcionamento rapidamente porém, possui uma grande limitação, o Angular assume a interface do usuário, deixando o ASP.NET em segundo plano, servindo uma API. Se desejamos que em algumas páginas o .NET esteja em primeiro plano e outras o Angular, é necessário duplicar a aparência e a estrutura do menu tanto no ASP.NET Core quanto no Angular. Como alternativa, a interface do usuário pode ser inteiramente atendida por uma única aplicação Angular, todavia, será necessário implementar todas as páginas, incluindo conteúdo estático e dinâmico, como Contato, Licenças, Quem Somos, no Angular SPA.
A configuração que desejo é um site ASP.NET que sirva como um portal e, em seguida, incorpore artefatos Angular nas páginas ASP.NET. De um modo geral, existem dois padrões arquitetônicos principais. Sob o primeiro padrão de design, temos uma aplicação Angular com roteamento que desejamos incorporar em uma exibição ASP.NET com Angular fornecendo um submenu e o site ASP.NET fornecendo o menu de nível superior. Sob o segundo padrão de design, temos os componentes Angular que não justificam uma aplicação completa, mas ainda é necessário incorporá-los nos modos de exibição do ASP.NET. Por exemplo, suponhamos que nós queremos incorporar um componente que exibe a hora atual em uma exibição do ASP.NET. É fácil desenvolver esse componente no Angular, mas é complicado incorporá-lo às visualizações do MVC. Por fim, queremos ter o máximo de reutilização de código, para aproveitar componentes entre aplicações Angular e incorporar os mesmos nos modos de exibição do ASP.NET. Este artigo demonstra como inicializar os projetos ASP.NET e Angular para acomodar esses padrões de design de arquitetura. Se desejamos ver o código-fonte final, podemos consultar o repositório Multi App Demo no GitHub.
Para recapitular, queremos construir:
- Um site ASP.NET Core que atua como um portal com uma estrutura de menus em que cada menu abre uma exibição do MVC;
- Capacidade de hospedar um ou mais SPAs em Angular no site;
- Capacidade de reutilizar alguns dos componentes desses SPAs nos modos de exibição do ASP.NET.
Visão geral da implementação
- Criaremos um site ASP.NET MVC usando o .NET Core (versão 3.0 no momento da redação deste artigo);
- Criaremos um projeto Angular usando a Angular CLI e iremos integrá-lo (fluxo de trabalho de desenvolvedor, criação de produtos, publicação, etc.) no projeto ASP.NET;
- Iniciaremos o projeto Angular como componentes da Web (em outras palavras, Custom Elements, também conhecido como Angular Elements);
- Usaremos o Angular CLI para gerar as aplicações, que farão referência ao projeto Angular raiz para componentes reutilizáveis;
- Incorporaremos as aplicações Angular nas visualizações ASP.NET usando iFrames apontando para o mesmo domínio.
- Os iFrames usarão o JavaScript para redimensionar o conteúdo e integraremos o roteamento do ASP.NET com aplicações Angular construídas com iFrames, para que seja possível favoritar o conteúdo nas visualizações Angular roteadas.
- Para hospedar componentes Angular nos modos de exibição ASP.NET, empacotaremos os componentes como Web Components.
Criando o projeto ASP.NET Core MVC
Utilizaremos o Visual Studio 2019 Community Edition, que pode ser baixado gratuitamente no site da Microsoft. A partir de 2019, a seleção automática de templates é diferente das versões anteriores, mas, independentemente da versão que possuímos, as etapas são as mesmas.
- Criar um novo projeto;
- Selecionar o ASP.NET Core Web Application.
- Selecionar o nome e o local para o projeto (no caso chamaremos este projeto de MultiAppDemo).
- Escolher o ASP.NET Core (no caso deste artigo utilizaremos a versão 3.0).
- Por fim, escolher o ASP.NET Model-View-Controller (por uma questão de simplicidade, vamos selecionar sem autenticação, para que o VS (Visual Studio) não gere artefatos irrelevantes para este passo a passo).
A visualização do nosso projeto no Solution Explorer deve ser parecido com a imagem abaixo:
Como usamos o modelo ASP.NET MVC e não o modelo SPA, precisamos adicionar o pacote NuGet Microsoft.AspNetCore.SpaServices.Extensions. Para instalar o pacote, vamos abrir o Package Manager Console e executar a seguinte instrução:
Install-Package Microsoft.AspNetCore.SpaServices.Extensions
Criando um projeto Angular
Antes de mais nada, precisamos verificar se temos os seguintes softwares instalados (todos gratuitos):
- Visual Studio Code
- Node.js
- Angular CLI. Para instalar, vamos abrir o prompt de comando e executar este comando:
npm install -g @angular/cli
.
Para este exemplo, utilizaremos o Angular v8. Usar uma versão anterior é totalmente aceitável, desde que tenhamos acesso à API createCustomElement. Esses recursos também estão disponíveis no Angular v7.
Para criarmos uma solução Angular a ser usada no projeto ASP.NET MVC, precisamos abrir o prompt de comando e navegar até a pasta que contém o arquivo do projeto MVC (extensão .csproj). Quando estivermos lá, podemos criar o projeto em Angular executando o comando abaixo:
ng new Apps
Escolha N para roteamento e CSS para o estilo.
Mude o diretório para o Apps e então, digitar o seguinte (incluindo o ponto da direita):
code .
Agora teremos o projeto Angular aberto no Visual Studio Code.
Inicializando os elementos do Angular
A idéia é usar o projeto raiz como um repositório para componentes reutilizáveis que estarão disponíveis para outras aplicações Angular (vamos criá-los mais tarde) como componentes Angular normais e para as visualizações do MVC como Web Components (também conhecidos como Angular Elements).
Mas antes de mais nada, o que é um Web Component? Aqui está uma definição do webcomponents.org:
Os Web Components são um conjunto de APIs da plataforma web que permitem criar novas tags HTML personalizadas, reutilizáveis e encapsuladas para uso em páginas e aplicações web.
O Angular fornece uma maneira de empacotar componentes como componentes web, por meio de uma API chamada Angular Elements. Por exemplo, se criarmos um componente Angular que mostra a hora atual e inicializarmos esse componente como elemento Angular de horário atual, poderemos incluir a tag <current-time /> nas páginas HTML de maneira simples. Para mais informações, utilize o site oficial da Angular.
Agora, vamos abrir o projeto Angular no VS Code, abrindo uma janela no terminal e digitando um comando para gerar o componente relógio e adicioná-lo no componente do app.module.ts:
ng g c current-time
Depois de executar o comando, teremos uma pasta chamada current-time dentro do src/app com vários arquivos que formam o componente do relógio. Vamos alterar o app/current-time/current-time.component.html para ter a seguinte marcação:
<p>{{time}}</p>
Altere o app/current-time/current-time.component.ts para ter o seguinte código:
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);
}
}
}
A implementação é bastante simples. Temos um timer que dispara a cada meio segundo. O cronômetro atualiza a propriedade time com uma sequência que representa a hora atual e o modelo HTML é vinculado a essa sequência.
Vamos colar os estilos abaixo no 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;
}
Agora, salve todos os arquivos modificados para então inicializar esse componente do relógio como um componente web:
- Abra um novo terminal dentro do Visual Studio Code.
- Adicione as bibliotecas Angular Elements e polyfills. Para fazer isso, basta digitar o seguinte comando na janela do terminal:
ng add @ angular/elements
- Depois de adicionado, navegue para o src/app/app.module.ts
- Se os arquivos ainda não estiverem lá, precisamos adicionar a seguinte declaração de importação na parte superior do app.module.ts:
import {createCustomElement} de '@ angular/elements';
- Adicione o Injector à importação do @angular/core:
import {NgModule, Injector} de '@angular/core';
- Substitua a inicialização: [AppComponent] por
entryComponents: [ClockComponent]
- Por fim, adicione o construtor e ngDoBootstrap à classe AppModule.
constructor(private injector: Injector) {
}
ngDoBootstrap(){
customElements.define('current-time', createCustomElement(CurrentTimeComponent,
{injector: this.injector}));
}
Precisamos realizar mais uma etapa, que será necessária mais tarde quando formos tentar importar o CurrentTimeComponent em uma aplicação Angular diferente. Devemos exportar esse componente do módulo. Para fazer isso, vamos adicionar a propriedade exports logo acima dos provedores:
exports: [
CurrentTimeComponent
],
Todo o app.module.ts deve ficar assim:
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}));
}
}
Agora, vamos testar se a solução funciona. Vamos para src\index.html e vamos substituir <app-root></app-root> por <current-time></current-time>. Digite ng serve --open no terminal para executar o projeto. Agora devemos ver a hora atual exibida no navegador.
Usando os Web Components no projeto ASP.NET
A próxima etapa é disponibilizar o componente de tempo atual no ASP.NET MVC Core Project. Para fazer isso, precisamos abrir o projeto ASP.NET no Visual Studio. Depois, cole o seguinte código antes da tag de fechamento </body> em 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>
O código acima tem dois blocos, um de desenvolvimento e outro não. Quando estamos em desenvolvimento, o projeto Angular que hospeda os componentes web estará em execução na porta 4200, iniciada no VS Code. Quando estivermos em produção, o projeto Angular será compilado na pasta wwwroot/apps/core com arquivos javascript nomeados com hashes anexadas. Para referenciar corretamente os arquivos, usaremos os auxiliares de tag asp-src-include.
Em seguida, no _Layout.cshtml, adicione <current-time></current-time> logo após a tag de fechamento </main>.
Para testar se a configuração de desenvolvimento funciona, faça o seguinte:
- Vá para o VS Code, onde o projeto Angular está aberto, e digite o seguinte comando no prompt do terminal:
ng serve --liveReload = false
; - Agora, vá para o Visual Studio onde o projeto do ASP.NET está aberto e pressione F5 para executar o projeto.
O site ASP.NET deve abrir e devemos ver o componente de horário atual exibido em todas as páginas.
Criando a aplicação Angular
Os Web Components são ótimos e podem ser o futuro das UIs da Web, entretanto, ainda precisamos de um lugar para inicializar os projetos Angular como SPAs (aplicativos de página única).
O Angular é projetado com base no conceito de módulos e alguns dos recursos, principalmente o roteamento, estão alinhados a eles, não aos componentes. Ao misturar o desenvolvimento Angular e ASP.NET, o objetivo é hospedar as aplicações Angular nos modos de exibição MVC. Para isso, é necessário que o ASP.NET MVC forneça a estrutura de menus de nível superior e os SPAs forneçam os próprios menus e estruturas de roteamento que residem em uma aplicação MVC maior. Além disso, precisamos obter a reutilização de código para que este possa ser compartilhado entre vários SPAs da solução, incluindo também as páginas que não são Angular como componentes web.
O primeiro passo é criar um novo aplicativo no Angular. A maneira mais fácil de fazer isso é usar a Angular CLI (interface da linha de comandos). Se ainda não tivermos essa ferramenta, vamos abrir o projeto Angular no VS Code, iniciar um novo terminal e posteriormente executar o comando:
ng g application App1 --routing=true
Isso gerará uma nova aplicação Angular em Apps\projects\App1 com o módulo de roteamento configurado. Vamos gerar dois componentes e configurar as rotas, para que possamos ter um lugar para rotear. Execute os dois comandos abaixo no terminal:
ng g c Page1 --project=App1
ng g c Page2 --project=App1
Agora veremos duas novas pastas de componentes, page1 e page2, em Apps/Projects/App1/src/app.
Vamos configurar o roteamento para esses componentes, alterando o app.component.html em Apps/Projects/App1/src/app para obter esta marcação:
<h2>App1</h2>
<a routerLink="/page1" routerLinkActive="active">Page1</a>
<a routerLink="/page2" routerLinkActive="active">Page2</a>
<router-outlet></router-outlet>
E depois, atualizar o app-routing.module.ts em Apps/projects/App1/src/app com o seguinte código:
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 { }
Este é o código de roteamento padrão. Para uma análise mais detalhada do roteamento do Angular, visite a página oficial.
Vamos agora testar se a nova aplicação está configurada corretamente. Vamos abrir um novo terminal para digitar o seguinte comando:
ng serve App1 --port 4201 --open
O navegador deve abrir e devemos ver algo assim:
Observe que estamos usando a porta 4201, que é diferente da porta que usamos para o projeto Angular raiz. Cada aplicação que criamos precisará ser servida em uma porta diferente no desenvolvimento, mas em ambientes que não sejam dev, as aplicações ASP.NET e Angular serão executados na mesma porta.
Um objetivo desta demonstração é conseguir reutilizar o código. Vamos agora reutilizar o componente Angular do projeto base no App1. Para fazer isso, vamos incluir uma importação do CurrentTimeComponent no módulo principal do App1.
Acesse o app.module.ts em Apps/projects/App1/src/app e adicione a seguinte declaração de importação:
import { CurrentTimeComponent } from '../../../../src/app/current-time/current-time.component';
O que está acontecendo aqui é que estamos importando o CurrentTimeComponent do projeto raiz. Como alternativa, podemos importar o AppModule inteiro do projeto raiz.
Em seguida, adicione o CurrentTimeComponent à lista de declarações:
declarations: [
AppComponent,
Page1Component,
Page2Component,
CurrentTimeComponent
],
Agora, vá para app.component.html em App1 para adicionar a tag para o horário atual, logo abaixo do roteamento.
<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>
Observe que estamos usando a tag Angular (app-current-time) para este componente, e não o nome da tag do componente web (current-time). Isso ocorre porque estamos incluindo o componente como sendo um componente Angular. O App1 não tem conhecimento desse componente Angular usado em outro lugar como componente web.
Vamos salvar todos os arquivos e verificar o navegador. Nossa página do App1 agora deve mostrar o componente de horário atual.
Integrando App1 no ASP.NET MVC como um SPA
A última coisa que precisamos fazer neste passo a passo é incorporar o App1 na aplicação ASP.NET MVC como um SPA. Para isso, precisamos dos seguintes recursos:
- O SPA deve ser incorporado em uma das visualizações do MVC;
- Deve ser possível fazer o link direto para uma página SPA;
- O carregamento ao vivo deve ser suportado.
Primeiro, vamos configurar uma view MVC comum chamada App1 fora da Home Controller.
No projeto MVC, vamos para para Controllers/HomeController.cs para adicionarmos o seguinte código:
[Route("app1/{*url}")]
public IActionResult App1(string url)
{
return View("App1", url);
}
A construção {* url} no atributo Route diz ao ASP.NET para capturar tudo à direita do segmento /app1/ na variável url. Passaremos isso para a aplicação Angular.
Agora, vamos clicar com o botão direito do mouse no token View() e selecionar Add View. Vamos chamar o modo de exibição App1 e clicar no botão Adicionar. Isso deve criar um arquivo chamado App1.cshtml no Views/Home. Verifiquemos se o arquivo tem a seguinte marcação:
@{
ViewData["Title"] = "App1";
}
This is the view for App1.
Vamos para Shared/_Layout.cshtml para adicionar um link para esta visualização logo abaixo do link Privacy view. A maneira mais fácil é copiar a marcação do Privacy view e substituir a palavra Privacy pela palavra App1.
<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>
Enquanto estamos no _Layout.cshtml, vamos fazer mais uma alteração, adicionando um pouco de marcação ao redor do componente web <current-time> para ter uma indicação visual de que este é um componente web e não um componente Angular. Para fazer isso, vamos adicionar o <HR> e um comentário:
<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>
Em seguida, vamos testar a aplicação. Pressionamos F5 e certifiquemos de navegar para a visualização App1 através do link App1.
A próxima etapa é incorporar a aplicação App1 ao modo de exibição App1 MVC. Nós vamos usar um iframe que aponta para uma URL com mesmo domínio. O uso de um iframe possui o benefício de encapsular o App1 no próprio container, mas apresenta dois desafios:
- O iframe precisa ser redimensionado dinamicamente quando o conteúdo for alterado.
- A barra de endereço da janela superior precisa ser alterada quando um usuário navega dentro da aplicação Angular.
Resolveremos esses dois desafios usando JavaScript. Isso só é possível porque o iframe aponta para o mesmo domínio, evitando assim as restrições entre domínios.
Mas, antes de fazer isso, ainda precisamos fazer mais algumas modificações no código .NET.
Primeiro, vamos configurar o App1 no Startup. Vamos abrir o Startup.cs e adicionar o seguinte código no método 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;
}
});
});
Esse código informa ao runtime do .NET core para mapear a aplicação para o caminho /apps/app1, para o proxy da porta 4201 em desenvolvimento e esperar que os arquivos compilados estejam disponíveis no wwwroot/apps/app1 em ambientes de não desenvolvimento.
Mas não queremos que a aplicação seja veiculado para um usuário em /apps/app1. Queremos que a aplicação esteja disponível quando um usuário navega para a visualização App1, que pode ser /home/app1 ou apenas as URLs /app1.
É aqui que vamos usar um iframe. Vamos abrir o App1.cshtml para adicionarmos a seguinte marcação:
<iframe src="/apps/app1/@Model" class="app-container" frameborder="0" scrolling="no"></iframe>
Observemos a construção @Model, que é mapeado para {* url} no componente. Estamos passando a parte do caminho à direita do app1 da janela superior para o iframe, para que o roteamento funcione dentro da aplicação Angular.
Neste ponto, podemos testar a aplicação. Vamos para o VS Code para executarmos o seguinte comando, através de um terminal disponível:
ng serve App1 --port 4201 --servePath / --baseHref /apps/app1/ --publicHost http://localhost:4201
Este comando inicia o App1 na porta 4201, e define o HREF base, pois sabemos que será veiculado no apps/app1 e instrui o Angular a usar o localhost:4201 para recarregar ao vivo em vez de usar URLs relativas.
Vamos para o Visual Studio e pressionemos F5. Depois que o site ASP.NET aparecer no navegador, naveguemos até o menu App1. Se virmos uma tela semelhante à seguinte, significa que a aplicação está conectada corretamente.
Enquanto a aplicação App1 do Angular aparece dentro da visualização App1, o conteúdo estará cortado, além disso, se clicarmos nos links Page 1 e Page 2, poderemos ver que a navegação está funcionando dentro do componente Angular, mas a barra de endereço superior no navegador não reflete o estado atual da navegação. Vamos corrigir esses dois problemas.
Para redimensionar o iframe com o conteúdo na inicialização e sempre que o conteúdo do iframe mudar, usaremos um componente JavaScript chamado iFrame Resizer, criado por David Bradshaw.
Há três etapas que precisamos executar para fazer esse componente funcionar.
Em _Layout.cshtml, colemos a seguinte tag script logo acima da tag de script que aponta para site.js
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.min.js">
</script>
Adicionemos a seguinte linha de código no site.js, localizado em wwwroot/js.
$('.app-container').iFrameResize({ heightCalculationMethod: 'documentElementOffset' });
Em seguida, vamos para o VS Code e logo acima da tag de fechamento </body>, adicionemos a seguinte tag de script ao Index.html localizado em Apps/projects/App1/src:
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.contentWindow.min.js">
</script>
Vamos salvar todos os arquivos e testar novamente a aplicação. O App1 agora deve ficar assim:
Observe como o conteúdo está mais cortado. O bom do iFrame Resizer é que ele manterá o iframe redimensionado para caber no conteúdo após o carregamento inicial do iframe.
Vamos agora resolver o problema da barra de endereços que não está sendo atualizada quando os links do Angular roteados são clicados. Não é atualizado porque o App1 está sendo executado dentro de um iframe. O endereço do iframe está mudando, mas não o vemos porque a barra de endereços que está visível é para a janela superior do navegador.
Lembremos de que já temos o código para capturar o caminho à direita do segmento de URL /app1 na variável {* url} para passar para o iframe. O que precisamos adicionar é o código contrário, quando o roteamento é ativado dentro da aplicação Angular, queremos que as alterações sejam propagadas para a barra de endereço superior.
Para fazer isso, precisamos adicionar o código ao módulo de roteamento na aplicação App1.
Vamos abra o app-routing.module.ts localizado em Apps/projects/App1/src/app. E adicionemos o seguinte código no construtor do 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);
}
}
});
}
Esse código determina se a aplicação está sendo executada no iframe comparando o href da janela superior com a janela atual. Se a aplicação estiver sendo executado no iframe, o código salvará o HREF da janela superior em uma variável local, mas corta para do HREF à direita do segmento /app1. Em seguida, o código entra no evento NavigationEnd e anexa a URL roteada ao HREF da janela superior.
Também precisaremos adicionar o Router e NavigationEnd às importações. Todo o app-routing.module.ts deve ficar assim:
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);
}
}
});
}
}
Para testar a aplicação, iniciemos o Visual Studiom, e cliquemos nos links Page1 ou Page2. Podemos observar que a URL principal agora está mudando, e podemos copiar a URL modificada e colá-la em uma janela separada, e o App1 será roteado para o componente especificado na URL superior.
Ajustando as configurações de publicação
Há uma última coisa a ser feita. Precisamos modificar o arquivo do projeto para incorporar as tarefas de construção Angular no processo de publicação. Para fazer isso, vamos para o projeto ASP.NET, cliquemos com o botão direito do mouse no arquivo do projeto, selecionemos Editar <YourProjectName>.csproj. O arquivo do projeto deve ser semelhante a este abaixo:
<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>
A parte interessante é a tag Target. Instruímos o processo de compilação a executar o npm install, que compila os dois projetos Angular e copia a saída da pasta dist para a pasta wwwroot do site ASP.NET.
Para testar se a configuração de publicação funciona:
- Clique com o botão direito do mouse no nome do projeto ASP.NET no Visual Studio;
- Vá em Publish;
- Em Pick a publish target, escolha a opção Folder;
- Por fim, clique no botão Publish.
No final do processo, veremos o caminho completo da pasta em que os novos arquivos foram publicados na janela Output. Para testar o site publicado:
- Abra a pasta da publicação no terminal;
- Depois, digite o comando:
dotnet <Nome do seu projeto>.dll
; - Por fim, vá para o navegador e abra o http://localhost:5000
Conclusão
Criamos um site ASP.NET, o integramos a dois projetos Angular e incorporamos artefatos Angular nas views do MVC. Se quiser brincar com a solução, encorajo a clonar o projeto do GitHub. Tente adicionar o App2 e servi-lo a partir de uma view diferente do MVC ou tente criar mais componentes web.
Sobre o autor
Evgueni Tsygankov desenvolve softwares há 30 anos, desde o Commodore 64 nos anos 80 até a computação em nuvem atualmente. Lidera uma equipe de desenvolvedores como parte da Effita, uma empresa de software com sede em St Louis, Missouri. Nas horas vagas, Evgueni passa o seu tempo com os dois filhos e joga hóquei no gelo e futebol.