Avvio dell'app in ASP.NET Core

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Di Rick Anderson

Le app ASP.NET Core create con i modelli Web contengono il codice di avvio dell'applicazione nel file Program.cs.

Per Blazor indicazioni sull'avvio, che aggiunge o sostituisce le linee guida in questo articolo, vedere ASP.NET avvio di CoreBlazor.

Il codice di avvio dell'app seguente supporta:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Le app che usano EventSource possono misurare il tempo di avvio per comprendere e ottimizzare le prestazioni di avvio. L'evento ServerReady in Microsoft.AspNetCore.Hosting rappresenta il punto in cui il server è pronto per rispondere alle richieste.

Per altre informazioni sull'avvio dell'applicazione, vedere ASP.NET Panoramica dei concetti fondamentali di Base.

Estendere l'avvio con filtri di avvio

Usare IStartupFilter:

  • Per configurare il middleware all'inizio o alla fine della pipeline middleware di un'app senza una chiamata esplicita a Use{Middleware}. Usare IStartupFilter per aggiungere valori predefiniti all'inizio della pipeline senza registrare in modo esplicito il middleware predefinito. IStartupFilter consente a un componente diverso di chiamare Use{Middleware} per conto dell'autore dell'app.
  • Per creare una pipeline di Configure metodi. IStartupFilter.Configure può impostare un middleware da eseguire prima o dopo l'aggiunta del middleware dalle librerie.

IStartupFilter implementa Configure, che riceve e restituisce un Action<IApplicationBuilder>. IApplicationBuilder definisce una classe per configurare la pipeline delle richieste di un'app. Per altre informazioni, vedere Creare una pipeline middleware con IApplicationBuilder.

Ogni IStartupFilter può aggiungere uno o più middleware nella pipeline delle richieste. I filtri vengono richiamati nell'ordine in cui sono stati aggiunti al contenitore di servizi. Poiché i filtri possono aggiungere il middleware prima o dopo il passaggio del controllo al filtro successivo, i filtri vengono aggiunti all'inizio o alla fine della pipeline dell'app.

L'esempio seguente dimostra come registrare un middleware con IStartupFilter. Il middleware RequestSetOptionsMiddleware imposta un valore di opzioni da un parametro di stringa di query:

public class RequestSetOptionsMiddleware
{
    private readonly RequestDelegate _next;

    public RequestSetOptionsMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // Test with https://localhost:5001/Privacy/?option=Hello
    public async Task Invoke(HttpContext httpContext)
    {
        var option = httpContext.Request.Query["option"];

        if (!string.IsNullOrWhiteSpace(option))
        {
            httpContext.Items["option"] = WebUtility.HtmlEncode(option);
        }

        await _next(httpContext);
    }
}

RequestSetOptionsMiddleware è configurato nella classe RequestSetOptionsStartupFilter:

namespace WebStartup.Middleware;
// <snippet1>
public class RequestSetOptionsStartupFilter : IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return builder =>
        {
            builder.UseMiddleware<RequestSetOptionsMiddleware>();
            next(builder);
        };
    }
}
// </snippet1>

l'oggetto IStartupFilter è registrato in Program.cs:

using WebStartup.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddTransient<IStartupFilter,
                      RequestSetOptionsStartupFilter>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Quando viene specificato un parametro della stringa di query per option , il middleware elabora l'assegnazione del valore prima che il middleware ASP.NET Core esegua il rendering della risposta:

@page
@model PrivacyModel
@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<p> Append query string ?option=hello</p>
Option String: @HttpContext.Items["option"];

L'ordine di esecuzione del middleware viene impostato in base all'ordine delle registrazioni IStartupFilter:

  • Più implementazioni IStartupFilter possono interagire con gli stessi oggetti. Se l'ordinamento è un fattore importante, ordinare le registrazioni di servizio IStartupFilter in un ordine corrispondente a quello di esecuzione dei relativi middleware.

  • Le librerie possono aggiungere middleware con una o più implementazioni IStartupFilter che viene eseguito prima o dopo altri middleware dell'app registrati con IStartupFilter. Per richiamare un middleware IStartupFilter prima che un middleware venga aggiunto da IStartupFilter di una libreria:

    • Posizionare la registrazione del servizio prima che la libreria venga aggiunta al contenitore di servizi.
    • Per richiamarlo in un secondo momento, posizionare la registrazione dei servizi dopo l'aggiunta della libreria.

Nota: non è possibile estendere l'app ASP.NET Core quando si esegue l'override Configuredi . Per altre informazioni, vedere questo problema di GitHub.

Aggiungere elementi di configurazione all'avvio da un assembly esterno

Un'implementazione IHostingStartup consente di aggiungere miglioramenti a un'app all'avvio da un assembly esterno all'esterno del file dell'app Program.cs . Per altre informazioni, vedere Usare assembly di avvio dell'hosting in ASP.NET Core.

Avvio, configurazioneservizi e configurazione

Per informazioni sull'uso dei ConfigureServices metodi e Configure con il modello di hosting minimo, vedere:

Le app ASP.NET Core create con i modelli Web contengono il codice di avvio dell'applicazione nel file Program.cs.

Il codice di avvio dell'app seguente supporta:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Per altre informazioni sull'avvio dell'applicazione, vedere ASP.NET Panoramica dei concetti fondamentali di Base.

Estendere l'avvio con filtri di avvio

Usare IStartupFilter:

  • Per configurare il middleware all'inizio o alla fine della pipeline middleware di un'app senza una chiamata esplicita a Use{Middleware}. Usare IStartupFilter per aggiungere valori predefiniti all'inizio della pipeline senza registrare in modo esplicito il middleware predefinito. IStartupFilter consente a un componente diverso di chiamare Use{Middleware} per conto dell'autore dell'app.
  • Per creare una pipeline di Configure metodi. IStartupFilter.Configure può impostare un middleware da eseguire prima o dopo l'aggiunta del middleware dalle librerie.

IStartupFilter implementa Configure, che riceve e restituisce un Action<IApplicationBuilder>. IApplicationBuilder definisce una classe per configurare la pipeline delle richieste di un'app. Per altre informazioni, vedere Creare una pipeline middleware con IApplicationBuilder.

Ogni IStartupFilter può aggiungere uno o più middleware nella pipeline delle richieste. I filtri vengono richiamati nell'ordine in cui sono stati aggiunti al contenitore di servizi. Poiché i filtri possono aggiungere il middleware prima o dopo il passaggio del controllo al filtro successivo, i filtri vengono aggiunti all'inizio o alla fine della pipeline dell'app.

L'esempio seguente dimostra come registrare un middleware con IStartupFilter. Il middleware RequestSetOptionsMiddleware imposta un valore di opzioni da un parametro di stringa di query:

public class RequestSetOptionsMiddleware
{
    private readonly RequestDelegate _next;

    public RequestSetOptionsMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // Test with https://localhost:5001/Privacy/?option=Hello
    public async Task Invoke(HttpContext httpContext)
    {
        var option = httpContext.Request.Query["option"];

        if (!string.IsNullOrWhiteSpace(option))
        {
            httpContext.Items["option"] = WebUtility.HtmlEncode(option);
        }

        await _next(httpContext);
    }
}

RequestSetOptionsMiddleware è configurato nella classe RequestSetOptionsStartupFilter:

namespace WebStartup.Middleware;
// <snippet1>
public class RequestSetOptionsStartupFilter : IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return builder =>
        {
            builder.UseMiddleware<RequestSetOptionsMiddleware>();
            next(builder);
        };
    }
}
// </snippet1>

l'oggetto IStartupFilter è registrato in Program.cs:

using WebStartup.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddTransient<IStartupFilter,
                      RequestSetOptionsStartupFilter>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Quando viene specificato un parametro della stringa di query per option , il middleware elabora l'assegnazione del valore prima che il middleware ASP.NET Core esegua il rendering della risposta:

@page
@model PrivacyModel
@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<p> Append query string ?option=hello</p>
Option String: @HttpContext.Items["option"];

L'ordine di esecuzione del middleware viene impostato in base all'ordine delle registrazioni IStartupFilter:

  • Più implementazioni IStartupFilter possono interagire con gli stessi oggetti. Se l'ordinamento è un fattore importante, ordinare le registrazioni di servizio IStartupFilter in un ordine corrispondente a quello di esecuzione dei relativi middleware.

  • Le librerie possono aggiungere middleware con una o più implementazioni IStartupFilter che viene eseguito prima o dopo altri middleware dell'app registrati con IStartupFilter. Per richiamare un middleware IStartupFilter prima che un middleware venga aggiunto da IStartupFilter di una libreria:

    • Posizionare la registrazione del servizio prima che la libreria venga aggiunta al contenitore di servizi.
    • Per richiamarlo in un secondo momento, posizionare la registrazione dei servizi dopo l'aggiunta della libreria.

Nota: non è possibile estendere l'app ASP.NET Core quando si esegue l'override Configuredi . Per altre informazioni, vedere questo problema in GitHub.

Aggiungere elementi di configurazione all'avvio da un assembly esterno

Un'implementazione IHostingStartup consente di aggiungere miglioramenti a un'app all'avvio da un assembly esterno all'esterno del file dell'app Program.cs . Per altre informazioni, vedere Usare assembly di avvio dell'hosting in ASP.NET Core.

La classe Startup configura i servizi e la pipeline delle richieste dell'app.

Classe Startup

Le app ASP.NET Core usano una classe Startup denominata Startup per convenzione. La classe Startup:

  • Include facoltativamente un metodo ConfigureServices per configurare i servizi dell'app. Un servizio è un componente riutilizzabile che fornisce la funzionalità delle app. I servizi vengono registrati in e usati nell'app ConfigureServices tramite l'inserimento delle dipendenze (DI) o ApplicationServices.
  • Include un metodo Configure per creare la pipeline di elaborazione delle richieste dell'app.

ConfigureServices e Configure sono chiamate dal runtime di ASP.NET Core all'avvio dell'app:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

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

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

L'esempio precedente riguarda Razor Pages. La versione MVC è simile.

La classe Startup viene specificata al momento della compilazione dell'host dell'app. La classe Startup viene in genere specificata chiamando il metodo WebHostBuilderExtensions.UseStartup/<TStartup> nel generatore host:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

L'host fornisce i servizi disponibili al costruttore della classe Startup. L'app aggiunge servizi aggiuntivi tramite ConfigureServices. I servizi dell'host e dell'app saranno disponibili in Configure e nell'app.

Solo i tipi di servizio seguenti possono essere inseriti nel Startup costruttore quando si usa l'host generico (IHostBuilder):

public class Startup
{
    private readonly IWebHostEnvironment _env;

    public Startup(IConfiguration configuration, IWebHostEnvironment env)
    {
        Configuration = configuration;
        _env = env;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        if (_env.IsDevelopment())
        {
        }
        else
        {
        }
    }
}

La maggior parte dei servizi non è disponibile fino a quando non viene chiamato il metodo Configure.

Avvio multiplo

Quando l'app definisce classi Startup separate per i diversi ambienti (ad esempio StartupDevelopment), la classe Startup appropriata viene selezionata durante il runtime. La classe il cui suffisso di nome corrisponde all'ambiente corrente ha la priorità. Se l'app viene eseguita nell'ambiente di sviluppo e include sia una classe Startup che una classe StartupDevelopment, viene usata la classe StartupDevelopment. Per altre informazioni, vedere Usare più ambienti.

Per altre informazioni sull'host, vedere L'host. Per informazioni sulla gestione degli errori durante l'avvio, vedere Gestione delle eccezioni durante l'avvio.

Metodo ConfigureServices

Il metodo ConfigureServices è:

  • Facoltativo.
  • Chiamato dall'host prima del metodo Configure per configurare i servizi dell'app.
  • Dove le opzioni di configurazione sono impostate per convenzione.

L'host può configurare alcuni servizi prima che vengano chiamati i metodi Startup. Per altre informazioni, vedere L'host.

Per le funzionalità che richiedono l'installazione sostanziale, sono disponibili i metodi di estensione Add{Service} in IServiceCollection. Ad esempio, AddDbContext, AddDefaultIdentity, AddEntityFrameworkStores e AddRazorPages:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDefaultIdentity<IdentityUser>(
            options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddRazorPages();
    }

L'aggiunta dei servizi al contenitore dei servizi li rende disponibili all'interno dell'app e nel metodo Configure. I servizi vengono risolti tramite inserimento delle dipendenze o da ApplicationServices.

Metodo Configure

Il metodo Configure viene usato per specificare come risponde l'app alle richieste HTTP. La pipeline delle richieste viene configurata aggiungendo i componenti middleware a un'istanza IApplicationBuilder. IApplicationBuilder è disponibile per il metodo Configure ma non viene registrato nel contenitore dei servizi. L'hosting crea IApplicationBuilder e lo passa direttamente a Configure.

I modelli ASP.NET Core configurano la pipeline con il supporto per:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

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

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

L'esempio precedente riguarda Razor Pages. La versione MVC è simile.

Ogni metodo di estensione Use aggiunge uno o più componenti middleware alla pipeline delle richieste. Ad esempio, UseStaticFiles configura il middleware per fornire i file statici.

Ogni componente del middleware è responsabile della chiamata del componente seguente nella pipeline o del corto circuito della catena, se necessario.

I servizi aggiuntivi, ad esempio IWebHostEnvironment, ILoggerFactory o qualsiasi elemento definito in ConfigureServices, possono essere specificati nella firma del metodo Configure. Questi servizi vengono inseriti se sono disponibili.

Per altre informazioni su come usare IApplicationBuilder e sull'ordine di elaborazione del middleware, vedere ASP.NET Middleware core.

Configurare i servizi senza Startup

Per configurare i servizi e la pipeline di elaborazione delle richieste senza usare una classe Startup, chiamare i metodi pratici ConfigureServices e Configure sul generatore di host. Se vengono effettuate più chiamate a ConfigureServices, le chiamate vengono aggiunte l'una all'altra. In presenza di più chiamate del metodo Configure viene usata l'ultima chiamata di Configure.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.ConfigureServices(services =>
                {
                    services.AddControllersWithViews();
                })
                .Configure(app =>
                {
                    var loggerFactory = app.ApplicationServices
                        .GetRequiredService<ILoggerFactory>();
                    var logger = loggerFactory.CreateLogger<Program>();
                    var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
                    var config = app.ApplicationServices.GetRequiredService<IConfiguration>();

                    logger.LogInformation("Logged in Configure");

                    if (env.IsDevelopment())
                    {
                        app.UseDeveloperExceptionPage();
                    }
                    else
                    {
                        app.UseExceptionHandler("/Home/Error");
                        app.UseHsts();
                    }

                    var configValue = config["MyConfigKey"];
                });
            });
        });
}

Estendere l'avvio con filtri di avvio

Usare IStartupFilter:

  • Per configurare il middleware all'inizio o alla fine della pipeline configure middleware di un'app senza una chiamata esplicita a Use{Middleware}. IStartupFilter viene usato da ASP.NET Core per aggiungere impostazioni predefinite all'inizio della pipeline senza dover rendere l'autore dell'app registrare in modo esplicito il middleware predefinito. IStartupFilter consente a un componente diverso di chiamare Use{Middleware} per conto dell'autore dell'app.
  • Per creare una pipeline di Configure metodi. IStartupFilter.Configure può impostare un middleware da eseguire prima o dopo l'aggiunta del middleware dalle librerie.

IStartupFilter implementa Configure, che riceve e restituisce un Action<IApplicationBuilder>. IApplicationBuilder definisce una classe per configurare la pipeline delle richieste di un'app. Per altre informazioni, vedere Creare una pipeline middleware con IApplicationBuilder.

Ogni IStartupFilter può aggiungere uno o più middleware nella pipeline delle richieste. I filtri vengono richiamati nell'ordine in cui sono stati aggiunti al contenitore di servizi. Poiché i filtri possono aggiungere il middleware prima o dopo il passaggio del controllo al filtro successivo, i filtri vengono aggiunti all'inizio o alla fine della pipeline dell'app.

L'esempio seguente dimostra come registrare un middleware con IStartupFilter. Il middleware RequestSetOptionsMiddleware imposta un valore di opzioni da un parametro di stringa di query:

public class RequestSetOptionsMiddleware
{
    private readonly RequestDelegate _next;

    public RequestSetOptionsMiddleware( RequestDelegate next )
    {
        _next = next;
    }

    // Test with https://localhost:5001/Privacy/?option=Hello
    public async Task Invoke(HttpContext httpContext)
    {
        var option = httpContext.Request.Query["option"];

        if (!string.IsNullOrWhiteSpace(option))
        {
            httpContext.Items["option"] = WebUtility.HtmlEncode(option);
        }

        await _next(httpContext);
    }
}

RequestSetOptionsMiddleware è configurato nella classe RequestSetOptionsStartupFilter:

public class RequestSetOptionsStartupFilter : IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return builder =>
        {
            builder.UseMiddleware<RequestSetOptionsMiddleware>();
            next(builder);
        };
    }
}

IStartupFilter è registrato nel contenitore di servizi in ConfigureServices.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
           .ConfigureAppConfiguration((hostingContext, config) =>
           {
           })
         .ConfigureWebHostDefaults(webBuilder =>
         {
             webBuilder.UseStartup<Startup>();
         })
        .ConfigureServices(services =>
        {
            services.AddTransient<IStartupFilter,
                      RequestSetOptionsStartupFilter>();
        });
}

Quando viene specificato un parametro di stringa di query per option, il middleware elabora l'assegnazione del valore prima che il middleware ASP.NET Core esegua il rendering della risposta.

L'ordine di esecuzione del middleware viene impostato in base all'ordine delle registrazioni IStartupFilter:

  • Più implementazioni IStartupFilter possono interagire con gli stessi oggetti. Se l'ordinamento è un fattore importante, ordinare le registrazioni di servizio IStartupFilter in un ordine corrispondente a quello di esecuzione dei relativi middleware.

  • Le librerie possono aggiungere middleware con una o più implementazioni IStartupFilter che viene eseguito prima o dopo altri middleware dell'app registrati con IStartupFilter. Per richiamare un middleware IStartupFilter prima che un middleware venga aggiunto da IStartupFilter di una libreria:

    • Posizionare la registrazione del servizio prima che la libreria venga aggiunta al contenitore di servizi.
    • Per richiamarlo in un secondo momento, posizionare la registrazione dei servizi dopo l'aggiunta della libreria.

Aggiungere elementi di configurazione all'avvio da un assembly esterno

Un'implementazione IHostingStartup consente l'aggiunta di miglioramenti a un'app all'avvio, da un assembly esterno alla classe Startup dell'app. Per altre informazioni, vedere Usare assembly di avvio dell'hosting in ASP.NET Core.

Risorse aggiuntive