In un'applicazione ASP.NET Core, la possibilità di monitorare la salute dei vari componenti tramite un controllo delle risorse è fondamentale, soprattutto in architetture a microservizi. L'implementazione di un controllo della salute personalizzato, che verifichi la disponibilità di un endpoint esterno, rappresenta una delle soluzioni più efficaci per monitorare la dipendenza da servizi esterni. Questo articolo esplorerà come creare e configurare un controllo della salute che effettui una richiesta HTTP a un sito web esterno e restituisca lo stato di "Salute" (Healthy) o "Non Salute" (Unhealthy) in base alla risposta.

Creazione di una Classe per il Controllo della Salute

Per implementare un controllo della salute personalizzato, è necessario prima creare una nuova classe che implementi l'interfaccia IHealthCheck. In questo caso, la classe effettuerà una richiesta HTTP a un URL esterno per verificare se il sito risponde correttamente.

csharp
using Microsoft.Extensions.Diagnostics.HealthChecks; using System.Net.Http; using System.Threading; using System.Threading.Tasks;
public class ExternalEndpointHealthCheck : IHealthCheck
{
private readonly string _externalUrl; public ExternalEndpointHealthCheck(string externalUrl) { _externalUrl = externalUrl; }
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken)
{
using (var httpClient = new HttpClient()) { try { var response = await httpClient.GetAsync(_externalUrl, cancellationToken); if (response.IsSuccessStatusCode) { return HealthCheckResult.Healthy($"Il controllo per {_externalUrl} ha avuto esito positivo."); } return HealthCheckResult.Unhealthy($"Il controllo per {_externalUrl} ha fallito."); } catch { return HealthCheckResult.Unhealthy($"Il controllo per {_externalUrl} ha fallito."); } } } }

In questa classe, il costruttore riceve l'URL del sito esterno da monitorare. Il metodo CheckHealthAsync effettua una richiesta GET all'URL specificato e determina lo stato della salute in base alla risposta HTTP. Se il sito esterno restituisce un codice di stato di successo (ad esempio, 200 OK), il controllo è considerato "Salute", altrimenti è "Non Salute".

Registrazione del Controllo della Salute Personalizzato

Per registrare il controllo della salute personalizzato nell'applicazione, è necessario aggiungerlo al contenitore di iniezione delle dipendenze nel file Program.cs o Startup.cs. Questo permetterà al sistema di eseguire il controllo automaticamente quando l'endpoint di salute sarà richiesto.

csharp
var builder = WebApplication.CreateBuilder(args); builder.Services.AddHealthChecks()
.AddCheck("ExternalEndpointHealthCheck", null, new[] { "external_endpoint" });
var app = builder.Build(); app.MapHealthChecks("/health");

In questo esempio, il controllo della salute è registrato con il nome "ExternalEndpointHealthCheck". Quando l'utente accederà all'endpoint /health, il sistema restituirà lo stato di salute dell'endpoint esterno.

Configurazione del Controllo della Salute

Durante la fase di registrazione del controllo, è necessario fornire l'URL del sito esterno, che in questo esempio è configurato con Google:

csharp
builder.Services.AddSingleton(_ => new ExternalEndpointHealthCheck("https://www.google.com"));

Questa configurazione permette al controllo della salute di eseguire la richiesta HTTP a Google per verificarne la disponibilità.

Test del Controllo della Salute

Una volta configurato l'applicativo, è possibile testare il controllo accedendo all'endpoint /health dell'applicazione. Se la configurazione è corretta, l'endpoint restituirà lo stato di salute dell'URL esterno monitorato.

Ad esempio, se il controllo dell'endpoint di Google è riuscito, l'output sarà simile a questo:

json
{
"status": "Healthy", "description": "Il controllo per https://www.google.com ha avuto esito positivo." }

Se, invece, il sito esterno è fuori servizio o non raggiungibile, il risultato sarà "Unhealthy", con una descrizione dell'errore.

Considerazioni Importanti

È fondamentale comprendere che i controlli della salute sono una parte cruciale nel garantire la robustezza e l'affidabilità di un'applicazione, soprattutto in ambienti complessi come quelli a microservizi. La gestione corretta delle dipendenze da servizi esterni attraverso i controlli della salute è essenziale per monitorare in tempo reale la disponibilità di tali servizi.

Inoltre, è utile sapere che, in contesti di produzione, un controllo della salute su un sito esterno potrebbe essere influenzato da vari fattori, come la latenza di rete o il tempo di inattività temporaneo del servizio esterno. Pertanto, è importante impostare dei limiti temporali adeguati e considerare l'uso di più meccanismi di fallback per garantire che l'applicazione non venga considerata non salubre per cause transitorie.

Endtext

Come distribuire un'applicazione .NET Core su un server Ubuntu con Nginx come proxy inverso

La distribuzione di un'applicazione .NET Core su un server Linux richiede diversi passaggi per configurare l'ambiente, installare le dipendenze necessarie e rendere l'applicazione accessibile tramite il web. Ubuntu è una delle distribuzioni più utilizzate per questo tipo di configurazione grazie alla sua stabilità e alla vasta documentazione disponibile. In questa guida, vedremo come preparare e distribuire un'applicazione ASP.NET Core 9.0 su un server Ubuntu 22.04, utilizzando Nginx come reverse proxy.

Il primo passo consiste nell'installazione di .NET su Ubuntu. Per farlo, bisogna aggiungere il repository Microsoft, importare la chiave di firma e aggiornare i pacchetti:

bash
# Aggiungi il repository di Microsoft
wget https://packages.microsoft.com/config/ubuntu/$repo_version/packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb rm packages-microsoft-prod.deb sudo apt update

Dopo aver configurato il repository, possiamo installare il .NET SDK 9.0:

bash
sudo apt-get install -y dotnet-sdk-9.0

Per verificare che l'installazione sia andata a buon fine, basta eseguire:

bash
dotnet --version

Se preferisci utilizzare il runtime .NET, puoi installarlo con il seguente comando:

bash
sudo apt-get install -y aspnetcore-runtime-9.0

Una volta configurato l'ambiente, puoi procedere con lo sviluppo dell'applicazione ASP.NET Core. Creiamo un'applicazione minimal API con il comando:

bash
dotnet new webapi -n dotnetapp
cd dotnetapp

Questa applicazione di esempio è pronta per essere sviluppata e testata localmente. Prima di continuare con la distribuzione, è importante verificare che l'applicazione funzioni correttamente nel proprio ambiente di sviluppo.

Nel file Program.cs, bisogna configurare l'applicazione per girare dietro un reverse proxy, come Nginx. Per farlo, aggiungiamo la middleware per i forwarded headers:

csharp
using Microsoft.AspNetCore.HttpOverrides; var builder = WebApplication.CreateBuilder(args); builder.Services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); var app = builder.Build(); app.UseForwardedHeaders();

Dopo aver scritto il codice e testato l'applicazione, possiamo procedere alla sua pubblicazione. Il comando per pubblicare l'applicazione in modalità release è il seguente:

bash
dotnet publish -c Release -o ./publish

A questo punto, l'applicazione è pronta per essere trasferita sul server Linux. Per farlo, dobbiamo copiare i file pubblicati nella directory /var/www/dotnetapp sul server:

bash
sudo mkdir /var/www/dotnetapp sudo cp -r ./publish/* /var/www/dotnetapp

Una volta trasferiti i file, dobbiamo assicurarci che i permessi siano corretti e che i file siano di proprietà dell'utente che eseguirà l'applicazione:

bash
sudo chown -R $USER:$USER /var/www/dotnetapp
sudo chmod -R 755 /var/www/dotnetapp

Il passo successivo è configurare l'applicazione come un servizio systemd, in modo che venga eseguita come un demone e si avvii automaticamente all'avvio del sistema. Creiamo un file di servizio in /etc/systemd/system/dotnetapp.service con il seguente contenuto:

bash
[Unit]
Description=Example .NET Web API App running on Ubuntu [Service] WorkingDirectory=/var/www/dotnetapp ExecStart=/usr/bin/dotnet /var/www/dotnetapp/dotnetapp.dll Restart=always RestartSec=10 KillSignal=SIGINT SyslogIdentifier=dotnet-example User=agusk Environment=ASPNETCORE_ENVIRONMENT=Production [Install] WantedBy=multi-user.target

Dopo aver creato il file, possiamo avviare e abilitare il servizio:

bash
sudo systemctl start dotnetapp.service sudo systemctl enable dotnetapp.service

Per verificare che il servizio sia attivo, eseguiamo:

bash
sudo systemctl status dotnetapp.service

Ora che l'applicazione è in esecuzione come servizio, dobbiamo configurare Nginx per farlo funzionare come reverse proxy. Se Nginx non è già installato, possiamo farlo con il comando:

bash
sudo apt install nginx

A questo punto, dobbiamo configurare Nginx per inoltrare le richieste HTTP alla nostra applicazione .NET Core. Modifichiamo il file di configurazione di Nginx:

bash
sudo nano /etc/nginx/sites-available/default

Aggiungiamo la seguente configurazione nel blocco server:

nginx
location / { proxy_pass http://localhost:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }

Questa configurazione consente a Nginx di inoltrare le richieste HTTP in entrata alla nostra applicazione ASP.NET Core che gira sulla porta 5000. Dopo aver salvato il file di configurazione, riavviamo Nginx:

bash
sudo systemctl restart nginx

Infine, possiamo testare la nostra applicazione accedendo all'indirizzo IP del server o al suo nome di dominio tramite un browser. L'applicazione dovrebbe essere visibile e funzionante. Per testarla ulteriormente, possiamo navigare fino a http://<ip_del_server>/WeatherForecast.

In sintesi, la distribuzione di un'applicazione .NET Core su un server Linux richiede attenzione nella configurazione dell'ambiente, nella corretta gestione dei permessi e nell'uso di strumenti come Nginx per gestire il traffico HTTP. Configurando il sistema per avviare l'applicazione come un servizio, possiamo garantire che essa venga eseguita automaticamente e rimanga sempre disponibile. È importante anche seguire le best practice di sicurezza, come l'aggiornamento regolare del server e l'uso di certificati SSL/TLS per proteggere la comunicazione tra il client e il server.

Come Utilizzare EF Core e le Migrazioni per Creare e Gestire un Database in un'API Minimale di ASP.NET Core

L'uso di Entity Framework Core (EF Core) e delle sue migrazioni è un metodo potente per gestire e interagire con i database in progetti .NET, in particolare quando si sviluppano API minimaliste con ASP.NET Core. Le migrazioni consentono di creare e aggiornare la struttura del database in modo sicuro e strutturato, facilitando la gestione delle modifiche al database nel tempo.

Per iniziare, supponiamo di voler creare un semplice servizio RESTful in cui gestiremo operazioni CRUD (Create, Read, Update, Delete) su un database SQL Server, utilizzando EF Core per interagire con il database. I passaggi principali sono suddivisi in diverse fasi, a partire dalla creazione del database fino alla configurazione dell'API.

Il primo passo consiste nell'aggiungere le migrazioni al progetto. Utilizzando il comando dotnet ef migrations add InitialCreate si crea una migrazione iniziale che prepara lo schema del database. Successivamente, eseguendo dotnet ef database update, il database viene effettivamente creato e aggiornato in base alle migrazioni applicate. Questo approccio consente di configurare il database direttamente attraverso il codice, senza bisogno di scrivere manualmente gli script SQL. Se dovessi incontrare errori relativi a "Invariant Globalization", potresti risolverli disabilitando questa opzione nel file del progetto efcoredb.csproj.

Se il progetto prevede l'uso di un database in memoria, non è necessario alcun ulteriore setup. Tuttavia, in molti scenari, è più utile configurare un database SQL reale, come nel caso dell'esempio fornito, dove si lavora con un database SQL Server.

Un altro aspetto importante per migliorare l'esperienza dell'utente che interagisce con l'API è l'integrazione di Scalar UI per OpenAPI. Scalar UI fornisce una comoda interfaccia utente per testare e visualizzare le API in modo interattivo. Per aggiungere questa funzionalità, basta installare il pacchetto Scalar.AspNetCore con il comando dotnet add package Scalar.AspNetCore, e successivamente configurarlo nel file Program.cs come segue:

csharp
using Scalar.AspNetCore; ... if (app.Environment.IsDevelopment()) { app.MapOpenApi(); app.MapScalarApiReference(); }

Questa configurazione consente agli sviluppatori di esplorare e testare le API direttamente tramite un'interfaccia grafica durante lo sviluppo.

Successivamente, è fondamentale testare le API appena create. Per farlo, puoi creare un file .http con richieste di test da inviare al server. Nel file di esempio EfCoreMinimalApi.http, vengono definite diverse operazioni HTTP, come ottenere tutti i prodotti (GET /products), ottenere un prodotto per ID (GET /products/{id}), creare un nuovo prodotto (POST /products), aggiornare un prodotto (PUT /products/{id}) e cancellare un prodotto (DELETE /products/{id}). Ogni richiesta è configurata per accettare o inviare dati in formato JSON.

L'uso di un client REST come l'estensione per Visual Studio Code ti permette di inviare le richieste e verificare le risposte dell'API. Puoi anche modificare il corpo delle richieste per testare operazioni di creazione, aggiornamento e cancellazione di prodotti. L'output del programma sarà visibile direttamente nel client REST, facilitando la diagnosi di eventuali problemi o confermando il corretto funzionamento del servizio.

Oltre alla configurazione e ai test, una parte cruciale di questo processo è la gestione delle migrazioni e delle modifiche alla struttura del database. Utilizzare il comando dotnet ef migrations add per aggiungere nuove migrazioni è essenziale per tenere traccia di tutte le modifiche alla struttura del database. Quando vengono effettuate modifiche ai modelli C#, come l'aggiunta di nuove tabelle o colonne, queste devono essere applicate tramite una nuova migrazione per aggiornare il database. L'uso delle migrazioni garantisce che il database rimanga sincronizzato con il codice dell'applicazione.

Infine, un altro passo importante, se si sta utilizzando il Database First, è il processo di "scaffolding" del DbContext e delle classi modello. Se si ha accesso a un database esistente, il comando dotnet ef dbcontext scaffold consente di generare automaticamente il codice per interagire con il database, evitando la necessità di scrivere manualmente il codice per i modelli e il contesto. In questo caso, è necessario fornire la stringa di connessione al database e configurare correttamente il file Program.cs per abilitare la connessione.

Un aspetto essenziale che non deve essere trascurato è l'importanza di separare la configurazione del database, come la stringa di connessione, dai file di codice. Invece di hardcodare la connessione direttamente nel codice, è consigliato spostare la configurazione nel file appsettings.json per una gestione più sicura e flessibile. Questo approccio consente di gestire facilmente le differenze tra gli ambienti di sviluppo e produzione.

L'interazione con il database, sia utilizzando un approccio Code First che Database First, deve essere eseguita in modo tale che l'applicazione possa evolvere senza interrompere il flusso di lavoro degli utenti o la stabilità del sistema. Le migrazioni EF Core svolgono un ruolo fondamentale in questo processo, poiché permettono di eseguire modifiche incrementalmente senza dover riscrivere l'intero schema del database.

In aggiunta a quanto già discusso, è fondamentale tenere in considerazione la gestione degli errori e la sicurezza nelle comunicazioni tra client e server, in particolare quando si opera su database reali. Utilizzare HTTPS per la trasmissione sicura dei dati e proteggere gli endpoint con opportune politiche di autorizzazione e autenticazione sono pratiche indispensabili.