BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Single Page Applications e ASP.NET Core 3.0

Single Page Applications e ASP.NET Core 3.0

Favoritos

Pontos Principais

  • Os desenvolvedores continuam migrando para os Single Page Applications (SPAs), embora o desenvolvimento client-side não implica que precisemos de um SPA sempre;
  • Existem várias maneiras de usar as estruturas do SPA com o ASP.NET Core, incluindo middleware, conexão baseada em diretório ou integração ao projeto;
  • O middleware e os métodos baseados em diretório são acompanhados de algumas restrições, como suporte limitado à plataforma e complexidades de implantação e dependência;
  • A integração direta dos SPAs pode criar ilhas de funcionalidade nos sites;
  • A escolha do método a ser utilizado geralmente tem mais a ver com habilidade e adequação do que com a pureza arquitetônica.

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 aqueles 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 para uso geral, modular, multiplataforma e de código aberto 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.

O desenvolvimento web está mudando bastante nos últimos anos, devido a maturidade do Angular, React, Vue e demais ferramentas. Passamos da criação de web pages para a criação de aplicações, da renderização no servidor para a renderização no navegador. Na maioria dos casos, essa mudança realmente aumenta a usabilidade baseada no navegador, entretanto, à medida que os desenvolvedores continuam com a migração para o desenvolvimento client-side, muitos se perguntam se ainda devem usar o ASP.NET.

Single Page Applications e desenvolvimento web

Os Single Page Applications (SPAs) não são uma invenção nova, mas estão se apossando do mundo do desenvolvimento web de várias maneiras diferentes. Para alguns sites, como o Facebook e o Twitter, a mudança para os SPAs foi bem útil, e é por isso que algumas estruturas do SPA vieram das empresas responsáveis por esses sites. Os desenvolvedores de todo o mundo notaram e implementaram os SPAs em seus sites e, especialmente, nas aplicações corporativas.

Se vocês me conhecem, sabem que gosto do desenvolvimento client-side, mas o termo "SPA" me dá calafrios. Prefiro pensar em frameworks client-side como Angular, React e Vue como uma maneira de melhorar o site. Ao criar um único e enorme SPA, estamos voltando para o tempo das aplicações monolíticas e do tempo do Visual Basic 5.

Vejo essas estruturas como criadoras de ilhas de funcionalidade, focadas no que o código client-side faz muito bem, aumentar a interação do usuário. Quando substituímos o site inteiro por um SPA, é necessário utilizarmos outros métodos para melhorar a otimização do mecanismo de pesquisa e imitar as URLs de destino para que pareça um site com múltiplas páginas. A menos que estejamos criando um Twitter ou Facebook da vida, simplesmente não entendo o motivo de utilizarmos o SPA nestes casos.

Usando Frameworks de SPA com o ASP.NET Core

Se somos novatos no ASP.NET Core, talvez não saibamos que existem várias maneiras de usar as estruturas de SPA com ele:

  • A Microsoft criou um middleware para o Angular e o React;
  • Usar um SPA no próprio diretório e criar um Projeto do ASP.NET Core;
  • Integrar uma estrutura SPA diretamente em um projeto.

Vamos falar sobre cada um deles.

O Middleware do SPA

A Microsoft criou uma solução middleware para dar suporte com mais facilidade ao uso dos SPAs criados para o próprio ecossistema. O middleware adota uma abordagem bem prática, que permite que um SPA fique em um subdiretório e trabalhe diretamente de forma isolada. Veja como funciona, abaixo.

Como usar o método Middleware

Podemos criar essa abordagem diretamente no novo modelo de projeto no Visual Studio, como mostra a Figura 1.

Figura 1

Essa abordagem funciona quando temos um projeto grande em SPA (neste exemplo, usando o Angular) em um diretório ClientApp, como mostra a Figura 2.

Figura 2

No ASP.NET Core 3.0, os SPAs funcionam por meio de um middleware que aponta para esse diretório (indiferente do que chamamos). Isso é feito injetando o middleware como sendo a última etapa no método de configuração da inicialização:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {

      // ...

      if (!env .IsDevelopment())
      {
        app.UseSpaStaticFiles();
      }

      app.UseRouting();

      app.UseEndpoints(endpoints =>
      {
        endpoints.MapControllerRoute(
                  name: "default",
                  pattern: "{controller}/{action=Index}/{id?}");
      });

      app.UseSpa(spa =>
      {
        // To learn more about options for serving an Angular SPA from ASP.NET Core,
        // see https://go.microsoft.com/fwlink/?linkid=864501

        spa.Options.SourcePath = "ClientApp";

        if (env.IsDevelopment())
        {
          spa.UseAngularCliServer(npmScript: "start");
        }
      });
    }

Isso envolve duas chamadas: UseSpaStaticFiles e UseSpa. Essas chamadas configuram a capacidade de receber chamadas de um index.html criado pelo Angular-CLI. Nesse caso, veremos que durante o desenvolvimento, será inicializado o 'npm start', que faz parte de como o middleware inicializa o desenvolvimento da aplicação Angular.

Isso é semelhante à maneira como o middleware do React funciona. Atualmente, não há suporte para outras plataformas (como Vue, Backbone etc.). A outra limitação é que, embora exista uma versão antiga do middleware Spa que funcionava no ASP.NET Core 2.0, a nova versão só funciona a partir do ASP.NET Core 2.1.

Usar o SPA como subdiretório do ASP.NET Core

Embora o middleware seja excelente opção, se estivermos criando apenas um monolito em SPA sem uso para outras páginas web, acredito que seja a realidade para a maioria dos desenvolvedores, a integração do ASP.NET Core em um aplicativo MVC tradicional faz mais sentido. Normalmente, podemos manter um SPA em uma pasta separada e tratá-lo como um projeto filho, sem ter que fazer muitas integrações entre ambos os projetos.

Como usar a abordagem de subdiretório do SPA

Vejamos este exemplo. Suponha que tenhamos um projeto Angular em um subdiretório chamado ClientApp, como na Figura 3.

Figura 3

Como esse diretório possui o próprio package.json, podemos usar a CLI da biblioteca com a qual estamos trabalhando para criar compilações. Podemos até desenvolvê-lo isoladamente sem invocar o projeto ASP.NET Core, mas como normalmente adicionamos o SPA a uma exibição existente do Razor, geralmente executamos os dois.

Um problema deste método é que o diretório de compilação do projeto SPA normalmente fica no próprio diretório. Podemos usar o middleware SpaStaticFiles para resolver isso, mas na maioria das vezes, podemos simplesmente alterar a configuração do SPA para incorporar no diretório wwwroot. Abaixo segue um exemplo do arquivo angular.json (para a CLI do Vue.js.):

{
  ...
  "projects": {
    "ng-core": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      ...
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "../wwwroot/client",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            ...

Nesse caso, estamos dizendo ao CLI do Vue.js para gerar a compilação em um subdiretório da pasta js do wwwroot, portanto, não precisamos configurar o ASP.NET Core para procurar diretamente no diretório SPA. O Angular e o React suportam esse tipo de diretório de saída, se não quisermos usar o middleware SpaStaticFiles.

Usando um ou mais subdiretórios com nossa própria compilação, podemos ter muitos projetos em SPA (mesmo que sejam estruturas diferentes) em um projeto do ASP.NET Core. Mas existem algumas limitações que envolvem o gerenciamento de implantação e dependência. Ao fazermos isso, cada projeto terá o próprio diretório node_modules, o que significa que os tempos de construção podem ficar longos.

Como estamos configurando o diretório de saída no wwwroot, não há necessidade de especificar a inclusão de um diretório ao implantar o projeto, mas precisaremos informar ao MSBuild (por exemplo, o arquivo csproj) para criar o projeto SPA antes da implantação. O truque aqui é adicionar um destino ao nosso arquivo .csproj:

  <Target Name="client-app"
          BeforeTargets="ComputeFilesToPublish">
    <Exec Command="npm install"
          WorkingDirectory="ClientApp"></Exec>
    <Exec Command="npm run build"
          WorkingDirectory="ClientApp "></Exec>
  </Target>

O Target especifica que algo deve ser considerado antes de calcular os arquivos a serem publicados (ou seja, antes de procurar no wwwroot os arquivos a serem incluídos). As duas linhas 'Exec' são apenas uma maneira de executar as etapas de instalação e criação no diretório ClientApp. Tudo é executado durante a publicação, por isso não afetará o ciclo de desenvolvimento.

A grande desvantagem está mais no Visual Studio do que no ASP.NET Core. Como o project.json está em um subdiretório (e não na raiz do projeto ASP.NET Core), não é possível fazer com que o Gerenciador de Tarefas encontre a compilação. Isso significa que precisaremos executá-la separadamente. Se estivermos usando o Visual Studio Code, Rider ou outra IDE, talvez já estejamos acostumados a abrir as janelas do terminal e fazer a construção na mão, portanto, é provável que isso não seja muito difícil para nós.

A outra limitação está relacionada à anterior. Mais de um package.json para gerenciar. Essa é a que mais dói. A maioria dos projetos do ASP.NET Core já usa o NPM para gerenciar outras dependências (dependências de desenvolvimento e produção). Ter que gerenciar dois ou mais arquivos package.json pode nos deixar um pouco frustrados. É por isso que costumo usar a última das opções, integrar totalmente o SPA ao projeto ASP.NET Core.

Integrar totalmente um SPA ao ASP.NET Core

Se já usamos uma etapa de construção, por que não fazer tudo funcionar junto? Essa é a estratégia de integrar totalmente uma compilação de SPA ao projeto ASP.NET Core. Isso não significa que precisaremos criar um projeto SPA em cada compilação do ASP.NET Core, ainda podemos confiar na criação de observadores e outros recursos para acelerar o desenvolvimento. A construção e a implantação podem ser combinadas com os métodos SPA para fazer com que os testes e a produção funcionem da maneira mais fácil possível.

Como integrar diretamente o SPA

Este método envolve estas etapas:

  • Mesclando a configuração do NPM
  • Movendo a configuração para a raiz do projeto

Vamos analisar cada uma das etapas.

Mesclando a configuração do NPM

Para trazer o SPA para um projeto ASP.NET Core, precisamos mover o package.json para a raiz do projeto ou mesclá-los. Por exemplo, podemos ter um arquivo package.json existente que ajuda na importação de bibliotecas client-side:

{
  "version": "1.0.0",
  "name": "mypackage",
  "private": true,
  "dependencies": {
    "jquery": "3.3.1",
    "bootstrap": "4.3.1"
  }
}

Infelizmente, a maioria dos projetos SPA tem muitas dependências e traz outros elementos da configuração. Então, depois de trazer todo o projeto Angular, o arquivo ficará assim:

{
  "version": "1.0.0",
  "name": "mypackage",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "jquery": "3.3.1",
    "bootstrap": "4.3.1",
    "@angular/animations": "^6.1.0",
    "@angular/common": "^6.1.0",
    "@angular/compiler": "^6.1.0",
    "@angular/core": "^6.1.0",
    "@angular/forms": "^6.1.0",
    "@angular/http": "^6.1.0",
    "@angular/platform-browser": "^6.1.0",
    "@angular/platform-browser-dynamic": "^6.1.0",
    "@angular/router": "^6.1.0",
    "core-js": "^2.5.4",
    "rxjs": "~6.2.0",
    "zone.js": "~0.8.26"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.8.0",
    "@angular/cli": "~6.2.9",
    "@angular/compiler-cli": "^6.1.0",
    "@angular/language-service": "^6.1.0",
    "@types/jasmine": "~2.8.8",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "~8.9.4",
    "codelyzer": "~4.3.0",
    "jasmine-core": "~2.99.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~3.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~1.1.2",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.11.0",
    "typescript": "~2.9.2"
  }
}

Depois de fazermos isso, devemos eliminar manualmente os diretórios node_modules (no projeto principal e na pasta do cliente) e reinstalar todos os pacotes com o comando abaixo:

> npm install

Agora que o NPM está mesclado, é hora de mover a configuração.

Movendo a configuração

Moveremos os arquivos de configuração (não o código-fonte) para a raiz do projeto. Dependendo da estrutura que estivermos utilizando, teremos um conjunto de arquivos de configuração. Por exemplo, com o Angular, seriam angular.json, tsconfig.json e tslint.json.

Como estamos movendo os arquivos do diretório relativo, precisaremos alterar os caminhos no arquivo de configuração para apontar para os novos diretórios. Por exemplo, no angular.json, precisamos alterar qualquer caminho que comece com "src/" para "ClientApp/src/":

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "ng-core": {
      "root": "",
      "sourceRoot": "ClientApp/src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {...},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "wwwroot/client",
            "index": "ClientApp/src/index.html",
            "main": "ClientApp/src/main.ts",
            "polyfills": "ClientApp/src/polyfills.ts",
            "tsConfig": "ClientApp/src/tsconfig.app.json",
            "assets": [
              "ClientApp/src/favicon.ico",
              "ClientApp/src/assets"
            ],
            "styles": [
              "ClientApp/src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "ClientApp/src/environments/environment.ts",
                  "with": "ClientApp/src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true
            }
          }
        },
        "serve": { ... },
      },
    },
  },
  "defaultProject": "ng-core"
}

Neste ponto, já podemos compilar o projeto. A maneira mais fácil de verificar se irá funcionar é executar a compilação com o comando abaixo:

> ng build

Se a compilação for bem-sucedida, nosso SPA deverá funcionar no projeto. Se estivermos no Visual Studio, poderemos usar o "Task Runner Explorer" para iniciar a compilação. Podemos ver os diferentes processos do NPM na Figura 4.

Figura 4

Como o processo de inicialização efetivamente reconstrói qualquer alteração no código, podemos vinculá-lo ao "Project Open" para que o projeto continue a ser construído à medida que codificamos. Pode fazer isso como mostra a Figura 5.

Figura 5

Depois disso, devemos ter tudo o que precisamos para trabalhar no desenvolvimento. Mas se vamos usar o MSBuild para fazer qualquer publicação do projeto, precisaremos fazer outra alteração. Como na integração anterior (Como usar a abordagem de subdiretório do SPA), ainda precisamos especificar que a compilação será executada antes da publicação no .csproj:

  <Target Name="client-app"
          BeforeTargets="ComputeFilesToPublish">
    <Exec Command="npm install"
          WorkingDirectory="ClientApp"></Exec>
    <Exec Command="npm run build"
          WorkingDirectory="ClientApp "></Exec>
  </Target>

Qual escolher?

Muitas decisões, como as mostradas neste artigo, tem mais a ver com a habilidade e adequação do que com a pureza arquitetônica ou com as práticas recomendadas. Se o que estamos fazendo hoje está funcionando, não precisamos fazer a dança das cadeiras. Em geral, prefiro fazer integrações completas do que as demais opções, porque ao adicionar um SPA (ou mais de um) a uma aplicação web, raramente desejo criar um SPA monolítico que substitua uma aplicação corporativa gigante. Quero que a web faça o que sabe fazer bem e adicione o conteúdo à web quando precisarmos de mais controle, melhores experiências e interações do usuário.

Da mesma forma, não uso o ASP.NET Core apenas para criar uma API. Há momentos em que a geração de views no servidor é a coisa certa a fazer, seja para segurança dos dados através da conexão (por exemplo, enviando dados resumidos em vez de enviar dados sensíveis), para melhorar o cache das views do SPA (por exemplo, uma exibição com uma lista em cache dos países incorporados à view) ou mesmo para usar o layout do site fora do SPA.

Essa opinião depende do uso das estruturas do SPA (Angular, React, Vue etc.) como formas de criar ilhas de funcionalidade em um site padrão. Se estiver construindo o "SPA para gerenciar todas as funcionalidades" na organização e decidir que é o caminho certo a se seguir, isso significa mais poder. Apenas, não é a escolha que eu faria em grande parte dos casos, mas logicamente, sou apenas mais um desenvolvedor, e como tal, posso estar errado.

Sobre o autor

Shawn Wildermuth lida com computadores e software desde que conseguiu um Vic-20 no início dos anos 1980. Como MVP da Microsoft desde 2003, também está envolvido com a Microsoft como ASP.NET Insider e ClientDev Insider. Provavelmente fizemos um de seus mais de vinte cursos na Pluralsight. É autor de oito livros e inúmeros artigos sobre desenvolvimento de software. É possível ainda encontrá-lo em conferências locais e internacionais em que palestrou, incluindo TechEd, Oredev, SDC, NDC, VSLive, DevIntersection, MIX, Devteach, DevConnections e Dev Reach. Podemos entrar em contato com ele através do blog wildermuth.com. Ele também está fazendo seu primeiro documentário sobre desenvolvedores de software chamado "Hello World: The Film". Mais sobre o projeto no helloworldfilm.com.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT