BT

Início Artigos Angular e ASP.NET Core 3.0 na prática

Angular e ASP.NET Core 3.0 na prática

Favoritos

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.

 

 

Avalie esse artigo

Relevância
Estilo/Redação

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Comentários da comunidade

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

BT

Seu cadastro no InfoQ está atualizado? Poderia rever suas informações?

Nota: se você alterar seu email, receberá uma mensagem de confirmação

Nome da empresa:
Cargo/papel na empresa:
Tamanho da empresa:
País:
Estado:
Você vai receber um email para validação do novo endereço. Esta janela pop-up fechará em instantes.