Struttura del progetto per le app di Blazor

Suggerimento

Questo contenuto è un estratto dell'eBook, Blazor per gli sviluppatori di Web Forms ASP.NET per Azure, disponibile in .NET Docs o come PDF scaricabile gratuitamente che può essere letto offline.

Blazor-for-ASP-NET-Web-Forms-Developers eBook cover thumbnail.

Nonostante le significative differenze di struttura del progetto, Web Forms ASP.NET e Blazor condividono molti concetti simili. In questo caso verrà esaminata la struttura di un progetto Blazor per confrontarla con un progetto Web Forms ASP.NET.

Per creare la prima app Blazor, seguire le istruzioni riportate nei Blazor passaggi introduttivi. È possibile seguire le istruzioni per creare un'Blazor app server o un'BlazorWebAssemblyapp ospitata in ASP.NET Core. Ad eccezione della logica specifica del modello di hosting, la maggior parte del codice di entrambi i progetti è la stessa.

File di progetto

Blazor Le app Server sono progetti .NET. Il file di progetto per l'app Server Blazor è il più semplice possibile:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

</Project>

Il file di progetto di un'app BlazorWebAssembly è leggermente più complesso (i numeri di versioni esatti possono variare):

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
  </ItemGroup>

</Project>

La destinazione del progetto BlazorWebAssembly è Microsoft.NET.Sdk.BlazorWebAssembly anziché Microsoft.NET.Sdk.Web SDK perché viene eseguito nel browser in un runtime .NET basato su WebAssembly. Non è possibile installare .NET in un Web browser come su un server o su una macchina per sviluppatori. Di conseguenza, il progetto fa riferimento al framework Blazor usando riferimenti a singoli pacchetti.

In confronto, un progetto Web Forms ASP.NET predefinito include quasi 300 righe di XML nel relativo file .csproj, la maggior parte delle quali elenca esplicitamente i vari file di codice e di contenuto del progetto. Dal rilascio di .NET 5, sia il server Blazor che le app BlazorWebAssembly possono condividere facilmente un runtime unificato.

Anche se sono supportati, i riferimenti a singoli assembly sono meno comuni nei progetti .NET. La maggior parte delle dipendenze del progetto vengono gestite come riferimenti al pacchetto NuGet. Nei progetti .NET è sufficiente fare riferimento alle dipendenze del pacchetto di primo livello. Le dipendenze transitive vengono incluse automaticamente. Invece di usare il file packages.config comunemente presente nei progetti Web Forms ASP.NET per fare riferimento ai pacchetti, i riferimenti ai pacchetti vengono aggiunti al file di progetto usando l'elemento <PackageReference>.

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>

Punto di accesso

Il punto di ingresso dell'app Server Blazor viene definito nel file Program.cs, come in un'applicazione Console. Quando l'app viene eseguita, crea ed esegue un'istanza dell'host Web usando le impostazioni predefinite specifiche per le app Web. L'host Web gestisce il ciclo di vita dell'app Server Blazor e imposta i servizi a livello di host. Esempi di tali servizi sono la configurazione, la registrazione, l'inserimento delle dipendenze e il server HTTP. Questo codice è per lo più boilerplate e spesso viene lasciato invariato.

using BlazorApp3.Areas.Identity;
using BlazorApp3.Data;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
builder.Services.AddSingleton<WeatherForecastService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

Le app BlazorWebAssembly definiscono anche un punto di ingresso in Program.cs. Il codice è leggermente diverso. Il codice è simile, in quanto configura l'host dell'app per fornire gli stessi servizi a livello di host all'app. L'host dell'app WebAssembly non configura tuttavia un server HTTP perché viene eseguito direttamente nel browser.

Le app Blazor non usano un file Global.asax per definire la logica di avvio dell'app. Questa logica è invece contenuta in Program.cs o in una classe Startup correlata a cui viene fatto riferimento da Program.cs. In ogni caso, il codice viene usato per configurare l'app e tutti i servizi specifici dell'app.

In un'app Server Blazor, il file Program.cs visualizzato viene usato per configurare l'endpoint per la connessione in tempo reale usata da Blazor tra i browser client e il server.

In un'app BlazorWebAssembly il file Program.cs definisce i componenti radice dell'app e dove deve essere eseguito il rendering:

using BlazorApp1;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();

File statici

A differenza dei progetti Web Forms ASP.NET, non tutti i file in un progetto Blazor possono essere richiesti come file statici. Solo i file nella cartella wwwroot sono indirizzabili sul Web. Questa cartella viene definita "radice Web" dell'app. Qualsiasi elemento all'esterno del radice Web dell'app non è indirizzabile sul Web. Questa configurazione offre un livello di sicurezza aggiuntivo che previene l'esposizione accidentale dei file di progetto sul Web.

Impostazione

La configurazione nelle app Web Forms ASP.NET viene in genere gestita usando uno o più file web.config. Le app Blazor in genere non hanno file web.config. In caso affermativo, il file viene usato solo per configurare le impostazioni specifiche di IIS quando è ospitato in IIS. Le app Blazor Server usano invece le astrazioni di configurazione di ASP.NET Core. (Le applicazioni BlazorWebAssembly attualmente non supportano le stesse astrazioni di configurazione, ma questa funzionalità potrebbe essere aggiunta in futuro). Ad esempio, l'app Server Blazor predefinita archivia alcune impostazioni in appsettings.json.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Altre informazioni sulla configurazione sono disponibili nei progetti ASP.NET Core, nella sezione Configurazione.

Componenti Razor

La maggior parte dei file dei progetti Blazor sono file .razor. Razor è un linguaggio di creazione di modelli basato su HTML e C#, usato per generare dinamicamente l'interfaccia utente Web. I file .razor definiscono i componenti che costituiscono l'interfaccia utente dell'app. Nella maggior parte dei casi, i componenti sono identici per le app Server Blazor e BlazorWebAssembly. I componenti in Blazor sono analoghi ai controlli utente in Web Forms ASP.NET.

Ogni file di componente Razor viene compilato in una classe .NET al momento della creazione del progetto. La classe generata cattura lo stato del componente, la logica di rendering, i metodi del ciclo di vita, i gestori di eventi e altre logiche. Altre informazioni sulla creazione di componenti sono disponibili nella sezione Creare componenti riutilizzabili dell'interfaccia utente con Blazor.

I file _Imports.razor non sono file del componente Razor. Definiscono invece un set di direttive Razor da importare in altri file .razor all'interno della stessa cartella e delle relative sottocartelle. Ad esempio, un file _Imports.razor è un modo convenzionale per aggiungere direttive using per gli spazi dei nomi comunemente usati:

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using BlazorApp1
@using BlazorApp1.Shared

Pagine

Dove si trovano le pagine nelle app Blazor? Blazor non definisce un'estensione di file separata per le pagine indirizzabili, ad esempio i file .aspx delle app Web Forms ASP.NET. Le pagine vengono invece definite assegnando route ai componenti. Una route viene in genere assegnata usando la direttiva Razor @page. Ad esempio, il componente Counter creato nel file Pages/Counter.razor definisce la route seguente:

@page "/counter"

Il routing in Blazor viene gestito sul lato client, non sul server. Quando l'utente si sposta nel browser, Blazor intercetta la navigazione e quindi esegue il rendering del componente con la route corrispondente.

Le route dei componenti non vengono attualmente dedotte dal percorso del file del componente, come avviene per le pagine .aspx o per le Razor Pages di ASP.NET Core. Questa funzionalità potrebbe essere aggiunta in futuro. Ogni route deve essere specificata in modo esplicito nel componente. L'archiviazione di componenti instradabili in una cartella Pagine non ha un significato speciale ed è una pura convenzione.

Verranno fornite altre informazioni sul routing in Blazor nella sezione Pagine, routing e layout.

Layout

Nelle app Web Forms ASP.NET un layout di pagina comune viene gestito usando le pagine master (Site.Master). Nelle app Blazor il layout di pagina viene gestito usando i componenti di layout (Shared/MainLayout.razor). I componenti di layout vengono illustrati in modo più dettagliato nella sezione Pagina, routing, e layout.

Bootstrap Blazor

Per eseguire il bootstrap Blazor, l'app deve:

  • Specificare la posizione nella pagina in cui eseguire il rendering del componente radice (App.Razor).
  • Aggiungere il corrispondente script del framework Blazor.

Nell'app Server Blazor la pagina host del componente radice viene definita nel file _Host.cshtml. Questo file definisce una pagina di Razor, non un componente. Razor Pages usa la sintassi Razor per definire una pagina indirizzabile dal server, molto simile a una pagina .aspx.

@page "/"
@namespace BlazorApp3.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = "_Layout";
}

<component type="typeof(App)" render-mode="ServerPrerendered" />

L'attributo render-mode viene usato per definire la posizione in cui deve essere eseguito il rendering di un componente a livello radice. L'opzione RenderMode indica il modo in cui deve essere eseguito il rendering del componente. Nella tabella seguente vengono illustrate le opzioni RenderMode supportate.

Opzione Descrizione
RenderMode.Server Rendering interattivo dopo aver stabilito una connessione con il browser
RenderMode.ServerPrerendered Prima il pre rendering e poi il rendering in modo interattivo
RenderMode.Static Rendering come contenuto statico

Il file _Layout.cshtml include il codice HTML predefinito per l'app e i relativi componenti.

@using Microsoft.AspNetCore.Components.Web
@namespace BlazorApp3.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
    <link href="BlazorApp3.styles.css" rel="stylesheet" />
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
    @RenderBody()

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

Il riferimento allo script a _framework/blazor.server.js stabilisce la connessione in tempo reale con il server e si occupa di tutte le interazioni con utente e degli aggiornamenti dell'interfaccia utente.

Nell'app BlazorWebAssembly la pagina host è un semplice file HTML statico in wwwroot/index.html. L'elemento <div> con ID denominato app viene usato per indicare dove deve essere eseguito il rendering del componente radice.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>BlazorApp1</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link href="BlazorApp1.styles.css" rel="stylesheet" />
</head>

<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

</html>

Il componente radice di cui eseguire il rendering viene specificato nel file di Program.cs dell'app con la flessibilità necessaria per registrare i servizi tramite l'inserimento delle dipendenze. Per altre informazioni, vedere ASP.NET Core Blazor dependency injection.

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

Output della compilazione

Quando viene compilato un progetto Blazor, tutti i file di codice e del componente Razor vengono compilati in un singolo assembly. A differenza di Web Forms ASP.NET, Blazor non supporta la compilazione in fase di esecuzione della logica dell'interfaccia utente.

Eseguire l'app con Ricaricamento rapido

Per eseguire l'app server Blazor, premere F5 in Visual Studio per l'esecuzione con il debugger collegato oppure CTRL + F5 per l'esecuzione senza il debugger collegato.

Per eseguire l'app BlazorWebAssembly, scegliere uno degli approcci seguenti:

  • Eseguire il progetto client direttamente usando il server di sviluppo.
  • Eseguire il progetto server quando si ospita l'app con ASP.NET Core.

È possibile eseguire il debug delle app BlazorWebAssembly sia nel browser che in Visual Studio. Per informazioni dettagliate, vedere Debug ASP.NET Core BlazorWebAssembly.

Sia Blazor Server che le app BlazorWebAssembly supportano il Ricaricamento rapido in Visual Studio. Ricaricamento rapido è una funzionalità che aggiorna automaticamente le modifiche apportate a un'app Blazor live, nel browser. È possibile attivare o disattivare l'abilitazione del Ricaricamento rapido dalla relativa icona nella barra degli strumenti:

Visual Studio 2022: Hot Reload menu item and icon.

Selezionando il cursore accanto all'icona vengono visualizzate opzioni aggiuntive. È possibile attivare o disattivare Ricaricamento rapido, riavviare l'applicazione e attivare o disattivare l'attivazione del Ricaricamento rapido ogni volta che viene salvato un file.

Visual Studio 2022: Hot Reload menu item with expanded options.

È anche possibile accedere ad altre opzioni di configurazione. La finestra di dialogo di configurazione consente di specificare se Ricaricamento rapido deve essere abilitato durante il debug (insieme a Modifica e Continua), all'avvio senza eseguire il debug o quando viene salvato un file.

Visual Studio 2022: Hot Reload configuration options from the

Il "ciclo interno dello sviluppatore" è stato notevolmente semplificato con Ricaricamento rapido. Senza Ricaricamento rapido, uno sviluppatore Blazor deve in genere riavviare ed eseguire di nuovo l'app dopo ogni modifica, passando alla parte appropriata dell'app come richiesto. Con Ricaricamento rapido, è possibile apportare modifiche all'app in esecuzione senza doverla riavviare nella maggior parte dei casi. Ricaricamento rapido mantiene anche lo stato delle pagine, quindi non è necessario immettere nuovamente i valori del modulo o riportare l'app dove serve.