Deployment di Angular e ASP .NET Core su Azure

agosto 5, 2017
by Andrea Tosato
  • DeployAngularOnAzure

Creazione di un progetto Angular per una WebApp di Azure

Nell’articolo di oggi vi parlerò di come è possibile distribuire una Single Page Application su Azure all’interno del servizio WebApp.
Per automatizzare il processo di rilascio dell’applicazione, mi avvalgo dello strumento “Visual Studio Team Service”.
Inizio con il definire una semplicissima applicazione Angular 4 che effettua una chiamata HTTP a una applicazione ASP .NET WebApi.
Lo scenario che sto proponendo è tipico di tutte le applicazioni che vengono eseguite nel browser web dell’utente e danno una sensazione di notevole fluidità ma soprattutto una migliore velocità di elaborazione; la Single Page Application sposta il codice dal server al browser dell’utente ciò significa che l’applicazione viene eseguita su ogni singola macchina client.
Avere una Single Page Application significa spostare logiche e implementazioni all’interno di un contesto insicuro e non controllato come il browser di un PC o uno smartphone ai quali il sistema non ha accesso.

L’infrastruttura di queste web application è divisa nettamente in due macro aree: il front-end, in questo esempio Angular, e il back-end implementato in ASP .NET Core WebApi.
Sviluppare una applicazione web di questo tipo ci porta di conseguenza a duplicare le logiche di validazione di integrità e di sicurezza dei dati.
Le applicazioni dell’esempio risultano banali poiché il focus dell’articolo di oggi verte sugli strumenti di rilascio che abbiamo a disposizione in Visual Studio Team Service.

Progetto ASP. NET WebApi

Il progetto ASP .NET Core è composto dalla cartella dei controller, dalla cartella dei file statici wwwroot, e dai file Startup.cs e Program.cs. Il controller Home restituisce il nome dell’applicazione e l’orario presente sul server Azure in cui risiederà l’applicazione.

Struttura del progettoStatic Files

Il file di startup è molto simile a quello standard di un progetto ASP .NET Core WebApi, tuttavia dobbiamo aggiungere due pacchetti Nuget che servono per abilitare la funzionalità di Static File e di Cors. Il pacchetto lo si può trovare con il nome di: Microsoft.AspNetCore.StaticFiles

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {           
        // Abilito il CORS per il WebServer in memory di Angular-CLI
        if (env.IsDevelopment())
        {
            app.UseCors(builder => builder.WithOrigins("http://localhost:4200"));
        }
        app.UseDefaultFiles();
        app.UseStaticFiles();
        app.UseMvc();
    }
}

Con i nuovi progetti ASP .NET Core è necessario configurare l’applicazione in modo tale che essa possa servire i file statici, di conseguenza, alla richiesta di un file presente nella cartella wwwroot o nelle sottocartelle il server web dovrà restituire il file richiesto.
I file statici sono tutti quelli che non devono essere elaborati dal server ma solamente restituiti al client. Ad esempio i file .html, .css, .img, .zip etc.
L’abilitazione della funzionalità ci consentirà di far scaricare l’applicazione angular sul client dell’utente, facendogli scaricare la Single Page Application formata da i file index.html e javascript.

CORS

Vado inoltre a configurare il Cors (Cross-Origin Resource Sharing) solamente se l’applicazione viene eseguita in modalità di sviluppo. Definisco una sola regola che mi consente di accettare tutte le richieste che arrivano da un indirizzo diverso a quello del server web di ASP .NET Core. Il pacchetto lo si può trovare su Nuget con il nome di: Microsoft.AspNetCore.Cors
Accetto tutto le richieste che arrivano dall’url http://localhost:4200 poichè su questo particolare indirizzo verrà ospitato il server web in-memory fornito da angular-cli

 if (env.IsDevelopment())
 {
   app.UseCors(builder => builder.WithOrigins("http://localhost:4200"));
 }
 app.UseDefaultFiles();
 app.UseStaticFiles();

Durante lo sviluppo di Angular, preferisco mantenere suddiviso la parte Front-end da quella Back-end.
Il tool angular-cli è di notevole aiuto, sia nella fase di sviluppo che nella fase di rilascio dell’applicazione. Per ulteriori informazioni su angular-cli

Progetto Angular 4

Tramite l’istruzione ng g new app-esempio ho creato una nuova applicazione angular dal nome app-esempio.
L’applicazione, per praticità, viene creata all’interno della root del progetto ASP .NET Core così si potrà conservare il sorgente dell’applicazione in un unico progetto.
Nell’immagine di sinistra possiamo vedere il risultato di quanto appena descritto, l’applicazione ASP .NET Core WebApi con all’interno la folder del progetto di Angular. Nell’immagine di destra è riportata la schermata di creazione del progetto Angular con il dettaglio dei file creati all’avvio del progetto.

Folder Angular

Creazione progetto angular cli

Il progetto ASP .NET Core deve essere correttamente istruito per ignorare la compilazione dei file Typescript presenti all’interno delle folder del progetto WebApi.
I file Typescript scritti nel progetto Angular inducono il nostro IDE di sviluppo, Visual Studio 2017, a compilare tutto ciò che trova all’interno della cartella in cui sono presenti i sorgenti.
Per evitare compilazioni non desiderate è necessario editare il file .CSPROJ del progetto e aggiungere l’istruzione:

<TypeScriptCompileBlocked>True</TypeScriptCompileBlocked>

L’istruzione deve essere inserita all’interno del un tag PropertyGroup. Come da figura.

TypeScriptCompiledBlocked
Il codice Angular

Il codice angular è molto banale ma vorrei porre l’attenzione su alcuni aspetti che ci potrebbero aiutare nella fase di rilascio. Nell’esempio sotto riportato, per rendere l’esempio più comprensibile, ho inserito nell’inizializzazione del component una chiamata HTTP. Le chiamate HTTP dovrebbero essere sempre inserite in service appositi e richiamate nel component.
Nel costruttore del component vado a verificare se esiste la variabile d’ambiente baseUrl e ne recupero il valore. L’utilizzo di baseUrl avviene solamente durante lo sviluppo poichè le WebApi risiedono su una origine diversa rispetto a quella del server web Angular. In altre parole, Angular viene eseguito in http://localhost:4200 mentre le WebApi saranno ospitate su IIS Express in una porta diversa.

L’esecuzione del codice Angular all’interno delle WebApp avverrà nello stesso server web, per cui Angular e WebApi saranno ospitate all’interno dello stesso dominio.
Per dedurre il baseURL in un dominio condiviso sia dalla Single Page Application che da ASP .NET Core, utilizzo l’oggetto Location presente in ‘@angular/common’.
L’url per richiamare gli EndPoint delle WebApi saranno ricavate dall’url in cui Angular è ospitato in aggiunta al path del controller specifico: this.location.prepareExternalUrl(‘api/home/’).

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common';
import { environment } from "environments/environment";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [Location, {provide: LocationStrategy, useClass: PathLocationStrategy}]
})
export class AppComponent implements OnInit {
  public Dati: DatiBackEnd = new DatiBackEnd();
  private homeURL: string;
  constructor(private http: Http, private location: Location) {
    if(environment.baseUrl == undefined){
      this.homeURL = this.location.prepareExternalUrl('api/home/');
    } else {
      this.homeURL = environment.baseUrl + 'api/home/';
    }
  }

  ngOnInit(): void {
    this.http.get(this.homeURL)
    .subscribe(
      res => this.Dati = res.json(),
      err => console.log(err)
    );
  }

}

export class DatiBackEnd {
  applicazione: string;
  data: Date;
}

I file di environment presenti nel progetto saranno configurati diversamente a seconda che si tratti dell’ambiente di production o di sviluppo. Nel secondo caso ci troveremo la variabile baseUrl impostata con l’url dell’applicazione WebApi ospitata in IIS Express

export const environment = {
  production: false,
  baseUrl: 'http://localhost:5000/'
};

Il codice HTML espone nel markup una stringa statica {{Dati.applicazione}} e la data corrente {{Dati.data | date: ‘medium’ }} formattata dalla pipe “| date: ‘medium’ ” che ci restituisce una stringa leggibile del tipo Jul 21, 2017, 7:48:41 AM.

<div style="text-align:center">
  <h1>
    Welcome to : {{Dati.applicazione}}!!
  </h1>
</div>
<h2 style="text-align: center">{{Dati.data | date: 'medium' }}</h2>

Il Package.json

Il tool di sviluppo Angular-cli, si basa sul package manager di NPM e ci consente di importare le referenze a librerie esterne quali Angular.
Il file package.json contiene le librerie necessarie all’esecuzione dell’applicazione dependencies e le librerie utilizzate per lo sviluppo applicativo devDependencies.
La sezione scripts è al più interessante e ci tornerà utile nella seconda parte dell’articolo. In questa sezione sono definite tutte le operazioni che si possono richiamare tramite l’istruzione “npm run nome-comando”, in pratica posso introdurre degli alias a comandi più complessi che saranno eseguiti da npm stesso.
Richiamando l’istruzione npm run vsts, introdotta per agevolare la compilazione attraverso Visual Studio Team Service, andrò a lanciare nella shell del sistema operativo l’istruzione ng build –prod –aot=false.
L’istruzione ng build, andrà a richiamare angular-cli e compilerà l’applicazione Angular. L’ opzioni –prod consente di pubblicate l’applicazione per la produzione perciò caricherà il file di environment.prod anzichè quello di sviluppo. L’opzione –aot=false disabilita la compilazione preventiva del codice; ho preferito aggiungere questa opzione perchè angular non ha ancora perfezionato questa funzionalità e molte volte non riesce a compilare l’applicazione con successo.

{
  "name": "app-esempio",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "vsts": "ng build --prod --aot=false"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^4.0.0",
    "@angular/common": "^4.0.0",
    "@angular/compiler": "^4.0.0",
    "@angular/core": "^4.0.0",
    "@angular/forms": "^4.0.0",
    "@angular/http": "^4.0.0",
    "@angular/platform-browser": "^4.0.0",
    "@angular/platform-browser-dynamic": "^4.0.0",
    "@angular/router": "^4.0.0",
    "core-js": "^2.4.1",
    "rxjs": "^5.1.0",
    "zone.js": "^0.8.4"
  },
  "devDependencies": {
    "@angular/cli": "1.2.0",
    "@angular/compiler-cli": "^4.0.0",
    "@angular/language-service": "^4.0.0",
    "@types/jasmine": "~2.5.53",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "~3.0.1",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.0.4",
    "tslint": "~5.3.2",
    "typescript": "~2.3.3"
  }
}

In questa prima parte dell’articolo, abbiamo visto come è possibile scrivere una applicazione ASP .NET Core WebApi che ci permetta di eseguire Angular al suo interno; inoltre abbiamo appreso come è possibile sviluppare la Single Page Application eseguendo Angular su in-memory server fornito da angular-cli.
I passaggi successivi saranno: configurare Visual Studio Team Service per il deploy su Azure; nell’ultima parte rilasceremo le nostre due applicazioni nel servizio Azure WebApp.

About

Senior Software Developer . NET