<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Topics tagged with dev]]></title><description><![CDATA[A list of topics that have been tagged with dev]]></description><link>https://forum.androidiani.net/tags/dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 01 May 2026 00:34:38 GMT</lastBuildDate><atom:link href="https://forum.androidiani.net/tags/dev.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 30 Apr 2026 08:43:30 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Il pulsante di emergenza: revoca immediata dei token in .NET 10 con Duende IdentityServer]]></title><description><![CDATA[Immagina questo scenario da incubo: il telefono di un cliente bancario viene rubato, l’app mobile è già autenticata, e il ladro ha pieno accesso al suo conto. Il supporto riceve la chiamata disperata. Ogni secondo conta. Quanto tempo ci vuole per revocare quella sessione attiva e mettere al sicuro i fondi?Se stai usando JWT self-contained standard, la risposta onesta potrebbe essere “fino a un’ora”, a seconda della durata di validità del token. Non è accettabile. Vediamo come i Reference Token ti forniscono un vero pulsante di emergenza per queste situazioni, e come configurarli con Duende IdentityServer in .NET 10.Il problema dei JWT self-containedI JWT self-contained sono il cavallo di battaglia dell’autorizzazione moderna. Trasportano tutte le claim di cui un’API ha bisogno direttamente nel token. Nessuna query al database, nessuna chiamata al provider di identità. L’API valida la firma, controlla la scadenza, e il gioco è fatto. È elegante e performante.Ma questa natura self-contained è un’arma a doppio taglio. Una volta emesso un JWT, il provider di identità non ha più nulla da dire su di esso. Il token è valido fino a quando la claim exp non dice il contrario, tipicamente 5-60 minuti. Se un dispositivo viene rubato, un account compromesso, o una minaccia rilevata, non puoi revocare quel token. Sei costretto ad aspettare che scada.Per molte applicazioni questo compromesso è accettabile. Per ambienti ad alta sicurezza come banking, sanità o sistemi governativi, è un gap che non puoi permetterti.Reference Token: premere il pulsanteI Reference Token ribaltano il modello. Invece di incorporare tutte le claim direttamente nel token, IdentityServer memorizza il contenuto del token lato server nel suo persisted grant store e consegna al client un identificatore opaco (un “handle”). Quando un’API riceve questo handle, chiama l’endpoint di introspection di IdentityServer per validare il token e recuperare le claim.Questo cambia tutto. Poiché i dati del token risiedono sul server, puoi cancellarli in qualsiasi momento. La revoca è immediata. La prossima volta che l’API chiama l’endpoint di introspection, riceve "active": false, e l’accesso viene negato. Niente attese di scadenza, niente token obsoleti in circolazione.Il compromesso? Ogni chiamata API richiede un round-trip verso l’endpoint di introspection. Per API pubbliche su scala internet, è una preoccupazione. Per servizi interni e ambienti ad alta sicurezza, è un prezzo ragionevole per la capacità di staccare la spina istantaneamente.Configurare i Reference Token in IdentityServerPassare a Reference Token per un client richiede una singola riga di configurazione. Quando definisci il client in Duende IdentityServer, imposta la proprietà AccessTokenType:new Client
{
    ClientId = "banking_app",
    ClientSecrets = { new Secret("secret".Sha256()) },
    AllowedGrantTypes = GrantTypes.Code,

    // Questa è la riga chiave
    AccessTokenType = AccessTokenType.Reference,

    AllowOfflineAccess = true,
    RedirectUris = { "https://banking.example.com/signin-oidc" },
    AllowedScopes = { "openid", "profile", "accounts.read", "transfers.write" }
};I token emessi per questo client saranno ora handle opachi invece di JWT self-contained.Configurare l’API per l’introspectionLa tua API deve sapere come validare questi token opachi. Invece del (o in aggiunta al) classico JWT validation, configuri l’introspection OAuth 2.0. Prima, definisci un API Resource con un secret:new ApiResource("banking_api")
{
    Scopes = { "accounts.read", "transfers.write" },
    ApiSecrets = { new Secret("api_secret".Sha256()) }
};Poi nel Program.cs della tua API, registra l’handler di introspection:builder.Services.AddAuthentication("token")
    .AddOAuth2Introspection("token", options =&gt;
    {
        options.Authority = "https://identity.banking.example.com";
        options.ClientId = "banking_api";
        options.ClientSecret = "api_secret";
    });Se devi supportare sia JWT che Reference Token (magari durante una migrazione), puoi registrare entrambi gli handler e usare il forwarding per instradare i token a quello corretto:builder.Services.AddAuthentication("token")
    .AddJwtBearer("token", options =&gt;
    {
        options.Authority = "https://identity.banking.example.com";
        options.Audience = "banking_api";
        options.TokenValidationParameters.ValidTypes = ["at+jwt"];
        options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection");
    })
    .AddOAuth2Introspection("introspection", options =&gt;
    {
        options.Authority = "https://identity.banking.example.com";
        options.ClientId = "banking_api";
        options.ClientSecret = "api_secret";
    });Revocare un tokenQuando quella chiamata disperata arriva, il tuo sistema di supporto (o una pipeline automatica di rilevamento minacce) può revocare il token immediatamente usando l’endpoint di revocation di IdentityServer, che implementa la RFC 7009:using Duende.IdentityModel.Client;

var client = new HttpClient();
var result = await client.RevokeTokenAsync(new TokenRevocationRequest
{
    Address = "https://identity.banking.example.com/connect/revocation",
    ClientId = "banking_app",
    ClientSecret = "secret",
    Token = stolenAccessToken
});

if (result.IsError)
{
    logger.LogError("Token revocation failed: {Error}", result.Error);
}Una volta revocato, il token viene rimosso dal persisted grant store. La prossima richiesta di introspection da qualsiasi API confermerà che il token non è più attivo. L’accesso è tagliato.Non dimenticare: dovresti anche revocare il refresh token dell’utente per impedire al client di ottenere silenziosamente un nuovo access token:await client.RevokeTokenAsync(new TokenRevocationRequest
{
    Address = "https://identity.banking.example.com/connect/revocation",
    ClientId = "banking_app",
    ClientSecret = "secret",
    Token = refreshToken
});Nota: sia l’introspection che la revocation emettono eventi di audit che puoi usare per implementare log di audit nei settori regolamentati.Quando usare i Reference TokenI Reference Token non sono un sostituto universale dei JWT. Brillano in scenari specifici:La revoca immediata è un requisito imprescindibile (banking, sanità, sistemi compliance-driven)Comunicazione service-to-service interna dove il round-trip di introspection è trascurabileOperazioni ad alto rischio dove il beneficio di sicurezza supera il costo in performancePer API pubbliche su larga scala dove la latenza di revoca è accettabile, i JWT self-contained con breve durata rimangono una scelta solida. Puoi anche mixare i due approcci: Reference Token per client sensibili e JWT per quelli a minor rischio, tutto all’interno dello stesso deployment IdentityServer.ConclusioneOgni architettura di sicurezza implica compromessi. I JWT self-contained scambiano la revocabilità per la performance. I Reference Token scambiano la performance per il controllo. Per gli ambienti dove “aspetta che scada” non è una risposta accettabile, i Reference Token con Duende IdentityServer ti forniscono un vero pulsante di emergenza.L’implementazione è semplice: una proprietà sul client, un handler di introspection sull’API, e una chiamata di revocation quando devi staccare la spina. Quando accadono incidenti di sicurezza — e accadranno — sarai felice di averlo configurato.Fonte originale: The Emergency Stop Button – Implementing Immediate Token Revocation in .NET 10 — Khalid Abuhakmeh, Duende Software (28 aprile 2026)]]></description><link>https://forum.androidiani.net/topic/650373c9-e80f-4878-8fd6-cc69f4d213eb/il-pulsante-di-emergenza-revoca-immediata-dei-token-in-.net-10-con-duende-identityserver</link><guid isPermaLink="true">https://forum.androidiani.net/topic/650373c9-e80f-4878-8fd6-cc69f4d213eb/il-pulsante-di-emergenza-revoca-immediata-dei-token-in-.net-10-con-duende-identityserver</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Thu, 30 Apr 2026 08:43:30 GMT</pubDate></item><item><title><![CDATA[Come strutturare un’applicazione ASP.NET Core in crescita: dal monolite a strati ai vertical slice]]></title><description><![CDATA[Quando un’applicazione ASP.NET Core è piccola, quasi qualsiasi struttura di cartelle funziona. Controller in una cartella, servizi in un’altra, repository da qualche altra parte. Per un po’ va bene. Poi l’applicazione cresce, le funzionalità si moltiplicano e le regole di business si diffondono per tutta la codebase. Ogni modifica tocca cinque o sei file in posti diversi. I nuovi sviluppatori hanno bisogno di una guida turistica solo per capire dove si trova qualcosa.Quel momento è il punto in cui la struttura smette di essere una scelta cosmetica e diventa un problema di manutenibilità. In questo articolo esaminiamo le opzioni più comuni — Feature Folders, architettura a strati, Clean Architecture, Vertical Slices e Modular Monolith — con un’ottica pratica su quando e perché usarle.L’obiettivo reale non è l'”architettura perfetta”Prima di confrontare i pattern, è utile chiarire l’obiettivo. Non si tratta di rendere il progetto architetturalmente impressionante. Si tratta di rendere più facile:capire dove appartiene il codicemodificare una funzionalità senza rompere funzionalità non correlateinserire nuovi sviluppatori più velocementetestare il comportamento importante con meno attritofar evolvere la struttura man mano che il sistema cresceLa risposta giusta è solitamente quella che crea meno confusione per i prossimi 12-24 mesi di sviluppo, non quella che vince i dibattiti architetturali.La testabilità è una questione di architetturaUno dei controlli pratici più importanti è questo: possiamo verificare il comportamento di business importante con unit test veloci, senza avviare l’intera applicazione o un database reale? Se la risposta è no, l’attrito architetturale si manifesta come feedback lento, modifiche fragili e paura di fare refactoring.Una buona struttura migliora la testabilità rendendo esplicite le dipendenze e mantenendo le regole di business lontane dai dettagli del framework e dell’infrastruttura — cose come accesso al database, gestione HTTP, file system e chiamate a servizi esterni. Una regola pratica:Unit test per le decisioni di business e gli invarianti del dominioIntegration test per database, messaging e wiring HTTPEnd-to-end test per i percorsi utente criticiFeature FoldersI Feature Folders organizzano il codice per capacità di business invece che per tipo tecnico. Invece della struttura classica orizzontale:Controllers/ Services/ Repositories/ Models/ si passa a una struttura verticale per funzionalità:Features/   Orders/     Create/       CreateOrderEndpoint.cs       CreateOrderRequest.cs       CreateOrderHandler.cs     GetById/       GetOrderByIdEndpoint.cs       GetOrderByIdHandler.cs   Products/     List/       ListProductsEndpoint.cs       ListProductsHandler.cs Il principio guida è semplice: se devi modificare la funzionalità “Orders”, la maggior parte del codice che ti serve dovrebbe trovarsi da qualche parte sotto la cartella Orders. Questo riduce drasticamente il tempo di ricerca e la probabilità di modifiche accidentali a funzionalità non correlate.Adatto quando: l’applicazione sta crescendo oltre il CRUD basilare, il team vuole una chiara ownership per funzionalità, gli sviluppatori sono stanchi di saltare tra strati orizzontali per fare una piccola modifica.Attenzione: se applicati in modo disordinato, i Feature Folders possono diventare inconsistenti e trasformarsi in “un’altra convenzione di cartelle”.Architettura a strati (Layered Architecture)L’architettura a strati è la classica separazione in UI, logica di business e accesso ai dati:Web/ Application/ Domain/ Infrastructure/ Esiste da decenni proprio perché è facile da spiegare, facile da insegnare e fornisce una separazione delle responsabilità immediata. Per i team che vengono da tutorial e applicazioni di esempio, è spesso il punto di partenza più familiare.Un dettaglio pratico per .NET moderno: non è sempre necessario un layer repository separato, soprattutto se Entity Framework Core fornisce già l’astrazione necessaria per l’accesso ai dati semplice. Creare repository per “rispettare la struttura” piuttosto che per risolvere un problema reale è una delle trappole comuni.Adatto quando: il team è relativamente piccolo, l’applicazione non è ancora molto complessa, gli sviluppatori traggono vantaggio da una struttura familiare, la codebase è principalmente transazionale e CRUD-oriented.Attenzione: una modifica a una funzionalità richiede spesso modifiche su più strati. La logica di business può frammentarsi. Gli sviluppatori iniziano a creare astrazioni perché la struttura le richiede, non perché il problema ne ha bisogno.Clean ArchitectureClean Architecture pone forte enfasi sui confini tra logica di dominio e dettagli dell’infrastruttura. Il principio centrale è valido: le regole di business non dovrebbero essere strettamente accoppiate a database, web framework, message broker o SDK esterni.In pratica, però, alcuni team spingono Clean Architecture così lontano che ogni caso d’uso viene sepolto sotto strati di interfacce, wrapper, handler, repository e adattatori che il sistema non ha realmente bisogno. Il takeaway più importante non è il template completo, ma il principio: tieni le regole di business lontane dall’infrastruttura tecnica.// Esempio: un handler di dominio che NON dipende da EF Core direttamente public class CreateOrderHandler {     private readonly IOrderRepository _repository;  // astrazione, non EF diretto     private readonly IEventPublisher _events;      public CreateOrderHandler(IOrderRepository repository, IEventPublisher events)     {         _repository = repository;         _events = events;     }      public async Task&lt;OrderId&gt; Handle(CreateOrderCommand command, CancellationToken ct)     {         var order = Order.Create(command.CustomerId, command.Items);         await _repository.SaveAsync(order, ct);         await _events.PublishAsync(new OrderCreated(order.Id), ct);         return order.Id;     } } Adatto quando: il dominio ha una complessità significativa, l’applicazione ha una lunga vita prevista, più infrastrutture devono rimanere scambiabili o isolate, il team ha la disciplina per usare i confini intenzionalmente.Attenzione: è facile over-engineerare. Troppa cerimonia rallenta il lavoro su funzionalità semplici. I team inesperti spesso copiano diagrammi invece di risolvere il vero problema di manutenibilità.Vertical Slice ArchitectureL’architettura a vertical slice organizza il codice attorno a singoli casi d’uso o richieste. Invece di pensare per layer tecnici, ogni “slice” è un percorso verticale completo dalla richiesta alla risposta:Features/   PlaceOrder/     PlaceOrderCommand.cs     PlaceOrderHandler.cs     PlaceOrderValidator.cs     PlaceOrderEndpoint.cs   CancelOrder/     CancelOrderCommand.cs     CancelOrderHandler.cs     CancelOrderEndpoint.cs Ogni slice è autonoma e contiene tutto il necessario per gestire quella specifica operazione. Questo riduce l’accoppiamento tra funzionalità diverse: modificare “PlaceOrder” non richiede di toccare il codice di “CancelOrder”.MediatR è comunemente usato con questo pattern in .NET, ma non è obbligatorio — il pattern funziona anche con endpoint minimali diretti.Adatto quando: le funzionalità sono relativamente indipendenti tra loro, il team preferisce massimizzare la coesione per caso d’uso, si vuole limitare al minimo l’accoppiamento laterale.Attenzione: la duplicazione del codice tra slice simili può crescere se non si definisce chiaramente cosa è condiviso e cosa non lo è.Modular MonolithIl modular monolith è uno step successivo rispetto ai pattern precedenti: invece di organizzare il codice per funzionalità singole, si definiscono moduli di business più ampi con confini chiari tra loro, pur rimanendo un’unica applicazione deployabile.Modules/   Ordering/     Api/     Application/     Domain/     Infrastructure/   Catalog/     Api/     Application/     Domain/     Infrastructure/   Payments/     ... Ogni modulo espone un’interfaccia pubblica e nasconde i propri dettagli interni. La comunicazione tra moduli avviene attraverso quella interfaccia — mai direttamente tra le implementazioni interne. Questo crea i presupposti per un eventuale passaggio a microservizi, se e quando il sistema lo richiederà, senza dover fare un refactoring massiccio.Adatto quando: il sistema è abbastanza grande da giustificare confini chiari tra aree di business, non si vuole la complessità operativa dei microservizi, si vuole prepararsi a una futura decomposizione senza impegnarsi subito.Quale scegliere?Non esiste una risposta universale, ma questo schema può orientare la scelta:App nuova, team piccolo, CRUD dominante → Layered o Feature FoldersApp in crescita, molte funzionalità indipendenti → Feature Folders o Vertical SlicesDominio complesso, lunga vita prevista, team disciplinato → Clean ArchitectureSistema grande, confini di business chiari, no microservizi ancora → Modular MonolithInizia dalla struttura più semplice che risolve il tuo problema attuale. Evolvi quando la complessità del sistema lo giustifica, non prima. Il momento migliore per passare a un’architettura più sofisticata è quando il dolore del non averla è reale e misurabile — non anticipatorio.Fonte: ASP.NET: How to Structure a Growing Application So It Stays Maintainable — Chris Pietschmann, pietschsoft.com.]]></description><link>https://forum.androidiani.net/topic/26decad8-f135-4f5c-9328-7bbdf366198c/come-strutturare-un-applicazione-asp.net-core-in-crescita-dal-monolite-a-strati-ai-vertical-slice</link><guid isPermaLink="true">https://forum.androidiani.net/topic/26decad8-f135-4f5c-9328-7bbdf366198c/come-strutturare-un-applicazione-asp.net-core-in-crescita-dal-monolite-a-strati-ai-vertical-slice</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Wed, 29 Apr 2026 15:53:47 GMT</pubDate></item><item><title><![CDATA[Visual Studio Code 1.118: Agents app, Copilot CLI avanzato e TypeScript 7.0 in anteprima]]></title><description><![CDATA[La build Insiders di Visual Studio Code è arrivata alla versione 1.118, con un pacchetto di aggiornamenti concentrati soprattutto sullo sviluppo agente, sull’integrazione con Copilot CLI e sul supporto anticipato a TypeScript 7.0. Queste note coprono le modifiche introdotte tra il 21 e il 26 aprile 2026.Agents app: SSO condiviso con VS CodeDa questa build, l’Agents app di VS Code supporta la condivisione del Single Sign-On (SSO) con Visual Studio Code su Windows. L’autenticazione è bidirezionale: se esegui il logout da una delle due applicazioni, l’operazione si propaga automaticamente all’altra. Questo elimina la necessità di autenticarsi separatamente nei due ambienti, rendendo più fluido il lavoro che alterna editor e agenti.Sempre nell’Agents app, da questa versione viene rispettato lo stato di workspace trust già impostato in Visual Studio Code. Non è quindi più necessario ridefinire le autorizzazioni di fiducia del workspace quando si passa da un contesto all’altro.Supporto per sessioni Claude Code nell’Agents appUna novità rilevante per chi usa agenti IA nel proprio flusso di sviluppo: l’Agents app integra ora il supporto per le sessioni di Claude Code. Questo significa che puoi avviare e gestire sessioni di Claude Code direttamente dall’interno di VS Code, senza passare al terminale o a un’applicazione separata.Per navigare rapidamente tra le sessioni aperte nell’Agents app sono stati aggiunti nuovi keybinding: Ctrl+1 e Ctrl+2 permettono di passare tra le sessioni senza dover usare il mouse.Skill tool per le personalizzazioni agente e context: forkIl sistema di personalizzazione degli agenti si arricchisce di un nuovo skill tool per le agent customizations. Insieme a questa funzione, è stato introdotto il supporto per context: fork, che permette di isolare il contesto di una skill in un ramo separato. Questo offre maggiore controllo su quali informazioni vengono condivise tra skill diverse durante una sessione agente.Il menu di creazione delle personalizzazioni chat mostra ora anche descrizioni esplicative per ciascuna posizione di skill, aiutando gli sviluppatori a scegliere il tipo corretto di personalizzazione senza dover consultare la documentazione.Copilot CLI: selezione automatica del modello e badgeCopilot CLI ha ricevuto il supporto per la selezione automatica del modello (auto model). Questa funzione analizza il contesto della richiesta e sceglie automaticamente il modello più adatto, senza che l’utente debba specificarlo manualmente.Nelle risposte Copilot CLI visualizzate nel pannello chat è ora presente un badge con il nome del modello che ha gestito la richiesta. Questa piccola aggiunta è utile per chi vuole tenere traccia di quale modello viene effettivamente usato nelle diverse situazioni, soprattutto con l’auto-selezione attiva.Sul fronte dell’infrastruttura, il Copilot CLI SDK risolve ora node-pty direttamente da VS Code tramite hostRequire, eliminando la necessità di copiare i binari di node-pty nella cartella prebuilds dell’SDK durante la build o al runtime. Questo semplifica il packaging e riduce i potenziali problemi di compatibilità tra versioni.Le sessioni nel Copilot CLI SDK usano ora le API session-title del CLI come sorgente di verità per i nomi delle sessioni, garantendo nomi coerenti tra l’interfaccia della chat e il log delle sessioni.TypeScript 7.0: opt-in alle nightlyPer chi vuole vivere sul filo del rasoio: VS Code 1.118 introduce la possibilità di opt-in alle nightly di TypeScript 7.0. Per abilitarlo è sufficiente modificare l’impostazione typescript.experimental.useTsgo nelle preferenze utente o workspace. Si ricorda che TypeScript 7.0 è basato sul nuovo compilatore riscritto in Go (annunciato con TS 7.0 Beta), che promette velocità circa 10 volte superiori rispetto al compilatore attuale — ma è ancora in fase sperimentale.Supporto encoding CP857Aggiunto il supporto per la codifica CP857 (Code Page 857, usata per il turco nella vecchia codifica DOS). Un’aggiunta di nicchia, ma apprezzabile per chi lavora con legacy codebase o file di testo in quel formato.Accessibility nel terminaleSono stati introdotti miglioramenti all’accessibilità per il question carousel delle azioni del terminale, garantendo una navigazione più fluida per gli utenti che usano screen reader o altri strumenti assistivi.Come aggiornare alla build InsidersSe vuoi testare queste funzionalità prima del rilascio stabile, puoi scaricare la VS Code Insiders build dal sito ufficiale. La versione Insiders si affianca a quella stabile e può essere usata in parallelo.# Su Linux, tramite snap: sudo snap install --classic code-insiders  # Su Windows/macOS: scarica l'installer dalla pagina ufficiale Tieni presente che le funzionalità descritte in queste note riguardano la build Insiders e potrebbero cambiare prima del rilascio stabile del ciclo 1.118.Fonte: Visual Studio Code 1.118 Release Notes — Visual Studio Code Team, aggiornato al 27 aprile 2026.]]></description><link>https://forum.androidiani.net/topic/48e1848e-4797-4e22-8756-200d9145715c/visual-studio-code-1.118-agents-app-copilot-cli-avanzato-e-typescript-7.0-in-anteprima</link><guid isPermaLink="true">https://forum.androidiani.net/topic/48e1848e-4797-4e22-8756-200d9145715c/visual-studio-code-1.118-agents-app-copilot-cli-avanzato-e-typescript-7.0-in-anteprima</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Tue, 28 Apr 2026 16:40:16 GMT</pubDate></item><item><title><![CDATA[AI locale in un’estensione Chrome con Transformers.js e Manifest V3: architettura pratica]]></title><description><![CDATA[Hugging Face ha pubblicato una guida dettagliata su come costruire un’estensione Chrome che esegue modelli AI direttamente nel browser, senza server esterni, usando Transformers.js e Manifest V3 (MV3). Il progetto di riferimento è una browser assistant basata su Gemma 4 E2B, open source e già disponibile sul Chrome Web Store. Vediamo in dettaglio l’architettura e le scelte tecniche che rendono fattibile questo approccio.Perché AI locale in un’estensione?L’inferenza locale porta vantaggi concreti: nessun dato dell’utente inviato a server esterni, latenza ridotta dopo il download iniziale del modello, funzionamento offline. Il limite storico era la complessità di integrare modelli ONNX direttamente in un’estensione browser. Transformers.js risolve questo problema esponendo un’API familiare (ispirata alla libreria Python di HuggingFace) che gira interamente nel browser tramite WebAssembly e WebGPU.Architettura MV3: tre contesti, tre ruoliManifest V3 impone un’architettura a contesti separati, ognuno con accesso e ciclo di vita differenti. Il progetto usa tre entry point distinti:Background service worker (background.ts il piano di controllo. Gestisce il ciclo di vita dell’agente, l’inizializzazione dei modelli, l’esecuzione dei tool e i servizi condivisi come feature extraction. I modelli Transformers.js vengono caricati e mantenuti qui.Side panel (sidebar/ il layer di interazione con l’utente. Chat input/output, streaming degli aggiornamenti, controlli di setup.Content script (content.ts il bridge con la pagina web. Estrae contenuto dal DOM e gestisce l’evidenziazione di elementi.La regola di design è chiara: orchestrazione pesante nel background, UI e logica di pagina leggeri. Questo evita di caricare il modello più volte, mantiene l’interfaccia reattiva e rispetta i confini di sicurezza di Chrome.Contratto di messaggistica tipatoCon contesti separati, la comunicazione avviene tramite messaggi. Il progetto li tipizza con enum in src/shared/types.ts:// Side panel verso background
enum BackgroundTasks {
  CHECK_MODELS,
  INITIALIZE_MODELS,
  AGENT_GENERATE_TEXT,
  AGENT_GET_MESSAGES,
  AGENT_CLEAR,
  EXTRACT_FEATURES
}

// Background verso side panel
enum BackgroundMessages {
  DOWNLOAD_PROGRESS,
  MESSAGES_UPDATE
}

// Background verso content script
enum ContentTasks {
  EXTRACT_PAGE_DATA,
  HIGHLIGHT_ELEMENTS,
  CLEAR_HIGHLIGHTS
}
Il flusso tipico è: la side panel invia AGENT_GENERATE_TEXT, il background aggiunge il messaggio alla conversazione, esegue l’inferenza, poi emette MESSAGES_UPDATE alla side panel che ri-renderizza.Integrazione di Transformers.js: dove gira l’inferenzaL’estensione usa due modelli con ruoli distinti, definiti in src/shared/constants.ts:Text generation (LLM): onnx-community/gemma-4-E2B-it-ONNX, formato q4f16 – responsabile delle risposte chat e dell’esecuzione dell’agente.Feature extraction (embedding): un modello separato per estrarre vettori da testi di pagina, usato per operazioni semantiche.Entrambi i modelli vengono inizializzati e cachati nel background service worker. Il download avviene al primo avvio e i pesi rimangono nella cache del browser (via Cache API), così le sessioni successive partono istantaneamente. Il progresso del download viene trasmesso alla side panel tramite l’evento DOWNLOAD_PROGRESS.Agent loop e tool callingL’estensione implementa un loop agente completo. La classe Agent gestisce la cronologia dei messaggi e il ciclo di ragionamento:// Flusso semplificato di Agent.runAgent
while (true) {
  const response = await model.generate(chatMessages);

  if (response.hasToolCall) {
    const toolResult = await executeTool(response.toolCall);
    chatMessages.push({ role: "tool", content: toolResult });
  } else {
    // Risposta finale
    break;
  }
}
I tool disponibili includono EXTRACT_PAGE_DATA (estrae il testo dalla pagina corrente via content script) e HIGHLIGHT_ELEMENTS (evidenzia elementi nel DOM). L’interfaccia dei tool è definita con schema JSON per permettere al modello di invocarli correttamente.Build e packaging: Vite e MV3Il progetto usa Vite per il build, con configurazione custom per generare entry point separati per background, side panel e content script. I modelli ONNX non sono inclusi nel bundle dell’estensione (sarebbero troppo grandi), ma vengono scaricati da Hugging Face Hub al primo avvio.Un dettaglio pratico importante: i service worker MV3 possono essere terminati dal browser in qualsiasi momento quando inattivi. Bisogna gestire la persistenza dello stato (conversazione, modelli inizializzati) in modo da riprendere correttamente al risveglio del worker. Il progetto usa chrome.storage.session per lo stato effimero e chrome.storage.local per i dati persistenti tra sessioni.Considerazioni pratiche prima di adottare questo approccioPrima di replicare questa architettura in un progetto reale, vale la pena considerare alcune limitazioni:Dimensione modello: Gemma 4 E2B in q4f16 pesa diversi gigabyte. Il download iniziale richiede una connessione affidabile e spazio disco significativo nel profilo Chrome.Compatibilità hardware: le prestazioni variano molto tra macchine. Su hardware senza GPU decente, l’inferenza può essere lenta anche con quantizzazione aggressiva.Ciclo di vita service worker: Chrome può terminare il background worker dopo 5 minuti di inattività. Gestire il riavvio e la reinizializzazione del modello è parte non banale dell’implementazione.Review del Chrome Web Store: le estensioni con funzionalità AI vengono esaminate più attentamente; documentare chiaramente cosa fa il modello e dove girano i dati accelera il processo di approvazione.ConclusioneL’architettura descritta da HuggingFace è solida e dimostra che eseguire AI locale in un’estensione Chrome è fattibile oggi con Transformers.js. Il codice sorgente dell’estensione Gemma 4 Browser Assistant è disponibile su GitHub come riferimento completo, con implementazione reale di tool calling, streaming e gestione del ciclo di vita MV3. Per chi vuole portare funzionalità AI nelle proprie estensioni senza dipendere da API esterne, questo progetto è un ottimo punto di partenza.Fonte: How to Use Transformers.js in a Chrome Extension – Hugging Face Blog, 23 aprile 2026]]></description><link>https://forum.androidiani.net/topic/15a97c55-d83a-4922-bb0a-9653407a50ee/ai-locale-in-un-estensione-chrome-con-transformers.js-e-manifest-v3-architettura-pratica</link><guid isPermaLink="true">https://forum.androidiani.net/topic/15a97c55-d83a-4922-bb0a-9653407a50ee/ai-locale-in-un-estensione-chrome-con-transformers.js-e-manifest-v3-architettura-pratica</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Tue, 28 Apr 2026 10:22:24 GMT</pubDate></item><item><title><![CDATA[CodeAct e Hyperlight: agenti AI piu veloci con meno chiamate al modello nel .NET Agent Framework]]></title><description><![CDATA[Chi lavora con agenti AI su basi .NET sa bene che il vero collo di bottiglia non è spesso la qualità del modello, ma il numero di round trip tra il modello stesso e i tool. Un agente che deve recuperare dati, filtrarli, fare calcoli e assemblare un risultato finisce tipicamente per eseguire cinque o sei chiamate separate al modello, ognuna con la propria latenza e il proprio costo in token. Microsoft ha presentato una soluzione concreta a questo problema: CodeAct, ora disponibile nel pacchetto alpha agent-framework-hyperlight.Il problema: troppi turni, troppa latenzaNel flusso tradizionale, un agente ragiona come segue: chiede al modello quale tool usare, esegue quel tool, rimanda il risultato al modello, il quale decide il prossimo tool, e così via. Questo schema modello → tool → modello → tool moltiplica la latenza e il consumo di token con ogni step aggiuntivo. Su task composti da tre, quattro o cinque operazioni concatenate (tipico nelle pipeline di data wrangling, elaborazione report, lookup incrociati), il costo diventa significativo.CodeAct risolve il problema in modo elegante: invece di chiedere al modello di scegliere un tool alla volta, gli viene offerto un singolo tool speciale chiamato execute_code. Il modello esprime l’intero piano come un breve programma Python, che viene eseguito una volta sola in un ambiente sandbox. Il risultato? Latenza ridotta del ~50% e consumo di token calato di oltre il 60% su workload rappresentativi, secondo i dati pubblicati da Microsoft.Hyperlight: sandbox micro-VM per sicurezza senza compromessiLa parte che rende CodeAct praticabile in produzione è Hyperlight: una tecnologia Microsoft che avvia una micro-VM isolata per ogni esecuzione di codice generato dal modello. Il codice Python prodotto dall’LLM gira dentro questa sandbox, senza accesso al filesystem host, alla rete o a qualsiasi risorsa non esplicitamente autorizzata. I tool reali invece continuano a girare nel runtime dell’applicazione, con tutti i permessi necessari.Il bridge tra sandbox e tool avviene tramite la funzione call_tool(...): quando il codice nella sandbox chiama call_tool("nome_tool", ...), Hyperlight instrada la chiamata verso il tool nel processo principale, ne ritorna il risultato nella sandbox, e il programma continua. Il codice generato dall’AI rimane isolato; solo i tool verificati e distribuiti dallo sviluppatore hanno accesso reale alle risorse.Come si integra CodeAct nel proprio agenteIl setup è sorprendentemente compatto. Dopo aver installato i pacchetti agent-framework e agent-framework-hyperlight:from agent_framework import Agent, tool from agent_framework_hyperlight import HyperlightCodeActProvider  @tool def get_weather(city: str) -&gt; dict:     # Restituisce il meteo corrente per una citta     return {"city": city, "temperature_c": 21.5, "conditions": "partly cloudy"}  codeact = HyperlightCodeActProvider(     tools=[get_weather],     approval_mode="never_require", )  agent = Agent(     client=client,     name="CodeActAgent",     instructions="Sei un assistente utile.",     context_providers=[codeact], )  result = await agent.run(     "Ottieni il meteo di Seattle e Amsterdam e confrontali." ) HyperlightCodeActProvider si occupa di due cose in automatico: registra il tool execute_code ad ogni run dell’agente, e inietta nel system prompt le istruzioni sulla sandbox e sui tool disponibili via call_tool(...).Gestione delle approvazioni: chi controlla cosaAgent Framework distingue due modalità di approvazione per i tool:never_require: il framework invoca il tool automaticamente.always_require: ogni chiamata viene sospesa in attesa di un’approvazione human-in-the-loop.Con CodeAct, la logica cambia leggermente. I tool registrati su HyperlightCodeActProvider non vengono esposti direttamente al modello come tool di primo livello: il modello vede solo execute_code e raggiunge gli altri tool scrivendo call_tool("nome", ...) nel programma Python. L’approvazione, se richiesta, si applica all’intero blocco di codice, non alle singole chiamate interne.La regola pratica è chiara: i tool puri e sicuri (lookup dati, calcoli, chiamate read-only) vanno passati al provider, così il modello li può comporre in un unico turno. I tool con side effect (invio email, scrittura su sistemi in produzione, transazioni economiche) vanno tenuti sull’agente direttamente con approval_mode="always_require", così il modello li deve invocare esplicitamente uno per uno.Quando conviene usare CodeActCodeAct non è la soluzione giusta per ogni agente. I benefici massimi si ottengono con task che coinvolgono molte operazioni concatenate e chainabili: data wrangling, generazione report, lookup multipli, calcoli intermedi. Se il task dell’agente si risolve quasi sempre con una o due chiamate a tool, il guadagno è marginale.È anche importante considerare che il codice Python generato dal modello deve essere revisionabile: uno dei vantaggi collaterali di CodeAct è che l’intero piano dell’agente è concentrato in un singolo blocco di codice leggibile e auditabile, invece di essere distribuito su una catena di messaggi di tool-call.ConclusioneCodeAct con Hyperlight rappresenta un’evoluzione pragmatica nell’architettura degli agenti AI su .NET: meno turni, meno token, stessa qualità. Il pattern è disponibile oggi nel pacchetto alpha agent-framework-hyperlight, pronto per essere sperimentato su workload interni prima di adottarlo in produzione. Chi sta già usando Agent Framework e si trova a costruire pipeline di tool-calling complesse troverà probabilmente il guadagno di latenza immediato e concreto.Fonte: CodeAct in Agent Framework: Faster Agents with Fewer Model Turns – Microsoft Dev Blogs, 23 aprile 2026]]></description><link>https://forum.androidiani.net/topic/15e2400c-327b-4c3d-b59f-dc42f91dbc68/codeact-e-hyperlight-agenti-ai-piu-veloci-con-meno-chiamate-al-modello-nel-.net-agent-framework</link><guid isPermaLink="true">https://forum.androidiani.net/topic/15e2400c-327b-4c3d-b59f-dc42f91dbc68/codeact-e-hyperlight-agenti-ai-piu-veloci-con-meno-chiamate-al-modello-nel-.net-agent-framework</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Mon, 27 Apr 2026 13:55:45 GMT</pubDate></item><item><title><![CDATA[LangChain.js per sviluppatori JavaScript: corso gratuito per costruire agenti AI]]></title><description><![CDATA[Volete costruire agenti AI con JavaScript che vadano oltre le semplici chat? Agenti che ragionano, chiamano strumenti esterni e interrogano knowledge base in modo autonomo? Microsoft ha pubblicato un corso gratuito e open source per fare esattamente questo: LangChain.js for Beginners, 8 capitoli con oltre 70 esempi TypeScript eseguibili.Se già conoscete Node.js, npm, TypeScript e async/await, non avete bisogno di passare a Python per sviluppare applicazioni AI. LangChain.js vi mette a disposizione i componenti necessari — chat model, tool, agenti, retrieval e molto altro — senza dover cablare tutto da zero.Perché LangChain.js?LangChain.js è come avere un negozio di ferramenta completamente fornito a portata di mano. Invece di fabbricare ogni strumento dal metallo grezzo, prendete quello che serve dallo scaffale e iniziate a costruire. La libreria astrae l’integrazione con vari LLM (OpenAI, Azure OpenAI, Anthropic e altri), standardizza l’interfaccia per tool e agenti, e fornisce primitive per la costruzione di pipeline RAG.Il vantaggio rispetto a Python? Zero friction per chi già lavora nell’ecosistema JavaScript/TypeScript. Stessi strumenti di build, stesso toolchain CI/CD, stessa base di codice.Struttura del corso: un approccio agent-firstLa maggior parte dei tutorial su LangChain inizia con document loader ed embedding. Questo corso arriva agli agenti e ai tool presto, perché è lì che si trovano i sistemi AI in produzione. Gli agenti decidono cosa fare, quando usare strumenti, e se hanno bisogno di cercare dati.Capitoli 1-3: fondamentaLa prima chiamata a un LLM, chat model, streaming, prompt template e output strutturati con schemi Zod. Contenuto classico, ma necessario prima che le cose si facciano interessanti.Capitolo 4: Function Calling e ToolQui l’AI smette di parlare e inizia a fare. Si insegna al modello a chiamare funzioni personalizzate, e lui ragiona su quando usarle. Esempio pratico:import { ChatOpenAI } from "@langchain/openai";
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const weatherTool = tool(
  async ({ city }) =&gt; {
    // chiamata a una API meteo reale
    return `Il meteo a ${city} è soleggiato, 22°C`;
  },
  {
    name: "get_weather",
    description: "Ottieni le condizioni meteo correnti per una città",
    schema: z.object({
      city: z.string().describe("Il nome della città"),
    }),
  }
);

const model = new ChatOpenAI({ model: "gpt-4o" });
const modelWithTools = model.bindTools([weatherTool]);

const result = await modelWithTools.invoke(
  "Che tempo fa a Milano?"
);
console.log(result.tool_calls);Capitolo 5: Agenti con pattern ReActUn LLM risponde a domande. Un agente ragiona attraverso i problemi, sceglie i tool giusti ed esegue piani multi-step. Il capitolo 5 mostra come costruire agenti con il pattern ReAct (Reason + Act): il modello alterna tra pensiero esplicito e azioni concrete fino a raggiungere la risposta finale.import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";

const agent = createReactAgent({
  llm: new ChatOpenAI({ model: "gpt-4o" }),
  tools: [weatherTool, searchTool, calculatorTool],
});

const response = await agent.invoke({
  messages: [{ role: "user", content: "Pianifica un itinerario a Roma per domani" }],
});
console.log(response.messages.at(-1)?.content);Capitolo 6: MCP — Model Context ProtocolIl Model Context Protocol sta diventando lo standard per connettere l’AI a servizi esterni. Il capitolo guida alla costruzione di server MCP e al collegamento degli agenti tramite trasporti HTTP e stdio. È un’abilità sempre più richiesta man mano che l’ecosistema AI aziendale matura.Capitoli 7 e 8: RAG agentivoI capitoli finali portano documenti, embedding e ricerca semantica, poi combinano tutto in Agentic RAG. L’agente decide quando cercare nella knowledge base e quando rispondere direttamente da ciò che già conosce.È una distinzione importante. Il RAG tradizionale è come lo studente che sfoglia il libro di testo per ogni domanda, anche “Quanto fa 2+2?”. Il RAG agentivo è lo studente intelligente che risponde alle domande semplici a memoria e apre il libro solo quando ne ha davvero bisogno. Il risultato: risposte più veloci, costi inferiori (meno ricerche di embedding non necessarie) e un’esperienza complessivamente migliore.Come iniziareIl corso è open source su GitHub. Per iniziare bastano tre comandi:# Clona il repository
git clone https://github.com/microsoft/generative-ai-with-javascript

# Installa le dipendenze
cd generative-ai-with-javascript/lessons/08-langchain/
npm install

# Configura la chiave API
cp .env.example .env
# Aggiungi AZURE_OPENAI_API_KEY o OPENAI_API_KEY nel file .env

# Esegui il primo esempio
npx ts-node chapter1/01-first-llm-call.tsOgni capitolo include spiegazioni concettuali con analogie concrete, esempi di codice eseguibili immediatamente, sfide pratiche per testare la comprensione e punti chiave per consolidare l’apprendimento.A chi è rivoltoIl corso si rivolge a sviluppatori JavaScript/TypeScript che conoscono npm install e async/await. Non è richiesta esperienza precedente in AI o machine learning. Ogni capitolo inizia con un’analogia del mondo reale per ancorare il concetto prima di qualsiasi codice.Per chi già lavora su applicazioni .NET o backend e vuole esplorare il lato AI senza cambiare linguaggio, questo corso rappresenta il punto di ingresso ideale: nessun boilerplate Python, nessun ambiente virtuale da gestire, solo TypeScript e strumenti già familiari.Considerazioni finaliLangChain.js ha raggiunto una maturità che lo rende adatto a progetti di produzione. Il corso di Microsoft colma un gap reale: la maggior parte della documentazione e dei tutorial sull’AI generativa è orientata a Python. Avere un percorso strutturato, gratuito e orientato agli agenti per l’ecosistema JavaScript è un vantaggio concreto per chi già lavora in questo stack.Se state valutando come integrare capacità AI nelle vostre applicazioni Node.js o Deno, o se volete costruire un copilota interno per il vostro team, questo è il punto di partenza più pragmatico disponibile oggi.Fonte originale: LangChain.js for Beginners: A Free Course to Build Agentic AI Apps with JavaScript — Microsoft for Developers]]></description><link>https://forum.androidiani.net/topic/d6b3b4f8-f64b-4308-8c95-6ce062a6f12c/langchain.js-per-sviluppatori-javascript-corso-gratuito-per-costruire-agenti-ai</link><guid isPermaLink="true">https://forum.androidiani.net/topic/d6b3b4f8-f64b-4308-8c95-6ce062a6f12c/langchain.js-per-sviluppatori-javascript-corso-gratuito-per-costruire-agenti-ai</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Mon, 27 Apr 2026 11:00:43 GMT</pubDate></item><item><title><![CDATA[State Pattern in C#: guida decisionale con esempi pratici]]></title><description><![CDATA[Gli oggetti cambiano comportamento in base al loro stato interno continuamente. Un documento passa da bozza a revisione a pubblicato. Un personaggio di un gioco alterna tra inattivo, in corsa e in attacco. Un pagamento transita da pending ad autorizzato a catturato. La domanda quando usare lo State Pattern in C# emerge nel momento in cui la logica condizionale inizia a ramificarsi sullo stesso campo stato in metodo dopo metodo — e ogni nuovo stato obbliga a toccare più file.In questo articolo troverete una guida decisionale pratica per il pattern State in C#: quando merita davvero il suo posto e quando soluzioni più semplici — enum o flag booleani — bastano e avanzano.Cos’è lo State Pattern e cosa fa davveroLo State Pattern consente a un oggetto di cambiare il proprio comportamento quando il suo stato interno cambia. Dall’esterno sembra che l’oggetto abbia cambiato classe. Invece di sparpagliare istruzioni if e switch in ogni metodo che dipende dallo stato corrente, si incapsula il comportamento di ciascuno stato in una propria classe. L’oggetto delega al state object attivo in quel momento.La struttura coinvolge tre ruoli:Context — mantiene un riferimento allo state object corrente e vi delega il comportamentoState interface — dichiara i metodi che ogni stato deve implementareConcrete state classes — implementano l’interfaccia con il comportamento specifico del loro statoQuando avviene una transizione, il context sostituisce il riferimento al proprio state object. Questo approccio elimina i blocchi condizionali che crescono ogni volta che si aggiunge uno stato nuovo.Segnali che indicano che avete bisogno dello State PatternNon ogni oggetto con un campo status ha bisogno dello State Pattern. Tuttavia certi code smell sono segnali forti che il pattern ripulirà il design.La logica condizionale si ramifica sullo stesso stato ovunqueQuesto è il trigger principale. Quando vedete lo stesso switch o if-else che controlla _status in tre, quattro o dieci metodi diversi, il vostro oggetto sta gestendo le transizioni di stato nel modo più difficile. Ogni nuovo stato significa toccare ciascuno di quei metodi.Avete regole di transizione complesseSe le transizioni valide dipendono dallo stato attuale — e alcune azioni devono lanciare eccezioni o essere semplicemente ignorate a seconda di dove ci si trova — il pattern State rende queste regole esplicite invece di affogarle in condizionali.Ogni stato ha un comportamento distintoQuando lo stato influenza come si comporta un metodo, non soltanto se eseguirlo, il pattern vale l’investimento. Ciascuna classe stato diventa un luogo coeso che racchiude tutto il comportamento per quella condizione.Scenario 1: gestione degli ordini (comportamento condizionale complesso)Ecco un esempio di elaborazione ordini con lo State Pattern:public interface IOrderState {     void Submit(OrderContext context);     void Cancel(OrderContext context);     void Ship(OrderContext context);     void Deliver(OrderContext context); }  public sealed class OrderContext {     public IOrderState CurrentState { get; private set; }     public string OrderId { get; }      public OrderContext(string orderId)     {         OrderId = orderId;         CurrentState = new PendingState();     }      public void TransitionTo(IOrderState state)     {         Console.WriteLine(             $"Order {OrderId}: " +             $"{CurrentState.GetType().Name} -&gt; " +             $"{state.GetType().Name}");         CurrentState = state;     }      public void Submit() =&gt; CurrentState.Submit(this);     public void Cancel() =&gt; CurrentState.Cancel(this);     public void Ship()   =&gt; CurrentState.Ship(this);     public void Deliver() =&gt; CurrentState.Deliver(this); }  public sealed class PendingState : IOrderState {     public void Submit(OrderContext context)     {         Console.WriteLine("Ordine inviato per elaborazione.");         context.TransitionTo(new ProcessingState());     }      public void Cancel(OrderContext context)     {         Console.WriteLine("Ordine annullato prima dell'invio.");         context.TransitionTo(new CancelledState());     }      public void Ship(OrderContext context) =&gt;         throw new InvalidOperationException("Impossibile spedire un ordine in attesa.");      public void Deliver(OrderContext context) =&gt;         throw new InvalidOperationException("Impossibile consegnare un ordine in attesa."); }Notate come PendingState sappia esattamente cosa fare (o non fare) per ogni azione. Non c’è nessuno switch nello stato: il polimorfismo gestisce tutto.Scenario 2: workflow e gestione dei processiUn caso d’uso classico è la gestione di una domanda con audit trail integrato:public interface IApplicationState {     string StatusName { get; }     void Review(ApplicationContext context);     void Approve(ApplicationContext context);     void Reject(ApplicationContext context);     void RequestInfo(ApplicationContext context); }  public sealed class ApplicationContext {     public IApplicationState CurrentState { get; private set; }     public string ApplicantName { get; }     public List&lt;string&gt; AuditLog { get; } = new();      public ApplicationContext(string applicantName)     {         ApplicantName = applicantName;         CurrentState = new SubmittedState();         Log("Domanda inviata");     }      public void TransitionTo(IApplicationState state)     {         Log($"Transizione a {state.StatusName}");         CurrentState = state;     }      public void Log(string message) =&gt;         AuditLog.Add($"[{DateTime.UtcNow:u}] {message}"); }L’audit trail viene aggiornato automaticamente a ogni transizione, senza duplicazione di codice nei metodi chiamanti.Scenario 3: gestione dello stato di un personaggio in un giocoI giochi sono un esempio naturale: un personaggio che alterna tra IdleState, RunningState, AttackingState e DyingState beneficia enormemente di questo pattern, poiché ciascuno stato ha logica di input e di update completamente diversa.Quando NON usare lo State PatternIl pattern non è sempre la risposta giusta. Evitate di applicarlo nei seguenti casi:Stati booleani semplici: se l’oggetto ha solo attivo e inattivo, un campo booleano è più chiaro e diretto.Pochi stati senza transizioni significative: se avete due o tre stati con poco comportamento differenziato, gli enum bastano.La logica di transizione è esterna all’oggetto: se le decisioni di cambio stato appartengono a un orchestratore esterno, il pattern State aggiunge complessità senza benefici.State Pattern vs alternative: quando scegliere cosaUn enum con un switch è la scelta giusta quando gli stati sono pochi, stabili e il comportamento differisce solo su una o due dimensioni. Appena gli stati crescono, il comportamento diverge significativamente per metodo, o le transizioni diventano complesse, è il momento di passare al pattern State.Il pattern State non è la stessa cosa del pattern Strategy: Strategy cambia l’algoritmo usato per una singola operazione, mentre State cambia il comportamento complessivo dell’oggetto al variare della condizione interna. Possono tuttavia lavorare insieme: una transizione di stato può emettere eventi che degli Observer gestiscono.Integrazione con Dependency InjectionUna domanda comune è se il pattern State si integri bene con la DI di ASP.NET Core. La risposta è sì, con qualche accorgimento: le classi stato concrete possono essere registrate nel contenitore DI, ma è consigliabile usare factory o ActivatorUtilities.CreateInstance per creare le istanze in modo da evitare cicli nel contenitore.ConclusioneLo State Pattern in C# risolve un problema preciso: oggetti il cui comportamento cambia radicalmente al variare dello stato interno, con transizioni complesse e comportamento specifico per stato. Prima di applicarlo, fate questa verifica rapida: contate quante volte controllate lo stesso campo di stato in metodi diversi. Se la risposta supera tre o quattro, probabilmente il pattern vi risparmierà mesi di manutenzione futura.La regola d’oro rimane: preferite sempre la soluzione più semplice che risolve il problema. Un enum con uno switch è più leggibile di una gerarchia di classi per casi banali. Ma quando la complessità cresce, lo State Pattern offre un’architettura che scala senza sforzo.Fonte originale: When to Use State Pattern in C#: Decision Guide with Examples — Dev Leader]]></description><link>https://forum.androidiani.net/topic/8b11c7c5-9756-4b50-acf6-984e1c99b5ae/state-pattern-in-c-guida-decisionale-con-esempi-pratici</link><guid isPermaLink="true">https://forum.androidiani.net/topic/8b11c7c5-9756-4b50-acf6-984e1c99b5ae/state-pattern-in-c-guida-decisionale-con-esempi-pratici</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Sun, 26 Apr 2026 18:13:43 GMT</pubDate></item><item><title><![CDATA[Classificazione documenti in C# senza AI: approccio deterministico, spiegabile e pronto per la produzione]]></title><description><![CDATA[Classificare automaticamente i documenti aziendali è uno di quei problemi che, a prima vista, sembra un caso d’uso ideale per i modelli AI. Ma in ambienti di produzione, la stabilità, la tracciabilità e la prevedibilità del comportamento spesso contano più della flessibilità. In questo articolo vediamo come implementare un classificatore di documenti rule-based, ponderato e completamente spiegabile in C# .NET, senza toccare un singolo modello di machine learning.Perché non partire dall’AI?I modelli AI per la classificazione di testo sono potenti, ma introducono una serie di criticità in contesti enterprise:Non-determinismo: lo stesso documento può ricevere classificazioni diverse a seconda della versione del modello, del wording del prompt o di aggiornamenti interni del provider.Opacità: spiegare a un responsabile compliance perché il modello ha classificato un contratto come “fattura” è praticamente impossibile.Dipendenza da pipeline di dati: aggiornare il classificatore richiede raccolta di dati, rietichettatura, riaddestramento e deployment.Un approccio deterministico e rule-based risolve tutti e tre i problemi: stesso input, stesso output, sempre. Ogni decisione è tracciabile e modificabile senza toccare il codice, solo aggiornando un file di configurazione JSON.Architettura del classificatoreIl sistema segue una pipeline chiara:Caricamento dei profili di classificazione da file JSONApertura del documento .docx con TX Text ControlEstrazione del testo da corpo, intestazioni e piè di paginaRilevamento delle regioni strutturali (titolo, heading, corpo, header, footer)Matching delle regole per categoria con strategie configurabiliCalcolo degli score ponderati per categoriaRestituzione della categoria vincente con confidence score e spiegazione dettagliataL’elemento chiave è che tutta la logica di classificazione vive nel file JSON, non nel codice. Questo significa che un domain expert (non un developer) può modificare e migliorare il classificatore semplicemente editando la configurazione.Il file di configurazioneOgni categoria è descritta da un insieme di regole. Ogni regola specifica:term: il termine o la frase da cercareweight: il peso del contributo di questa regola allo scorematchMode: la strategia di matching (Phrase, WholeWord, Contains)strength: la forza del segnale (Strong, Weak)Esempio per la categoria “Resume”:{   "name": "Resume",   "rules": [     {       "term": "work experience",       "weight": 3.0,       "matchMode": "Phrase",       "strength": "Strong"     },     {       "term": "email",       "weight": 1.0,       "matchMode": "WholeWord",       "strength": "Weak"     }   ] }La distinzione tra segnali forti e deboli è cruciale. “Work experience” in un documento è un indicatore molto specifico di un CV, mentre “email” può apparire praticamente ovunque e deve pesare di meno. Questa granularità evita i falsi positivi che affliggono i classifier naïve basati su semplice keyword counting.Estrazione strutturata con TX Text ControlIl classificatore non tratta il documento come un blocco di testo piatto. Usando TX Text Control .NET Server, estrae il contenuto per regioni strutturali:using var textControl = new ServerTextControl(); textControl.Create(); textControl.Load(docxPath, StreamType.WordprocessingML);Vengono estratti separatamente:textControl.Paragraphs → testo del corpotextControl.Sections → HeadersAndFooters → intestazioni e piè di pagina di ogni sezioneQuesta distinzione è fondamentale: nelle fatture, i termini identificativi come “FATTURA N.” appaiono tipicamente all’inizio del documento o nel titolo. Nei report, il tipo di documento è spesso incorporato nell’header. Ignorare queste regioni significherebbe perdere segnali classificatori di primo livello.Structure Awareness: non tutto il testo vale ugualeIl miglioramento più significativo rispetto al semplice keyword matching è la consapevolezza della struttura. Il classificatore assegna pesi diversi agli stessi termini a seconda della regione in cui appaiono:Un termine nel titolo del documento ha peso massimo: è quasi certamente indicativo del tipo di documentoUn termine in un heading H1/H2 ha peso altoLo stesso termine nel corpo del documento ha peso standardNel footer (tipicamente template boilerplate) il peso è ridottoQuesto approccio riflette come un essere umano leggerebbe effettivamente il documento: prima si guarda il titolo, poi le intestazioni principali, infine il corpo.Scoring, confidence e spiegabilitàAl termine dell’analisi, il sistema restituisce non solo la categoria vincente, ma anche:Il confidence score (rapporto tra lo score della categoria vincente e la somma degli score di tutte le categorie)Una spiegazione dettagliata: quali regole hanno fatto match, in quale regione, con quale pesoQuesta tracciabilità è essenziale per scenari di audit e compliance. Se un documento viene classificato erroneamente, il problema è sempre identificabile e correggibile: si modifica la regola nel JSON e si riclassifica. Nessun riaddestramento, nessuna nuova pipeline di dati.Quando usare questo approccio (e quando no)L’approccio deterministico è ottimale quando:Il set di categorie è definito e stabile (fatture, contratti, report, CV, ecc.)La compliance e l’auditability sono requisiti primariIl volume di documenti è alto e la velocità di classificazione è criticaNon si dispone di dataset etichettati sufficienti per addestrare un modelloDove l’AI rimane superiore è nei casi con categorie ambigue, linguaggio naturale molto variabile, o quando il set di categorie evolve rapidamente e non si vuole aggiornare manualmente le regole. Un’architettura ibrida – classificazione rule-based come primo filtro, AI solo per i casi borderline – è spesso la soluzione migliore in produzione.ConclusioneLa classificazione documentale senza AI non è un’alternativa di ripiego: è una scelta ingegneristica deliberata per sistemi che richiedono stabilità, spiegabilità e controllo. Il pattern rule-based, ponderato e configuration-driven descritto in questo articolo è già in uso in ambienti di produzione enterprise e offre vantaggi concreti in termini di manutenibilità e trasparenza.Se il vostro stack include la gestione di documenti .docx in C#, questo approccio vale la pena di essere valutato prima di introdurre la complessità di un modello ML.Fonte: Document Classification Without AI: Deterministic, Explainable, and Built for Production in C# .NET – Bjoern Meyer, TX Text Control Blog]]></description><link>https://forum.androidiani.net/topic/0aa5c73a-f7b6-4d82-9d50-53d0272001cd/classificazione-documenti-in-c-senza-ai-approccio-deterministico-spiegabile-e-pronto-per-la-produzione</link><guid isPermaLink="true">https://forum.androidiani.net/topic/0aa5c73a-f7b6-4d82-9d50-53d0272001cd/classificazione-documenti-in-c-senza-ai-approccio-deterministico-spiegabile-e-pronto-per-la-produzione</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Sun, 26 Apr 2026 10:08:16 GMT</pubDate></item><item><title><![CDATA[.NET 10 su Ubuntu 26.04 “Resolute Raccoon”: installazione, container e Native AOT]]></title><description><![CDATA[Ubuntu 26.04 LTS, nome in codice Resolute Raccoon, è disponibile e porta con sé una delle novità più attese per gli sviluppatori .NET su Linux: .NET 10 è il runtime ufficiale incluso nel repository standard. In questo articolo esploriamo come installare .NET 10, come aggiornare le immagini container esistenti e come sfruttare Native AOT per ottenere binari ultra-compatti con avvio in pochi millisecondi.Perché .NET e Ubuntu insiemeLa collaborazione tra Microsoft e Canonical non è nuova: ogni nuovo Ubuntu LTS porta con sé l’ultimo .NET LTS come toolchain ufficialmente supportata. Ubuntu 26.04 non fa eccezione: .NET 10 è direttamente installabile via APT senza configurare PPA aggiuntive. Per chi lavora in ambienti enterprise o vuole un’infrastruttura stabile e aggiornabile tramite il gestore pacchetti di sistema, questo è un vantaggio non trascurabile.È comunque possibile installare anche .NET 8 e .NET 9 tramite PPA dedicata, per chi ha applicazioni su versioni precedenti.Installazione rapidaL’installazione di .NET 10 su Ubuntu 26.04 è immediata:sudo apt update sudo apt install dotnet-sdk-10.0Nessun repository aggiuntivo, nessuna chiave GPG da configurare manualmente. Il package manager si occupa di tutto. Per verificare la versione installata:dotnet --version # 10.0.105Eseguire C# direttamente da stdinUna delle funzionalità meno note ma molto utile per script e automazione è la possibilità di passare codice C# direttamente a dotnet run via stdin, usando i file-based apps:dotnet run - &lt;&lt; 'EOF' using System.Runtime.InteropServices; Console.WriteLine($"Hello {RuntimeInformation.OSDescription} from .NET {RuntimeInformation.FrameworkDescription}"); EOF # Hello Ubuntu Resolute Raccoon from .NET .NET 10.0.5Questo pattern è particolarmente utile negli script di sistema e nei workflow CI/CD dove si vuole eseguire logica .NET senza creare un progetto completo.Novità rilevanti di Ubuntu 26.04 per .NETUbuntu 26.04 introduce tre cambiamenti che impattano direttamente gli stack .NET in produzione:Linux 7.0: il team .NET avvierà test su questo kernel non appena disponibili VM nel laboratorio. Le prime build sono già in CI.Post-Quantum Cryptography: Ubuntu 26.04 spinge su questo fronte e .NET 10 include già il supporto agli algoritmi post-quantum, quindi la compatibilità è garantita.Rimozione di cgroup v1: nessun problema per .NET, che supporta cgroup v2 da diversi anni. Tuttavia, chi usa container con immagini molto datate o configurazioni cgroup v1 dovrà verificare la compatibilità.Container: aggiornare da noble a resoluteLe immagini ufficiali per .NET 10 sono già disponibili con il tag resolute. Aggiornare un Dockerfile esistente è questione di un semplice sed:sed -i "s/noble/resolute/g" Dockerfile.chiseledEsempio di build e avvio con limiti di risorse:docker build --pull -t aspnetapp -f Dockerfile.chiseled . docker run --rm -it -p 8000:8080 -m 50mb --cpus .5 aspnetappLe varianti Chiseled (immagini minimali senza shell e strumenti non necessari) sono disponibili anche per resolute, con le stesse caratteristiche di sicurezza della versione noble.Nota importante: i container ereditano il kernel dell’host. Un container resolute su un host Ubuntu 24.04 userà il kernel 6.x dell’host, non Linux 7.0. Tenere presente questa distinzione in fase di planning.Native AOT: binari compatti e avvio in 3msNative AOT (NAOT) è una delle funzionalità più potenti di .NET 10 per scenari server e CLI. Su Ubuntu 26.04, il pacchetto dedicato è dotnet-sdk-aot-10.0:apt install -y dotnet-sdk-aot-10.0 clangPubblicando una semplice applicazione console come NAOT si ottiene un binario da circa 1.4 MB, pronto all’esecuzione senza runtime installato:dotnet publish app.cs du -h artifacts/app/* # 1.4M  artifacts/app/app # 3.0M  artifacts/app/app.dbgLe performance di avvio sono notevoli:time ./artifacts/app/app # real 0m0.003s3 millisecondi. Per confronto, un’applicazione .NET classica JIT può richiedere 100-500ms di warm-up in scenari tipici. Native AOT è la scelta ideale per CLI tools, Lambda functions, microservizi ad avvio freddo e sidecar container.Per applicazioni web, lo stesso approccio funziona con &lt;PublishAot&gt;true&lt;/PublishAot&gt; nel .csproj:dotnet publish # Produce: releasesapi (13MB) + releasesapi.dbg (32MB)Considerazioni pratiche per il team di sviluppoPer chi gestisce pipeline CI/CD con Ubuntu, questo rilascio semplifica notevolmente la gestione delle dipendenze: non è più necessario configurare feed Microsoft o repository aggiuntivi per .NET 10. L’intero stack è aggiornabile tramite apt upgrade come qualsiasi altro pacchetto di sistema.Per i team che usano container come base di sviluppo standardizzata, aggiornare il tag da -noble a -resolute nei Dockerfile è sufficiente per passare alla nuova LTS. È comunque raccomandato verificare la compatibilità con la propria configurazione cgroup se si usano orchestratori come Kubernetes con configurazioni custom.ConclusioneUbuntu 26.04 LTS consolida ulteriormente la posizione di Linux come piattaforma di prima classe per .NET. L’integrazione diretta nel repository APT, il supporto alle immagini Chiseled, la compatibilità post-quantum e le performance eccezionali di Native AOT fanno di questo rilascio un upgrade significativo per chiunque sviluppi o distribuisca applicazioni .NET su Linux.Fonte: What’s new for .NET in Ubuntu 26.04 – Richard Lander, Microsoft .NET Blog]]></description><link>https://forum.androidiani.net/topic/f98e3a50-3d23-4e5b-b49e-189e51e14dc9/.net-10-su-ubuntu-26.04-resolute-raccoon-installazione-container-e-native-aot</link><guid isPermaLink="true">https://forum.androidiani.net/topic/f98e3a50-3d23-4e5b-b49e-189e51e14dc9/.net-10-su-ubuntu-26.04-resolute-raccoon-installazione-container-e-native-aot</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Sat, 25 Apr 2026 19:12:54 GMT</pubDate></item><item><title><![CDATA[Azure Developer CLI: scrivere gli hook in Python, TypeScript e .NET]]></title><description><![CDATA[L’Azure Developer CLI (azd) è lo strumento open-source di Microsoft pensato per accompagnare lo sviluppatore dall’ambiente locale fino al deployment su Azure. Tra le sue funzionalità più apprezzate, il sistema degli hook permette di iniettare logica personalizzata nei punti chiave del ciclo di vita: prima del provisioning, dopo il deployment, e così via. Fino a poco tempo fa, però, questa logica doveva essere scritta esclusivamente in Bash o PowerShell, costringendo chi lavora in Python o TypeScript a un cambio di contesto non sempre gradito.Con il rilascio più recente di azd, questo limite è stato rimosso: gli hook possono ora essere scritti in Python, JavaScript, TypeScript e .NET, oltre ai già supportati Bash e PowerShell. La selezione del linguaggio avviene automaticamente in base all’estensione del file, senza configurazione aggiuntiva.Cosa sono gli hook in azdGli hook sono script eseguiti automaticamente da azd in corrispondenza di eventi specifici nel workflow:preprovision: prima che venga eseguito il provisioning dell’infrastrutturapostprovision: dopo il provisioningpredeploy / postdeploy: prima e dopo il deployment dell’applicazioneSi definiscono nel file azure.yaml con il blocco hooks:, specificando il percorso allo script da eseguire. azd si occupa autonomamente di rilevare il runtime appropriato, installare le dipendenze e lanciare lo script.Come usare gli hook in PythonPer un hook Python, è sufficiente creare il file .py e, nella stessa directory o in una directory padre, un file requirements.txt o pyproject.toml. azd individuerà automaticamente il file di dipendenze, creerà un virtual environment e installerà i pacchetti necessari prima di eseguire lo script.Struttura tipica:hooks/
├── setup.py
└── requirements.txt
Configurazione in azure.yaml:hooks:
  preprovision:
    run: ./hooks/setup.py
È possibile personalizzare il nome del virtual environment tramite il blocco config:hooks:
  preprovision:
    run: ./hooks/setup.py
    config:
      virtualEnvName: .venv
Hook in JavaScript e TypeScriptPer gli hook JavaScript e TypeScript, azd cerca un file package.json nella stessa directory o in una directory padre. Esegue automaticamente npm install (o il package manager specificato nella configurazione) e lancia lo script.Per TypeScript, la novità più interessante è che non serve un tsconfig.json né una fase di compilazione separata: azd utilizza npx tsx per eseguire il file TypeScript direttamente.hooks/
├── seed.ts
└── package.json
hooks:
  postdeploy:
    run: ./hooks/seed.ts
    config:
      packageManager: pnpm   # npm | pnpm | yarn
Hook in .NET e C#Per i progetti .NET sono supportate due modalità distinte:Project mode: se nella directory dello script (o in una padre) è presente un file .csproj, .fsproj o .vbproj, azd esegue automaticamente dotnet restore e dotnet build.Single-file mode: a partire da .NET 10, i file .cs standalone vengono eseguiti direttamente con dotnet run script.cs, senza necessità di un progetto.hooks/
├── migrate.cs
└── migrate.csproj   # opzionale su .NET 10+
hooks:
  postprovision:
    run: ./hooks/migrate.cs
    config:
      configuration: Release   # Debug | Release
      framework: net10.0
Funzionalità avanzateOverride della directory di lavoroSe la root del progetto e la posizione dello script differiscono, si può usare il campo dir per specificare la working directory:hooks:
  preprovision:
    run: main.py
    dir: hooks/preprovision
Override esplicito del linguaggioSe l’estensione è assente o ambigua, è possibile forzare il runtime con il campo kind:hooks:
  preprovision:
    run: ./hooks/setup
    kind: python
Formato misto e override per piattaformaSi possono combinare hook in linguaggi diversi e specificare script differenti per Windows e sistemi POSIX:hooks:
  preprovision:
    run: ./hooks/setup.py
  predeploy:
    windows:
      run: ./hooks/build.ps1
    posix:
      run: ./hooks/build.sh
  postdeploy:
    run: ./hooks/seed.ts
  postprovision:
    run: ./hooks/migrate.cs
Come aggiornare azdPer assicurarsi di avere questa funzionalità disponibile, è sufficiente aggiornare azd all’ultima versione:azd update
Per una nuova installazione, è possibile seguire la guida ufficiale di installazione.ConclusioniIl supporto multi-linguaggio per gli hook di azd rappresenta un miglioramento concreto per i team che lavorano con stack tecnologici eterogenei. Non dover più mantenere script shell separati per la logica di deployment è un risparmio reale di complessità, soprattutto nei progetti .NET o Python dove gran parte della base di codice esistente può essere riutilizzata direttamente negli hook.La gestione automatica delle dipendenze (virtual env per Python, npm install per JS/TS, dotnet restore per .NET) elimina ulteriore boilerplate, rendendo l’integrazione trasparente. Chi già usa azd nel proprio workflow troverà questa novità immediatamente utile; chi non lo ha ancora esplorato può partire dalla documentazione ufficiale e dalla galleria di template.Fonte: Write azd hooks in Python, JavaScript, TypeScript, or .NET – Azure SDK Blog]]></description><link>https://forum.androidiani.net/topic/673b6796-781d-4080-b8f9-f5996d4b7836/azure-developer-cli-scrivere-gli-hook-in-python-typescript-e-.net</link><guid isPermaLink="true">https://forum.androidiani.net/topic/673b6796-781d-4080-b8f9-f5996d4b7836/azure-developer-cli-scrivere-gli-hook-in-python-typescript-e-.net</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Fri, 24 Apr 2026 09:03:20 GMT</pubDate></item><item><title><![CDATA[Addio byte[]: allocazioni a costo zero in .NET Framework con  ReadOnlySpan]]></title><description><![CDATA[Uno dei pattern di ottimizzazione più semplici e meno conosciuti nel mondo .NET è la sostituzione dei campi static readonly byte[] con proprietà static ReadOnlySpan&lt;byte&gt;. Andrew Lock, noto per le sue analisi approfondite su ASP.NET Core e il runtime, ha pubblicato un articolo che conferma un dettaglio fondamentale: questa tecnica funziona anche su .NET Framework, basta il pacchetto NuGet System.Memory. Zero allocazioni, zero costo di startup, nessuna pressione sul garbage collector.Il problema: allocazioni “gratuite” che non lo sonoConsideriamo un pattern che troviamo in quasi tutte le librerie che manipolano dati binari: signature di file, magic number, header fissi, tabelle di lookup. Tipicamente si scrive:public static class MyStaticData {     private static readonly byte[] ByteField = new byte[] { 1, 2, 3, 4 }; }Sembra innocuo: un singolo array, allocato una volta sola al caricamento del tipo. Ma in un processo con migliaia di tipi simili — pensiamo a un parser di formati immagine, a una libreria di crittografia, a un framework web — queste allocazioni si sommano. Ogni array è un oggetto gestito: richiede header, richiede tracciamento GC, occupa spazio sulla Gen 2 (perché sopravvive per sempre) e aumenta i tempi di startup.La soluzione: ReadOnlySpan&lt;byte&gt; come proprietàLa trasformazione è quasi meccanica:public static class MyStaticData {     private static ReadOnlySpan&lt;byte&gt; ReadOnlySpanProp =&gt; new byte[] { 1, 2, 3, 4 }; }Sintatticamente sembra che stiamo allocando un array ogni volta che accediamo alla proprietà. In realtà è esattamente il contrario: il compilatore C# riconosce questo pattern e incorpora i byte direttamente nei metadati dell’assembly, costruendo lo span con un puntatore a quei dati. Non viene mai eseguito newarr.L’IL generato mostra chiaramente la magia:IL_0000: ldsflda      int32 '&lt;PrivateImplementationDetails&gt;'::'...' IL_0005: ldc.i4.4 IL_0006: newobj       instance void valuetype [System.Memory]System.ReadOnlySpan`1&lt;unsigned int8&gt;::.ctor(void*, int32) IL_000b: retI dati vivono in una sezione di sola lettura dell’assembly; lo span viene costruito on-the-fly con pointer + length. È essenzialmente gratuito.Letterali UTF-8: lo stesso trucco, più ergonomicoA partire da C# 11 (.NET 7), la stessa ottimizzazione si ottiene con i letterali UTF-8:private static ReadOnlySpan&lt;byte&gt; Utf8Hello =&gt; "Hello world"u8;Il suffisso u8 istruisce il compilatore a codificare la stringa direttamente in UTF-8 nell’assembly. Molto utile per header HTTP, prefissi di protocollo, marker di formato binari — tutti casi in cui storicamente si manteneva una byte[] statica generata da Encoding.UTF8.GetBytes.I vincoli da rispettareL’ottimizzazione non si applica in modo uniforme. Vale solo per i tipi a byte singolo:byte[]sbyte[]bool[]Per gli altri tipi primitivi (int, long, double…) entra in gioco l’endianness: su .NET 7 e successivi c’è RuntimeHelpers.CreateSpan&lt;T&gt;() che la gestisce in modo trasparente, ma su .NET Framework il compilatore emette codice che cache l’array in un campo statico alla prima chiamata. Ancora efficiente, ma non zero-alloc.Il secondo vincolo è che tutti i valori devono essere costanti a compile-time:// Anti-pattern: alloca a ogni accesso private static readonly byte One = 1; private static ReadOnlySpan&lt;byte&gt; Bad =&gt; new byte[] { One, 2, 3, 4 };Qui One è un campo, non una costante, quindi il compilatore deve costruire l’array a runtime. La differenza tra const byte e static readonly byte diventa improvvisamente importante.Il terzo vincolo è usare ReadOnlySpan&lt;T&gt;, mai Span&lt;T&gt;:// Sbagliato: alloca un array mutabile a ogni accesso private static Span&lt;byte&gt; MutSpan =&gt; new byte[] { 1, 2, 3, 4 };Uno Span&lt;byte&gt; potrebbe essere scritto, e modificare dati immutabili condivisi sarebbe catastrofico. Il compilatore quindi non applica l’ottimizzazione.Il supporto su .NET FrameworkQuesta è la parte più interessante: il trucco funziona su .NET Framework 4.6.2+ semplicemente referenziando il pacchetto System.Memory:&lt;ItemGroup&gt;   &lt;PackageReference Include="System.Memory" Version="4.6.3" /&gt; &lt;/ItemGroup&gt;La ragione è che l’ottimizzazione è una feature del compilatore, non del runtime: serve solo che ReadOnlySpan&lt;T&gt; esista come tipo, e il pacchetto System.Memory lo fornisce. Chi mantiene librerie multi-target può quindi applicare questa ottimizzazione senza creare codice condizionale #if NET6_0_OR_GREATER.Collection expressions: la rete di sicurezzaSu C# 12 e successivi le collection expressions offrono protezione a compile-time:// Compila e non alloca private static ReadOnlySpan&lt;byte&gt; Safe =&gt; [1, 2, 3, 4];  // Errore CS9203 — il compilatore rifiuta private static Span&lt;byte&gt; Dangerous =&gt; [1, 2, 3, 4];L’errore CS9203 è un salvavita: impedisce di assegnare una collection expression a un tipo Span&lt;T&gt; in contesti static, perché il risultato sarebbe condivisibile e mutabile. Su .NET Framework o su versioni di C# precedenti questa protezione non esiste, quindi serve attenzione in fase di code review.Quando applicarla nel codice realeLe candidate ideali sono costanti binarie che vivono in campi static readonly byte[]: magic number (PNG, ZIP, PDF), prefissi protocollari, tabelle di sostituzione, chiavi di test fisse, certificati embedded. Il refactoring è meccanico e non cambia l’API pubblica della classe se la visibility è private.Attenzione invece ai metodi che accettano byte[]: non possiamo passare uno ReadOnlySpan&lt;byte&gt; a un’API che richiede un array. In questi casi la scelta è tra riscrivere il consumer per accettare ReadOnlySpan&lt;byte&gt; (preferibile) o mantenere l’array tradizionale. Molte API del BCL sono già state aggiornate negli ultimi anni: Stream.Write, HashAlgorithm.ComputeHash, Encoding.GetString accettano tutti ReadOnlySpan&lt;byte&gt; in overload moderni.ConclusioneCambiare static readonly byte[] in static ReadOnlySpan&lt;byte&gt; =&gt; è uno di quei refactoring che riducono allocazioni e startup con una modifica locale a costo zero. Funziona anche su .NET Framework, quindi vale la pena considerarla durante la manutenzione di codice legacy — un punto che spesso sfugge perché l’ecosistema associa Span&lt;T&gt; esclusivamente a .NET moderno.Fonte: Removing byte[] allocations in .NET Framework using ReadOnlySpan&lt;T&gt; di Andrew Lock.]]></description><link>https://forum.androidiani.net/topic/9882bbb4-d010-4a5e-a02d-eca46c97ec7b/addio-byte-allocazioni-a-costo-zero-in-.net-framework-con-readonlyspan</link><guid isPermaLink="true">https://forum.androidiani.net/topic/9882bbb4-d010-4a5e-a02d-eca46c97ec7b/addio-byte-allocazioni-a-costo-zero-in-.net-framework-con-readonlyspan</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Thu, 23 Apr 2026 15:01:47 GMT</pubDate></item><item><title><![CDATA[TypeScript 7.0 Beta: il nuovo compilatore in Go è circa 10 volte più veloce]]></title><description><![CDATA[Il team di TypeScript ha rilasciato la beta ufficiale di TypeScript 7.0, e non si tratta di un aggiornamento incrementale: il compilatore è stato riscritto in Go, con miglioramenti di performance che in molti scenari superano un fattore 10x. Dopo quasi un anno di anteprime tecniche sotto il nome TypeScript Native Preview, Microsoft porta la versione nativa del compilatore a un pubblico molto più ampio e la raccomanda per uso quotidiano, pur restando formalmente in beta.Perché riscrivere il compilatore in GoIl compilatore di TypeScript era storicamente scritto nello stesso linguaggio che compilava. Questa scelta, elegante dal punto di vista del bootstrapping, ha sempre comportato un costo: su codebase di grandi dimensioni il tsc può impiegare decine di secondi (o minuti) per il type-checking e il watch mode si appesantisce rapidamente all’aumentare dei file.La riscrittura in Go non è un rewrite da zero: il team parla esplicitamente di un port metodico, mantenendo parità strutturale con la logica di type-checking di TypeScript 6.0. Questo approccio riduce il rischio di regressioni semantiche: la stessa base di casi di test, le stesse regole, ma con le velocità permesse da codice nativo e dal parallelismo reale a memoria condivisa.Il risultato, secondo Microsoft, è che TypeScript 7.0 è circa 10 volte più veloce di TypeScript 6.0. Team come Bloomberg, Figma, Google, Slack e Vercel hanno riportato numeri comparabili durante la beta privata, con riduzioni drastiche dei tempi di build in CI.Come provarlo oggiL’installazione avviene come package separato per non rompere le pipeline esistenti. Basta un singolo comando:npm install -D @typescript/native-preview@beta
npx tsgo --version
# Version 7.0.0-betaDurante la fase beta, l’eseguibile si chiama tsgo al posto di tsc. Per Visual Studio Code è disponibile l’estensione “TypeScript Native Preview”, che affianca il language service classico permettendo di confrontare i tempi di risposta in tempo reale.Parallelismo configurabileUna delle novità più sottili, ma con maggiore impatto pratico, è il parallelismo integrato nel compilatore:--checkers N: numero di worker dedicati al type-checking (default 4). I worker mantengono viste indipendenti per evitare ricalcoli ridondanti, ma i risultati restano deterministici.--builders N: abilita la compilazione parallela di più progetti referenziati (project references). Ha un effetto moltiplicativo quando combinato con --checkers, ed è particolarmente efficace nei monorepo.--singleThreaded: forza l’esecuzione sequenziale per debugging o ambienti con memoria limitata (container CI con poca RAM, ad esempio).Alzare --checkers aumenta la velocità ma anche il consumo di memoria: su agenti CI piccoli conviene fare qualche prova empirica prima di spingerlo oltre 8.Breaking changes: la pulizia annunciataTypeScript 7.0 è anche l’occasione per rimuovere anni di retrocompatibilità. Chi mantiene progetti legacy dovrà prestare attenzione, perché molte opzioni di configurazione sono semplicemente scomparse:target: es5 non è più supportato.downlevelIteration, moduleResolution: node/node10/classic, e i moduli amd, umd, systemjs, none sono stati rimossi.baseUrl è stato eliminato: usare paths relativo alla root del progetto.esModuleInterop, allowSyntheticDefaultImports e alwaysStrict non possono più essere disattivati.Cambiano anche diversi default: strict: true, module: esnext, target pari all’ultima versione ECMAScript stabile prima di esnext, noUncheckedSideEffectImports: true, e soprattutto types: []. Quest’ultimo è il cambiamento che più spesso romperà le build: prima @types/* venivano inclusi automaticamente, ora vanno dichiarati esplicitamente:{
  "compilerOptions": {
    "types": ["node", "jest"]
  }
}Sul fronte del supporto a JavaScript con JSDoc, la pulizia è ancora più netta: i valori non possono più sostituire i tipi (usare typeof valore), la sintassi Closure-style function(string): void è rimossa, così come @enum e l’operatore postfisso !.Convivenza con TypeScript 6.0Per chi non può migrare subito tutte le pipeline, è possibile installare entrambe le versioni affiancate:npm install -D typescript@npm:@typescript/typescript6Così typescript continua a puntare a 6.0, mentre tsgo (o tsc7 dopo il rilascio finale) resta disponibile come entry point separato. È lo scenario consigliato per confrontare gradualmente i due compilatori su progetti reali prima di fare il cutover.Roadmap e cosa aspettarsiLa beta è datata 21 aprile 2026; il rilascio stabile è previsto entro due mesi, con una release candidate alcune settimane prima. Nel frattempo arriveranno un --watch più efficiente, la parità di declaration file emit per JavaScript, miglioramenti all’editor (ricerca dei riferimenti ai file, comandi import/export più granulari) e una API programmatica stabile, attesa per TypeScript 7.1 o successiva.Vale la pena migrare subito?Per team che lavorano su codebase grandi e soffrono di type-check lenti, la risposta è “quasi sicuramente sì, almeno in parallelo”. Microsoft stessa dichiara il compilatore “altamente stabile e altamente compatibile” sulla base di test su codebase da milioni di righe. La strategia più prudente è: installare @typescript/native-preview come dev dependency aggiuntiva, introdurlo come job di CI opzionale accanto al tsc esistente, misurare i tempi reali e segnalare eventuali incompatibilità sul repository microsoft/typescript-go.Le incompatibilità che emergeranno non saranno di natura logica ma di configurazione: soprattutto il nuovo default types: [] e la rimozione di baseUrl. Chi si è tenuto aggiornato con le versioni recenti dovrebbe cavarsela con poche modifiche al tsconfig.json.Fonte: Announcing TypeScript 7.0 Beta di Daniel Rosenwasser sul blog ufficiale TypeScript (Microsoft DevBlogs).]]></description><link>https://forum.androidiani.net/topic/092e94ec-9f51-4970-971e-16884f220e56/typescript-7.0-beta-il-nuovo-compilatore-in-go-è-circa-10-volte-più-veloce</link><guid isPermaLink="true">https://forum.androidiani.net/topic/092e94ec-9f51-4970-971e-16884f220e56/typescript-7.0-beta-il-nuovo-compilatore-in-go-è-circa-10-volte-più-veloce</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Thu, 23 Apr 2026 07:54:20 GMT</pubDate></item><item><title><![CDATA[12 tecniche per ottimizzare le query PostgreSQL su dataset di grandi dimensioni]]></title><description><![CDATA[Quando una tabella PostgreSQL cresce oltre il milione di righe, query che prima restituivano risultati in millisecondi iniziano ad impiegare secondi — o peggio. La buona notizia è che PostgreSQL offre strumenti potenti per affrontare questo problema. La cattiva notizia è che molti sviluppatori conoscono solo una parte di questi strumenti.In questo articolo passiamo in rassegna le 12 tecniche più efficaci per ottimizzare le query su grandi dataset, con esempi SQL concreti per ciascuna.1. Creare indici sulle colonne frequentemente filtrateIl consiglio più noto, ma non per questo meno importante. Un indice trasforma una scansione sequenziale (O(n)) in una ricerca B-tree (O(log n)). La differenza su una tabella da un milione di righe può essere di due ordini di grandezza.-- Prima: full sequential scan su ordini SELECT * FROM orders WHERE customer_id = 42;  -- Creazione dell'indice CREATE INDEX idx_orders_customer_id ON orders(customer_id);  -- Dopo: index scan, da 240ms a pochi msUsate EXPLAIN ANALYZE per verificare che l’indice venga effettivamente utilizzato.2. Normalizzare il database in modo strategicoLa normalizzazione riduce la ridondanza e migliora la coerenza dei dati, ma va applicata con giudizio. Una normalizzazione eccessiva crea decine di JOIN che possono diventare colli di bottiglia. La regola pratica: normalizzate i dati che cambiano spesso o che hanno alta cardinalità (liste di prodotti, clienti, categorie), denormalizzate i dati storici o di report dove la velocità di lettura è critica.3. Evitare SELECT *Selezionare tutte le colonne ha due costi nascosti: aumenta il volume di I/O e impedisce a PostgreSQL di soddisfare la query direttamente dall’indice (index-only scan). Specificate sempre le colonne necessarie:-- Evitare SELECT * FROM orders WHERE customer_id = 42;  -- Preferire SELECT id, created_at, total_amount FROM orders WHERE customer_id = 42;Quando le colonne selezionate fanno parte di un indice composito, PostgreSQL può restituire i dati senza accedere all’heap, eliminando un intero livello di I/O.4. Ordinare i JOIN in modo efficienteIl query planner moderno di PostgreSQL determina autonomamente l’ordine ottimale dei JOIN grazie al cost-based optimizer. Tuttavia, in scenari con molte tabelle o con join_collapse_limit ridotto, conviene strutturare i JOIN in modo che le tabelle più piccole (o più filtrate) vengano processate per prime, riducendo la cardinalità delle operazioni successive.5. Usare LIMIT durante l’esplorazione dei datiApparentemente ovvio, ma spesso trascurato: se l’interfaccia utente mostra al massimo 50 risultati, non ha senso recuperarne un milione dal database.SELECT id, name, email  FROM customers  ORDER BY created_at DESC  LIMIT 50 OFFSET 0;Attenzione al pagination problem: con OFFSET elevati, PostgreSQL scansiona comunque tutte le righe precedenti. Per paginazione su grandi dataset, preferite il keyset pagination (cursor-based).6. Indici parziali per subset frequentiUn indice parziale indicizza solo le righe che soddisfano una condizione, riducendo dimensioni e costo di manutenzione:-- Indice solo sugli ordini completati (subset più frequentemente interrogato) CREATE INDEX idx_completed_orders ON orders(customer_id) WHERE status = 'Completed';  -- La query deve includere la stessa condizione per usare l'indice SELECT id, total_amount  FROM orders  WHERE customer_id = 42 AND status = 'Completed';In un test pratico, questo indice ha dimezzato i tempi rispetto a un indice standard su tutte le righe.7. Usare i tipi di dato più piccoli necessariOgni byte conta quando moltiplicato per milioni di righe. Preferite sempre il tipo più compatto che soddisfa il requisito:integer (4 byte) invece di bigint (8 byte) per chiavi primarie &lt; 2 miliardismallint (2 byte) per enumerazioni con pochi valoritimestamp invece di timestamptz se il fuso orario è fissovarchar(n) con limite appropriato invece di text illimitato dove possibileTipi più piccoli significano pagine di dati più dense, quindi meno I/O per ogni query.8. Non applicare funzioni sulle colonne indicizzateApplicare una funzione a una colonna indicizzata invalida l’utilizzo dell’indice:-- L'indice su name NON viene usato SELECT * FROM customers WHERE LOWER(name) = 'mario rossi';  -- Soluzione: creare un indice funzionale CREATE INDEX idx_customers_lower_name ON customers(LOWER(name));  -- Ora l'indice viene usato SELECT * FROM customers WHERE LOWER(name) = 'mario rossi';Lo stesso vale per funzioni su date come DATE(created_at): usate range di timestamp o create l’indice sulla funzione.9. Partizionare le tabelle molto grandiIl partizionamento divide una tabella logica in sotto-tabelle fisiche, permettendo a PostgreSQL di escludere partizioni irrilevanti (partition pruning) durante le query:-- Tabella partizionata per anno CREATE TABLE orders_partitioned (     id         serial NOT NULL,     customer_id integer,     created_at  timestamp NOT NULL,     CONSTRAINT pk_orders PRIMARY KEY (id, created_at) ) PARTITION BY RANGE (created_at);  -- Creazione delle partizioni annuali CREATE TABLE orders_2024 PARTITION OF orders_partitioned     FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');  CREATE TABLE orders_2025 PARTITION OF orders_partitioned     FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');Una query che filtra per anno legge solo la partizione corrispondente, ignorando completamente le altre.10. Usare le transazioni per operazioni bulkPostgreSQL esegue un commit (e quindi una scrittura sincrona su WAL) dopo ogni statement. Raggruppare più operazioni in un’unica transazione riduce drasticamente i costi di I/O:-- Lento: un commit per ogni INSERT INSERT INTO log_events VALUES (...); INSERT INTO log_events VALUES (...); -- ... x 10.000  -- Veloce: un solo commit per tutto il batch BEGIN; INSERT INTO log_events VALUES (...); INSERT INTO log_events VALUES (...); -- ... x 10.000 COMMIT;In test pratici, l’approccio con transazione singola completa lo stesso lavoro in meno della metà del tempo rispetto agli inserimenti individuali.11. Evitare transazioni long-runningIl modello MVCC (Multi-Version Concurrency Control) di PostgreSQL mantiene versioni multiple delle righe per garantire la consistenza delle letture. Le transazioni long-running bloccano il processo di VACUUM dal rimuovere le versioni obsolete, causando table bloat: tabelle che crescono fisicamente anche quando i dati logici non aumentano.Spezzettate le operazioni pesanti in batch più piccoli e monitorate le transazioni attive con:SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state FROM pg_stat_activity WHERE state != 'idle' AND query_start IS NOT NULL ORDER BY duration DESC;12. Gestire il bloat con VACUUMOgni UPDATE e DELETE lascia righe “morte” sul disco. VACUUM le recupera:-- VACUUM standard: recupera spazio senza bloccare le letture VACUUM orders;  -- VACUUM FULL: recupera tutto lo spazio ma blocca l'accesso alla tabella -- Usare solo in finestre di manutenzione programmate VACUUM FULL orders;  -- Verificare lo stato del bloat SELECT relname, n_dead_tup, n_live_tup,        round(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 2) AS dead_pct FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 20;Per la maggior parte dei workload, autovacuum è sufficiente. Assicuratevi che sia abilitato e calibrate i threshold in base al volume di modifiche della vostra applicazione:-- Verificare la configurazione autovacuum per una tabella specifica SELECT reloptions FROM pg_class WHERE relname = 'orders';Riepilogo operativoNon tutte le tecniche si applicano a ogni scenario. Un approccio efficace inizia sempre dall’analisi con EXPLAIN (ANALYZE, BUFFERS) per identificare i reali colli di bottiglia, poi applica le ottimizzazioni in modo mirato. L’indice sbagliato o il partizionamento mal configurato possono peggiorare le prestazioni invece di migliorarle.Il punto di partenza universale resta lo stesso: misurare prima, ottimizzare dopo.Fonte: 12 practices for optimizing PostgreSQL queries for large datasets — elmah.io Blog]]></description><link>https://forum.androidiani.net/topic/32794ed8-7496-404a-bdc8-836d65bc5f68/12-tecniche-per-ottimizzare-le-query-postgresql-su-dataset-di-grandi-dimensioni</link><guid isPermaLink="true">https://forum.androidiani.net/topic/32794ed8-7496-404a-bdc8-836d65bc5f68/12-tecniche-per-ottimizzare-le-query-postgresql-su-dataset-di-grandi-dimensioni</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Wed, 22 Apr 2026 15:21:43 GMT</pubDate></item><item><title><![CDATA[Creare addon nativi per Node.js con .NET Native AOT: addio a Python e node-gyp]]></title><description><![CDATA[Da sempre, creare addon nativi per Node.js significava entrare nel mondo di C++ e node-gyp, con la necessità di installare Python, Visual Studio Build Tools e una serie di dipendenze che trasformavano il setup dell’ambiente in un’impresa. Il team di C# Dev Kit di Microsoft ha trovato una soluzione elegante: usare .NET Native AOT per produrre librerie condivise compatibili con l’interfaccia N-API di Node.js, scritte interamente in C#.In questo articolo vediamo come funziona questa tecnica, analizzando la struttura del progetto, il meccanismo di interop e i punti critici da tenere d’occhio in produzione.Perché Node.js supporta addon scritti in qualsiasi linguaggioUn addon nativo per Node.js è semplicemente una libreria condivisa (.dll su Windows, .so su Linux, .dylib su macOS) che esporta un punto di ingresso preciso: la funzione napi_register_module_v1. Node.js carica la libreria, chiama questa funzione e da quel momento il modulo è disponibile per JavaScript.L’interfaccia che rende tutto questo possibile è N-API (Node-API), una API C stabile e ABI-compatibile tra le versioni di Node.js. Questo significa che qualsiasi linguaggio in grado di produrre una shared library ed esportare una funzione C può diventare un addon Node.js — incluso C# compilato con Native AOT.Configurazione del progetto .NETIl file di progetto è sorprendentemente minimale:&lt;Project Sdk="Microsoft.NET.Sdk"&gt;
  &lt;PropertyGroup&gt;
    &lt;TargetFramework&gt;net10.0&lt;/TargetFramework&gt;
    &lt;PublishAot&gt;true&lt;/PublishAot&gt;
    &lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;
  &lt;/PropertyGroup&gt;
&lt;/Project&gt;Due impostazioni chiave:PublishAot: abilita la compilazione Ahead-of-Time, producendo una shared library nativa invece di un assembly IL.AllowUnsafeBlocks: necessario per l’interop con N-API tramite function pointer e tipi non gestiti.Il punto di ingresso del moduloL’entry point usa l’attributo [UnmanagedCallersOnly], che istruisce il compilatore a generare una funzione C-callable con la firma esatta attesa da Node.js:[UnmanagedCallersOnly(
    EntryPoint = "napi_register_module_v1",
    CallConvs = [typeof(CallConvCdecl)])]
public static nint Init(nint env, nint exports)
{
    // Registrazione delle funzioni esposte
    return exports;
}Il tipo nint (native-sized integer) rappresenta gli handle opachi che N-API usa per riferirsi agli oggetti JavaScript. Non si tratta di puntatori diretti a memoria, ma di token gestiti dall’engine V8 tramite N-API.Risoluzione delle funzioni N-API a runtimeLe funzioni N-API (come napi_create_string_utf8 o napi_get_cb_info) sono esportate direttamente da node.exe, non da una DLL separata. Per fare in modo che P/Invoke le risolva correttamente, si registra un custom resolver:private static void Initialize()
{
    NativeLibrary.SetDllImportResolver(
        System.Reflection.Assembly.GetExecutingAssembly(),
        ResolveDllImport);
}

private static nint ResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
    if (libraryName == "node")
        return NativeLibrary.GetMainProgramHandle();
    return IntPtr.Zero;
}Questo permette di dichiarare le importazioni P/Invoke con [LibraryImport("node")] e averle risolte contro il processo host a runtime.Marshalling delle stringhe UTF-8Uno dei punti più delicati è la conversione tra stringhe JavaScript (UTF-16 internamente in V8, UTF-8 via N-API) e stringhe .NET. La strategia ottimale prevede:Uso dello stack per stringhe piccole (≤512 byte) tramite stackallocUso di ArrayPool&lt;byte&gt; per stringhe più grandi, evitando allocazioni sull’heapprivate static string GetStringArg(nint env, nint info, int argIndex)
{
    // Recupera l'handle dell'argomento
    nint value = GetArgument(env, info, argIndex);
    
    // Prima chiamata: ottieni la dimensione necessaria
    nuint byteCount;
    napi_get_value_string_utf8(env, value, null, 0, out byteCount);
    
    // Allocazione efficiente in base alla dimensione
    if (byteCount &lt;= 512)
    {
        Span&lt;byte&gt; buffer = stackalloc byte[(int)byteCount + 1];
        napi_get_value_string_utf8(env, value, buffer, (nuint)buffer.Length, out _);
        return Encoding.UTF8.GetString(buffer[..^1]);
    }
    else
    {
        byte[] buffer = ArrayPool&lt;byte&gt;.Shared.Rent((int)byteCount + 1);
        try
        {
            napi_get_value_string_utf8(env, value, buffer, (nuint)buffer.Length, out _);
            return Encoding.UTF8.GetString(buffer, 0, (int)byteCount);
        }
        finally
        {
            ArrayPool&lt;byte&gt;.Shared.Return(buffer);
        }
    }
}Implementazione di una funzione reale: lettura dal RegistryL’esempio concreto mostrato dal team di Microsoft è un lettore del Windows Registry, che sostituisce il precedente addon C++:private static nint ReadStringValue(nint env, nint info)
{
    try
    {
        var keyPath = GetStringArg(env, info, 0);
        var valueName = GetStringArg(env, info, 1);
        
        using var key = Registry.CurrentUser.OpenSubKey(keyPath, writable: false);
        
        return key?.GetValue(valueName) is string value
            ? CreateString(env, value)
            : GetUndefined(env);
    }
    catch (Exception ex)
    {
        // CRITICO: le eccezioni non gestite in [UnmanagedCallersOnly] crashano il processo
        ThrowError(env, $"Registry read failed: {ex.Message}");
        return 0;
    }
}Attenzione: in un metodo [UnmanagedCallersOnly], le eccezioni non gestite provocano il crash dell’intero processo Node.js. Il pattern try/catch con ThrowError trasforma l’eccezione .NET in un errore JavaScript, mantenendo stabile il runtime.Integrazione con TypeScriptDopo dotnet publish, il file prodotto viene rinominato con estensione .node (convenzione Node.js) e caricato normalmente da TypeScript:interface RegistryAddon {
    readStringValue(keyPath: string, valueName: string): string | undefined;
}

const registry = require('./native/win32-x64/RegistryAddon.node') as RegistryAddon;

const sdkPath = registry.readStringValue(
    'SOFTWARE\\dotnet\\Setup\\InstalledVersions\\x64\\sdk',
    'InstallLocation'
);
console.log(`SDK installato in: ${sdkPath}`);Limiti e considerazioniQuesta tecnica ha un limite importante: Native AOT non supporta la cross-compilazione. Per ogni piattaforma target (Windows x64, Linux x64, macOS ARM64…) è necessario un ambiente di build separato. In pratica, questo si risolve con pipeline CI che eseguono la build su runner del sistema operativo corrispondente.Esiste anche un’alternativa di più alto livello, node-api-dotnet, che astrae molti dei dettagli mostrati qui e supporta scenari più complessi come l’esposizione di interi namespace .NET a JavaScript. L’approccio “thin wrapper” descritto in questo articolo è preferibile quando si vuole controllo totale e dipendenze minime.ConclusioniL’integrazione tra .NET Native AOT e N-API apre uno scenario interessante per i team che già lavorano con C# e devono interfacciarsi con l’ecosistema Node.js. Eliminare Python e node-gyp dal setup semplifica notevolmente l’ambiente di sviluppo e unifica le competenze necessarie intorno a un unico SDK.Il risultato è codice nativo con prestazioni paragonabili al C++, scritto con la produttività e la type safety di C# moderno, deployabile su Windows, Linux e macOS.Fonte: Writing Node.js addons with .NET Native AOT — Microsoft .NET Blog, Drew Noakes]]></description><link>https://forum.androidiani.net/topic/a0a8ba76-389e-4f9d-a652-0b5029500441/creare-addon-nativi-per-node.js-con-.net-native-aot-addio-a-python-e-node-gyp</link><guid isPermaLink="true">https://forum.androidiani.net/topic/a0a8ba76-389e-4f9d-a652-0b5029500441/creare-addon-nativi-per-node.js-con-.net-native-aot-addio-a-python-e-node-gyp</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Wed, 22 Apr 2026 09:19:56 GMT</pubDate></item><item><title><![CDATA[Primary Constructor e Dependency Injection in C# 12: vantaggi, insidie e quando usarli]]></title><description><![CDATA[Con C# 12, Microsoft ha introdotto i primary constructors per tutte le classi e le struct — non solo per i record come in precedenza. Per chi lavora quotidianamente con ASP.NET Core e il pattern di dependency injection, questa feature merita attenzione: riduce il boilerplate in modo significativo, ma nasconde un'insidia che è bene conoscere prima di adottarla sistematicamente.Il problema: il boilerplate del costruttore classicoChi ha scritto servizi ASP.NET Core sa bene come appare il costruttore tradizionale con dependency injection:public class OrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger&lt;OrderService&gt; _logger;
    private readonly IEventBus _eventBus;
    private readonly IValidator&lt;Order&gt; _validator;

    public OrderService(
        IOrderRepository orderRepository,
        ILogger&lt;OrderService&gt; logger,
        IEventBus eventBus,
        IValidator&lt;Order&gt; validator)
    {
        _orderRepository = orderRepository;
        _logger = logger;
        _eventBus = eventBus;
        _validator = validator;
    }
}Per ogni dipendenza: una dichiarazione di campo, un parametro nel costruttore, un'assegnazione nel corpo. Con quattro dipendenze, sono già dodici righe di puro scaffolding. In un servizio con sei o sette dipendenze, il costruttore diventa il blocco di codice più lungo della classe — senza contenere logica.La soluzione con primary constructorsCon i primary constructors di C# 12, lo stesso servizio si scrive così:public class OrderService(
    IOrderRepository orderRepository,
    ILogger&lt;OrderService&gt; logger,
    IEventBus eventBus,
    IValidator&lt;Order&gt; validator)
{
    public async Task&lt;Order?&gt; GetOrderAsync(Guid id)
    {
        logger.LogInformation("Fetching order {OrderId}", id);
        return await orderRepository.GetByIdAsync(id);
    }

    public async Task PlaceOrderAsync(Order order)
    {
        await validator.ValidateAndThrowAsync(order);
        await orderRepository.SaveAsync(order);
        await eventBus.PublishAsync(new OrderPlacedEvent(order.Id));
    }
}I parametri del primary constructor diventano direttamente disponibili in tutta la classe senza dichiarazioni esplicite di campo. Il risultato è codice più snello, con meno rumore visivo e il focus immediato sulla logica di business.L'insidia della mutabilità: perché Milan Jovanović era inizialmente scetticoIl motivo di resistenza di molti sviluppatori esperti è legittimo: i parametri del primary constructor non sono campi readonly. Il compilatore non impedisce la riassegnazione accidentale:public class OrderService(IOrderRepository orderRepository)
{
    public void SomeMethod()
    {
        orderRepository = null!;  // Compila senza warning
    }
}Con i campi privati readonly tradizionali, questo codice causa un errore di compilazione. Con i primary constructors, il compilatore lo accetta silenziosamente. In un servizio DI dove le dipendenze non dovrebbero mai essere rimpiazzate a runtime, questo è un rischio concreto in team di grandi dimensioni.Mitigazione: assegnazione esplicita a readonly fieldQuando la garanzia di immutabilità è critica, si può assegnare esplicitamente il parametro a un campo readonly:public class CriticalService(IRepository repository)
{
    private readonly IRepository _repository = repository;

    // Da qui in poi si usa _repository, mai repository direttamente
}Questo reintroduce parte del boilerplate, ma mantiene la sintassi più compatta per la firma del costruttore e garantisce l'immutabilità a livello compilatore.Quando usare i primary constructorsLa valutazione pragmatica è che i primary constructors offrono il massimo vantaggio nelle classi di servizio DI tipiche di ASP.NET Core, dove i parametri vengono usati ma raramente riassegnati. In questi scenari, il rischio di mutabilità accidentale è basso e i benefici di leggibilità sono immediati.Vale la pena usarli anche per entity e value object dove i parametri di costruzione diventano proprietà read-only:public class Order(Guid customerId, Money total)
{
    public Guid Id { get; } = Guid.NewGuid();
    public Guid CustomerId { get; } = customerId;
    public Money Total { get; } = total;
    public DateTime CreatedAt { get; } = DateTime.UtcNow;
}Quando evitarliTre scenari in cui i primary constructors non sono la scelta giusta:Logica di validazione nel costruttore: se il costruttore deve eseguire guard clause o validazioni prima dell'assegnazione, il corpo esplicito del costruttore tradizionale è necessario.Multiple signature di costruttore: i primary constructors supportano una sola firma. Con overload multipli (es. per serializzazione), la sintassi tradizionale è l'unica opzione.Cinque o più dipendenze: se una classe richiede molte dipendenze, il problema non è la sintassi del costruttore ma il design della classe. Il segnale che suggerisce un refactoring verso interfacce più coese o il pattern Facade.Conclusione: adottarli con consapevolezzaI primary constructors di C# 12 non sono una rivoluzione, ma un'evoluzione pragmatica del linguaggio. Per la maggior parte delle classi di servizio in ASP.NET Core, il tradeoff è favorevole: meno boilerplate, codice più leggibile, rischio di mutabilità basso nel contesto DI. L'importante è conoscere il comportamento del compilatore e scegliere consapevolmente quando la garanzia di readonly è effettivamente necessaria.Come sempre con le feature di C#, il consiglio è adottarle dove migliorano la leggibilità del codice reale, non per seguire una moda sintattica.Fonte originale: Why I Switched to Primary Constructors for DI in C# di Milan Jovanović.]]></description><link>https://forum.androidiani.net/topic/ed9059e2-0ab3-4ef4-a625-36f90606443b/primary-constructor-e-dependency-injection-in-c-12-vantaggi-insidie-e-quando-usarli</link><guid isPermaLink="true">https://forum.androidiani.net/topic/ed9059e2-0ab3-4ef4-a625-36f90606443b/primary-constructor-e-dependency-injection-in-c-12-vantaggi-insidie-e-quando-usarli</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Tue, 21 Apr 2026 13:28:35 GMT</pubDate></item><item><title><![CDATA[Rust 1.95.0: cfg_select!, if-let guard nei match e nuove API per Vec e atomici]]></title><description><![CDATA[Il 16 aprile 2026 è uscita Rust 1.95.0, una release che porta novità significative al linguaggio e alla sua libreria standard. Questa versione si segnala in particolare per l’introduzione della macro cfg_select!, il supporto agli if-let guard nei blocchi match, e una serie di nuove API per la gestione della memoria e degli atomici.Vediamo nel dettaglio cosa cambia e come queste novità influenzano il codice quotidiano dei Rustaceans.cfg_select!: condizionali di compilazione senza dipendenze esterneLa macro cfg_select! introduce un modo più espressivo per scrivere codice condizionale basato sulla configurazione di compilazione. Si comporta come un match valutato a compile-time sulle condizioni cfg, rimpiazzando di fatto il crate esterno cfg-if che molti progetti Rust usano da anni.Prima di Rust 1.95, per scrivere codice dipendente dalla piattaforma si usava tipicamente:// Prima: con cfg-if (dipendenza esterna nel Cargo.toml) cfg_if::cfg_if! {     if #[cfg(target_os = "linux")] {         fn get_system_info() -&gt; String {             linux_system_info()         }     } else if #[cfg(target_os = "macos")] {         fn get_system_info() -&gt; String {             macos_system_info()         }     } else {         fn get_system_info() -&gt; String {             String::from("unknown")         }     } }Con cfg_select!, la stessa logica si esprime ora senza aggiungere dipendenze al progetto:// Ora: built-in nella libreria standard fn get_system_info() -&gt; String {     cfg_select! {         target_os = "linux" =&gt; { linux_system_info() }         target_os = "macos" =&gt; { macos_system_info() }         _ =&gt; { String::from("unknown") }     } }La sintassi è più pulita e il fatto di essere nella libreria standard elimina la necessità di dichiarare cfg-if nel Cargo.toml. Particolarmente utile per chi sviluppa librerie cross-platform o crate che devono supportare più sistemi operativi e architetture CPU.if-let guard nei match: pattern matching più espressivoRust 1.95 stabilizza gli if-let guard all’interno delle espressioni match, costruendo sulle let chain introdotte in Rust 1.88. Un guard in un match è la condizione if che può seguire un pattern. Finora, questa condizione doveva essere un’espressione booleana semplice. Ora si può usare if let per abbinare pattern aggiuntivi direttamente nella guardia:fn process(value: Option&lt;Result&lt;i32, String&gt;&gt;) {     match value {         Some(x) if let Ok(y) = x =&gt; {             println!("Valore ottenuto: {}", y);         }         Some(Err(ref e)) =&gt; {             println!("Errore: {}", e);         }         None =&gt; {             println!("Nessun valore");         }     } }Questo permette di scrivere logica di pattern matching più complessa senza ricorrere a blocchi match annidati. Un caso d’uso pratico è la validazione combinata di metodo HTTP e corpo della richiesta:fn handle_request(req: &amp;Request) -&gt; Response {     match req.method() {         Method::POST if let Ok(body) = serde_json::from_str::&lt;Payload&gt;(req.body()) =&gt; {             process_payload(body)         }         Method::POST =&gt; {             Response::bad_request("Payload JSON non valido")         }         _ =&gt; {             Response::method_not_allowed()         }     } }Nota importante sull’esaustività: il compilatore non tiene conto dei pattern negli if-let guard per il controllo di esaustività (exhaustiveness checking). Questo significa che il compilatore non avvertirà se un pattern nell’if-let guard è irraggiungibile. Occorre quindi prestare attenzione quando si usano questi costrutti in match che devono essere esaustivi per correttezza.Nuove API per Vec, VecDeque e atomicipush_mut e push_back_mut: riferimenti mutabili post-inserimentoUna delle aggiunte più pratiche riguarda i metodi push_mut e varianti per Vec e VecDeque. Questi metodi inseriscono un elemento e restituiscono immediatamente un riferimento mutabile all’elemento appena inserito, eliminando il doppio accesso che prima era necessario:// Prima: due operazioni separate let mut v: Vec&lt;String&gt; = Vec::new(); v.push(String::new()); let last = v.last_mut().unwrap(); // secondo accesso necessario last.push_str("hello");  // Ora con push_mut: un unico accesso, più efficiente let mut v: Vec&lt;String&gt; = Vec::new(); let elem = v.push_mut(String::new()); elem.push_str("hello"); // riferimento diretto all'elemento appena inseritoMetodi analoghi (push_back_mut, push_front_mut) sono disponibili anche su VecDeque, utile per code e buffer circolari.update e try_update sugli atomiciI tipi atomici (AtomicUsize, AtomicBool, AtomicI32, ecc.) guadagnano i metodi update e try_update per semplificare i pattern read-modify-write senza dover scrivere manualmente il loop CAS (Compare-And-Swap):use std::sync::atomic::{AtomicUsize, Ordering};  let counter = AtomicUsize::new(0);  // Incrementa atomicamente, restituisce il vecchio valore let old = counter.update(Ordering::SeqCst, |x| x + 1); println!("Valore precedente: {}", old);  // try_update: permette di fallire condizionalmente // (ritorna Err se la chiusura restituisce None) let result = counter.try_update(Ordering::SeqCst, |x| {     if x &lt; 100 { Some(x + 1) } else { None } });  match result {     Ok(old_val) =&gt; println!("Aggiornato da {}", old_val),     Err(_) =&gt; println!("Limite raggiunto, nessun aggiornamento"), }Questi metodi gestiscono internamente il retry loop in caso di contesa tra thread, rendendo il codice più leggibile e meno soggetto a errori.Stabilizzazioni in const contextRust 1.95 estende il supporto ai const context per alcune API già stabili, consentendo un uso più ampio della valutazione a compile-time:fmt::from_fn ora utilizzabile in contesti constMetodi di ControlFlow ora const-stabiliNuovi metodi unsafe per puntatori: as_ref_unchecked e as_mut_uncheckedLayout::dangling_ptr per manipolazione avanzata della memoria in allocatori customBreaking change: JSON target spec non più supportata su stableRust 1.95 rimuove il supporto alle specifiche di target in formato JSON sul canale stable. Questa funzionalità era già effettivamente limitata al canale nightly (costruire core richiedeva già nightly), quindi l’impatto pratico è minimo per la maggior parte dei progetti. Chi usa target custom non standard dovrà assicurarsi di usare il canale nightly o di migrare a definizioni di target ufficialmente supportate.Come aggiornarePer aggiornare Rust all’ultima versione stabile, basta eseguire:rustup update stablePer verificare la versione installata:rustc --version # rustc 1.95.0 (xxxxxxxx 2026-04-16)ConclusioniRust 1.95.0 è una release solida che porta miglioramenti trasversali all’ergonomia del linguaggio. cfg_select! riduce la dipendenza da crate esterni per il codice condizionale, gli if-let guard aumentano l’espressività del pattern matching, e le nuove API per Vec e atomici semplificano pattern comuni nella gestione dello stato condiviso in contesti concorrenti.Nel complesso, questa release conferma la direzione del progetto Rust: migliorare l’ergonomia senza compromettere la sicurezza, incorporando nella libreria standard soluzioni che prima richiedevano crate esterni.Fonte: Announcing Rust 1.95.0 — The Rust Programming Language Blog]]></description><link>https://forum.androidiani.net/topic/0053a857-f38f-471f-bb65-f87de462fc3b/rust-1.95.0-cfg_select-if-let-guard-nei-match-e-nuove-api-per-vec-e-atomici</link><guid isPermaLink="true">https://forum.androidiani.net/topic/0053a857-f38f-471f-bb65-f87de462fc3b/rust-1.95.0-cfg_select-if-let-guard-nei-match-e-nuove-api-per-vec-e-atomici</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Mon, 20 Apr 2026 11:58:16 GMT</pubDate></item><item><title><![CDATA[Come GitHub usa eBPF per rendere i deploy sicuri: architettura e implementazione]]></title><description><![CDATA[Quando si parla di deployment safety, pochi casi studio sono più istruttivi di quello di GitHub: un’azienda che ospita il proprio codice sorgente sulla piattaforma che sta cercando di aggiornare. Questo crea una dipendenza circolare potenzialmente devastante — durante un’interruzione del servizio, il deploy che dovrebbe ripristinarlo potrebbe fallire proprio perché si appoggia alla piattaforma non disponibile.Un recente articolo del blog di ingegneria di GitHub descrive come il team abbia risolto questo problema usando eBPF (extended Berkeley Packet Filter), una tecnologia kernel Linux che permette di eseguire codice sandboxed direttamente nel kernel senza modificarlo.Il problema delle dipendenze nei deployGitHub ha identificato tre categorie di dipendenze problematiche nei propri script di deploy:Dipendenze dirette: script che scaricano binari da github.com durante il deployDipendenze nascoste: tool che controllano aggiornamenti su GitHub senza una richiesta esplicitaDipendenze transitive: servizi interni che chiamano dipendenze esterne in modo indirettoIl rischio è evidente: se github.com non è disponibile e il deploy ne dipende, si entra in un loop da cui è difficile uscire. Serviva un meccanismo per rilevare — e bloccare — queste dipendenze prima che causassero problemi in produzione.eBPF: filtraggio kernel-level senza modificare il kerneleBPF è un framework che consente di iniettare programmi nel kernel Linux in modo sicuro e verificato. Originariamente nato per il filtraggio dei pacchetti di rete (da cui il nome “Berkeley Packet Filter”), si è evoluto in una piattaforma generale per osservabilità, sicurezza e networking a bassa latenza.GitHub ha sfruttato due tipi specifici di programmi eBPF:BPF_PROG_TYPE_CGROUP_SKB: monitora il traffico in uscita (egress) e applica regole di blocco IP basate sui risultati della risoluzione DNSBPF_PROG_TYPE_CGROUP_SOCK_ADDR: intercetta le query DNS e le reindirizza a un proxy userspace che valuta le richieste rispetto a una lista di domini bloccatiI cgroup Linux vengono usati per isolare i processi di deploy. A questi cgroup vengono poi agganciate (attached) le mappe e i programmi eBPF, che possono così osservare e controllare il traffico di rete generato da quei processi specifici.L’architettura del sistemaIl flusso di funzionamento è il seguente:Un processo di deploy tenta di risolvere un nome di dominio (es. github.com)Il programma eBPF intercetta la query DNS tramite BPF_PROG_TYPE_CGROUP_SOCK_ADDRLa query viene reindirizzata a un proxy userspaceIl proxy verifica il dominio rispetto alla policy attivaSe il dominio è nella lista bloccata, la risposta viene falsificata (restituendo un IP non raggiungibile)Il programma BPF_PROG_TYPE_CGROUP_SKB monitora il traffico IP per rafforzare il bloccoTutte le violazioni vengono loggate con PID, nome del processo, comando e transaction ID DNSParticolarmente elegante è l’uso di mappe eBPF (BPF maps) per condividere stato tra i programmi kernel e il proxy userspace, consentendo aggiornamenti dinamici alla policy senza dover ricaricare i programmi.Implementazione con cilium/ebpf e GoPer semplificare lo sviluppo, il team ha usato la libreria Go cilium/ebpf, uno dei wrapper eBPF più maturi disponibili oggi. I programmi eBPF sono scritti in C e compilati con bpf2go, uno strumento che genera automaticamente le struct Go corrispondenti per interagire con le mappe e i programmi nel kernel.Un esempio semplificato di come si carica e aggancia un programma eBPF con cilium/ebpf:// Carica i programmi eBPF compilati
objs := bpfObjects{}
if err := loadBpfObjects(&amp;objs, nil); err != nil {
    log.Fatalf("loading objects: %v", err)
}
defer objs.Close()

// Aggancia il programma al cgroup del processo di deploy
l, err := link.AttachCgroup(link.CgroupOptions{
    Path:    "/sys/fs/cgroup/deploy",
    Attach:  ebpf.AttachCGroupInetEgress,
    Program: objs.FilterEgress,
})
if err != nil {
    log.Fatalf("attaching cgroup: %v", err)
}
defer l.Close()

log.Println("eBPF program attached, monitoring egress traffic...")La policy viene aggiornata dinamicamente scrivendo nelle mappe eBPF, senza necessità di riavviare nulla:// Blocca un dominio aggiungendolo alla mappa eBPF
key := []byte("github.com")
value := uint32(1) // 1 = blocked
if err := objs.BlockedDomains.Put(key, value); err != nil {
    log.Fatalf("updating map: %v", err)
}Questo approccio consente di modificare le policy a runtime, ad esempio durante un incidente, aggiungendo o rimuovendo domini dalla blocklist senza interrompere i processi in esecuzione.Correlazione PID e DNS transaction IDUno degli aspetti più sofisticati dell’implementazione è la capacità di correlare le query DNS bloccate al processo specifico che le ha generate. Il sistema usa una mappa eBPF per tenere traccia della coppia (PID, DNS transaction ID): quando il proxy userspace vede una query, può risalire al processo che l’ha originata e loggare il percorso completo dell’esecuzione, incluso il comando invocato.Questo livello di visibilità è fondamentale per il debugging: anziché sapere solo che “qualcosa ha chiamato github.com”, gli ingegneri possono vedere esattamente quale script, a quale riga, stava tentando di accedere alla risorsa bloccata.Risultati dopo sei mesi di rolloutDopo sei mesi di utilizzo in produzione, il sistema ha permesso a GitHub di:Rilevare dipendenze problematiche prima che causino guasti durante i deployIdentificare tool e script che accedevano silenziosamente a github.com durante le operazioni di deployMigliorare la velocità di recovery dagli incidenti, eliminando la circolarità delle dipendenzeCostruire un inventario delle dipendenze esterne degli script di deploy, utile per la pianificazione della resilienzaPerché eBPF è la scelta giusta per questo caso d’usoSoluzioni alternative avrebbero avuto limitazioni significative: un firewall a livello di rete avrebbe bloccato il traffico in modo troppo grossolano, impedendo anche il normale funzionamento dei servizi. Proxy applicativi avrebbero richiesto modifiche agli script di deploy. eBPF consente invece di applicare policy granulari, per-processo e dinamiche, senza modificare gli script esistenti né aggiungere overhead significativo alle operazioni normali.Il fatto che sia una tecnologia kernel-level la rende anche difficile da aggirare accidentalmente: non è sufficiente usare una libreria di networking alternativa o bypassare il resolver DNS del sistema per evadere i controlli.ConclusioniL’approccio di GitHub dimostra come eBPF stia trasformando la sicurezza e l’osservabilità delle infrastrutture moderne. Non più solo strumento per profiling di rete, eBPF diventa un componente architetturale per l’enforcement di policy a runtime, invisibile alle applicazioni e aggiornabile senza downtime.Per chi gestisce infrastrutture critiche o pipeline CI/CD complesse, questo caso studio offre un modello replicabile: usare eBPF per imporre vincoli ai processi di deploy e garantire che il sistema possa sempre ripristinarsi indipendentemente dallo stato dei servizi che ospita.Fonte: How GitHub uses eBPF to improve deployment safety — GitHub Engineering Blog]]></description><link>https://forum.androidiani.net/topic/53f075ca-efea-4310-8cdf-cb2f4647c155/come-github-usa-ebpf-per-rendere-i-deploy-sicuri-architettura-e-implementazione</link><guid isPermaLink="true">https://forum.androidiani.net/topic/53f075ca-efea-4310-8cdf-cb2f4647c155/come-github-usa-ebpf-per-rendere-i-deploy-sicuri-architettura-e-implementazione</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Mon, 20 Apr 2026 08:05:22 GMT</pubDate></item><item><title><![CDATA[AOT-Friendly DTO Mapping in .NET: Source Generators al posto della reflection]]></title><description><![CDATA[Con l’adozione crescente di NativeAOT e il trimming in .NET, uno dei nodi critici per molti progettisti è la gestione del mapping tra oggetti: le librerie classiche come AutoMapper si basano pesantemente sulla reflection a runtime, che è incompatibile con la compilazione Ahead-of-Time. In questo articolo esploreremo ElBruno.AotMapper, una libreria che risolve il problema spostando la generazione del codice di mapping a compile time grazie ai Roslyn Source Generators.Il problema: reflection e AOT non vanno d’accordoQuando si compila un’applicazione .NET con PublishAot=true oppure si abilita il trimming aggressivo, il runtime non può più analizzare dinamicamente i tipi come farebbe normalmente. Le librerie che usano System.Reflection per scoprire proprietà e invocare setter al volo — come AutoMapper nella sua configurazione classica — provocano errori in fase di esecuzione o vengono tagliate dal trimmer.Le alternative tradizionali per chi vuole restare AOT-compatible sono:Scrivere il mapping a mano (tedioso e soggetto a errori)Usare Mapster con configurazione esplicita (verbosa)Affidarsi a Source Generators che generano il codice prima della compilazioneElBruno.AotMapper percorre la terza strada: usa un Source Generator basato su Roslyn per emettere metodi di estensione fortemente tipizzati già al momento del build, con zero reflection a runtime.Come funziona: Source Generators al posto della reflectionI Roslyn Source Generators sono componenti che vengono eseguiti durante la compilazione e possono produrre file C# aggiuntivi. In questo caso, il generator analizza le vostre classi annotate con attributi specifici e genera automaticamente i metodi di mapping corrispondenti.I vantaggi concreti di questo approccio:Gli errori di mapping emergono in fase di compilazione, non a runtimeZero overhead da reflection: il codice generato è codice C# diretto, ottimizzabile dal JIT o dall’AOT compilerCompatibilità completa con NativeAOT e applicazioni trimmateIl codice generato è visibile e debuggabile (potete ispezionarlo nelle cartelle di build)InstallazioneLa libreria si divide in due package NuGet distinti:# Attributi e interfacce core dotnet add package ElBruno.AotMapper  # Il Source Generator (solo per il progetto che contiene i DTO) dotnet add package ElBruno.AotMapper.GeneratorIl Generator va aggiunto come PrivateAssets="all" in genere, per evitare che la dipendenza si propaghi ai progetti dipendenti:&lt;PackageReference Include="ElBruno.AotMapper.Generator" Version="x.y.z"&gt;   &lt;PrivateAssets&gt;all&lt;/PrivateAssets&gt;   &lt;IncludeAssets&gt;runtime; build; native; contentfiles; analyzers&lt;/IncludeAssets&gt; &lt;/PackageReference&gt;Utilizzo degli attributi di mappingMapping base con [MapFrom]Il caso più semplice: due classi con le stesse proprietà. Si annota il DTO destinazione con [MapFrom] specificando il tipo sorgente:// Entità del dominio public class Product {     public int Id { get; set; }     public string Name { get; set; } = string.Empty;     public decimal Price { get; set; }     public DateTime CreatedAt { get; set; } }  // DTO di risposta annotato [MapFrom(typeof(Product))] public class ProductDto {     public int Id { get; set; }     public string Name { get; set; } = string.Empty;     public decimal Price { get; set; } }Il Source Generator analizza questo codice e genera automaticamente un metodo di estensione. Dopo la compilazione potrete usarlo così:var product = await dbContext.Products.FindAsync(id); var dto = product.ToProductDto(); // Metodo generato, zero reflectionRimappatura di proprietà con [MapProperty]Quando i nomi delle proprietà non corrispondono tra sorgente e destinazione, si usa [MapProperty] per indicare esplicitamente il mapping:[MapFrom(typeof(User))] public class UserSummaryDto {     public int Id { get; set; }      [MapProperty(nameof(User.FirstName))]     public string Nome { get; set; } = string.Empty;  // Diverso da FirstName      [MapProperty(nameof(User.LastName))]     public string Cognome { get; set; } = string.Empty; }Ignorare proprietà con [MapIgnore]Per escludere campi sensibili o non necessari:[MapFrom(typeof(User))] public class PublicUserDto {     public int Id { get; set; }     public string UserName { get; set; } = string.Empty;      [MapIgnore]     public string? PasswordHash { get; set; }  // Non verrà mappato }Conversioni custom con [MapConverter]Per logiche di conversione non banali, si implementa IMapConverter&lt;TSource, TDestination&gt;:public class PriceToStringConverter : IMapConverter&lt;decimal, string&gt; {     public string Convert(decimal source) =&gt; source.ToString("C2"); }  [MapFrom(typeof(Product))] public class ProductDisplayDto {     public string Name { get; set; } = string.Empty;      [MapConverter(typeof(PriceToStringConverter))]     public string FormattedPrice { get; set; } = string.Empty; }Integrazione in un Minimal API ASP.NET CoreIl package AspNetCore della libreria facilita l’integrazione con Dependency Injection. Ecco un esempio completo di endpoint:// Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext&lt;AppDbContext&gt;(...);  var app = builder.Build();  app.MapGet("/products/{id}", async (int id, AppDbContext db) =&gt; {     var product = await db.Products.FindAsync(id);     if (product is null) return Results.NotFound();      // ToProductDto() è un metodo generato dal Source Generator     return Results.Ok(product.ToProductDto()); });  app.Run();Quando si pubblica con dotnet publish -r win-x64 -p:PublishAot=true, il codice di mapping generato viene incluso senza problemi perché è codice C# statico, non reflection dinamica.Considerazioni praticheLa libreria è indicata soprattutto per chi:Sta migrando applicazioni verso NativeAOT o vuole abilitare il trimmingSviluppa microservizi con startup time critico (AOT avvia le app molto più velocemente)Preferisce avere errori di mapping evidenziati a compile timeVuole ispezionare il codice generato per capire esattamente cosa succedeLa libreria è ancora in evoluzione (work-in-progress secondo l’autore), quindi prima di adottarla in produzione è consigliabile valutare alternative mature come Mapperly, anch’essa basata su Source Generators e con una community più consolidata. Detto questo, ElBruno.AotMapper è un ottimo punto di partenza per capire come funziona il mapping AOT-friendly e i Source Generators in generale.ConclusioneIl passaggio a NativeAOT e al trimming in .NET è una tendenza inesorabile, specialmente per applicazioni cloud-native e microservizi che richiedono avvio rapido e footprint ridotto. Le librerie di mapping basate su reflection appartengono a un’era che sta tramontando: i Source Generators sono il futuro, e ElBruno.AotMapper mostra come si possa risolvere un problema pratico quotidiano — il mapping DTO — con questo approccio moderno.Se volete esplorare ulteriormente l’argomento, la documentazione ufficiale di Roslyn Source Generators è disponibile su Microsoft Learn, e il progetto di riferimento industriale è Mapperly.Fonte originale: AOT-Friendly DTO Mapping in .NET – El Bruno]]></description><link>https://forum.androidiani.net/topic/95d679a0-1226-4238-8ee9-92326247ec8a/aot-friendly-dto-mapping-in-.net-source-generators-al-posto-della-reflection</link><guid isPermaLink="true">https://forum.androidiani.net/topic/95d679a0-1226-4238-8ee9-92326247ec8a/aot-friendly-dto-mapping-in-.net-source-generators-al-posto-della-reflection</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Sun, 19 Apr 2026 12:31:45 GMT</pubDate></item><item><title><![CDATA[Command Pattern in C#: guida completa con undo, redo e Dependency Injection]]></title><description><![CDATA[Il Command Pattern è uno dei design pattern comportamentali più utili nel mondo dello sviluppo software. In C#, la sua implementazione è particolarmente elegante e consente di costruire sistemi robusti con funzionalità di undo/redo, accodamento di operazioni e logging senza intaccare la logica di business. In questo articolo vedremo come implementarlo passo dopo passo, con esempi concreti e consigli pratici per applicazioni reali.Cos’è il Command Pattern?Il Command Pattern incapsula una richiesta come un oggetto autonomo, separando chi la emette da chi la esegue. Questo consente di parametrizzare i client con richieste diverse, accodare operazioni, supportare il rollback e costruire sistemi di macro o audit trail. In parole semplici: invece di chiamare direttamente un metodo, si crea un oggetto che “sa come” eseguire quella chiamata, e lo si passa all’esecutore.I casi d’uso più classici includono:Editor di testo o grafica con undo/redo illimitatoSistemi di workflow con operazioni reversibiliTransaction scripts in architetture DDDLogging e auditing di operazioni criticheMacro recording nelle applicazioni di produttivitàI componenti del patternUn’implementazione corretta del Command Pattern in C# prevede quattro attori principali:ICommand: l’interfaccia contrattuale che definisce Execute(), Undo() e una proprietà descrittivaReceiver: la classe che contiene la logica di business effettiva, ignara del patternConcrete Command: le implementazioni di ICommand, che catturano i parametri in costruzione e delegano al ReceiverInvoker: gestisce la coda di esecuzione e le stack di undo/redoImplementazione passo dopo passoStep 1: definire l’interfaccia ICommandIl contratto deve essere minimale. Tutti i dati necessari all’esecuzione viaggiano dentro il comando stesso, non vengono passati ai metodi:public interface ICommand {     string Description { get; }     void Execute();     void Undo(); }La scelta di metodi senza parametri è deliberata: favorisce l’immutabilità del comando dopo la costruzione e impedisce il condizionamento da stato esterno.Step 2: creare il ReceiverIl Receiver contiene la logica reale. Non sa nulla di comandi, stack o undo. È testabile in isolamento:public class TextDocument {     private readonly List&lt;string&gt; _lines = new();      public IReadOnlyList&lt;string&gt; Lines =&gt; _lines.AsReadOnly();      public void AddLine(string text) =&gt; _lines.Add(text);      public void RemoveLine(int index)     {         if (index &gt;= 0 &amp;&amp; index &lt; _lines.Count)             _lines.RemoveAt(index);     } }Step 3: implementare i Concrete CommandsOgni comando cattura i propri dati al momento della costruzione e implementa sia Execute che Undo. Notare che lo stato necessario per l’undo viene salvato durante Execute:public sealed class AddLineCommand : ICommand {     private readonly TextDocument _document;     private readonly string _text;      public string Description =&gt; $"Aggiungi riga: "{_text}"";      public AddLineCommand(TextDocument document, string text)     {         _document = document ?? throw new ArgumentNullException(nameof(document));         _text = text ?? throw new ArgumentNullException(nameof(text));     }      public void Execute() =&gt; _document.AddLine(_text);      public void Undo() =&gt; _document.RemoveLine(_document.Lines.Count - 1); }  public sealed class RemoveLineCommand : ICommand {     private readonly TextDocument _document;     private readonly int _index;     private string? _removedText;      public string Description =&gt; $"Rimuovi riga {_index}";      public RemoveLineCommand(TextDocument document, int index)     {         _document = document;         _index = index;     }      public void Execute()     {         // Salviamo lo stato per poter fare undo         _removedText = _document.Lines[_index];         _document.RemoveLine(_index);     }      public void Undo()     {         if (_removedText is not null)             _document.AddLine(_removedText);     } }Il punto critico è la cattura dello snapshot in Execute(): RemoveLineCommand ricorda il testo rimosso prima di cancellarlo, rendendo possibile il ripristino.Step 4: costruire l’Invoker con historyL’Invoker mantiene due stack: uno per l’undo e uno per il redo. Quando si esegue un nuovo comando, la redo stack viene svuotata per evitare storie ramificate incoerenti:public class CommandInvoker {     private readonly Stack&lt;ICommand&gt; _undoStack = new();     private readonly Stack&lt;ICommand&gt; _redoStack = new();      public void ExecuteCommand(ICommand command)     {         command.Execute();         _undoStack.Push(command);         _redoStack.Clear(); // Nuova azione invalida il redo     }      public bool CanUndo =&gt; _undoStack.Count &gt; 0;     public bool CanRedo =&gt; _redoStack.Count &gt; 0;      public void Undo()     {         if (!CanUndo) return;         var cmd = _undoStack.Pop();         cmd.Undo();         _redoStack.Push(cmd);     }      public void Redo()     {         if (!CanRedo) return;         var cmd = _redoStack.Pop();         cmd.Execute();         _undoStack.Push(cmd);     }      public IEnumerable&lt;string&gt; GetHistory() =&gt;         _undoStack.Select(c =&gt; c.Description); }Step 5: Macro Commands (Composite)Il Command Pattern si combina naturalmente con il Composite Pattern per costruire macro che raggruppano più operazioni atomiche:public sealed class MacroCommand : ICommand {     private readonly IReadOnlyList&lt;ICommand&gt; _commands;      public string Description =&gt; $"Macro ({_commands.Count} operazioni)";      public MacroCommand(IEnumerable&lt;ICommand&gt; commands)     {         _commands = commands.ToList();     }      public void Execute()     {         foreach (var cmd in _commands)             cmd.Execute();     }      public void Undo()     {         // L'undo avviene in ordine inverso         for (int i = _commands.Count - 1; i &gt;= 0; i--)             _commands[i].Undo();     } }Step 6: integrazione con Dependency InjectionIn applicazioni .NET moderne, il pattern si integra perfettamente con il DI container di ASP.NET Core:// Program.cs builder.Services.AddSingleton&lt;TextDocument&gt;(); builder.Services.AddSingleton&lt;CommandInvoker&gt;(); builder.Services.AddTransient&lt;DocumentCommandFactory&gt;();  // Factory per creare comandi on-demand public class DocumentCommandFactory {     private readonly TextDocument _document;      public DocumentCommandFactory(TextDocument document)     {         _document = document;     }      public ICommand AddLine(string text) =&gt; new AddLineCommand(_document, text);     public ICommand RemoveLine(int index) =&gt; new RemoveLineCommand(_document, index); }I componenti longevi (TextDocument, CommandInvoker) sono Singleton; i comandi vengono creati on-demand tramite factory e rimangono short-lived.Errori comuni da evitareChi si avvicina al Command Pattern per la prima volta tende a commettere questi errori:Logica nel comando invece che nel Receiver: i comandi devono orchestrare, non implementare. La logica di business appartiene al Receiver, dove può essere testata in isolamento.Mancato snapshot dello stato per l’undo: se dimenticate di catturare lo stato prima dell’esecuzione, l’undo diventa impossibile o inaffidabile.Stato condiviso tra comandi: ogni comando deve essere autosufficiente. Lo stato mutabile condiviso porta a bug sottili quando i comandi vengono eseguiti in ordine diverso.Undo implementato in modo forzato: per operazioni genuinamente non reversibili (come l’invio di un’email), meglio lanciare un’eccezione o documentare esplicitamente il limite, piuttosto che fingere un undo inesistente.ConclusioneIl Command Pattern è uno strumento potente e sottovalutato. Quando il vostro sistema ha bisogno di storico operazioni, undo/redo, accodamento differito o audit trail, questo pattern vi offre una soluzione elegante e testabile. La separazione netta tra Receiver (logica) e Command (orchestrazione) rende il codice mantenibile anche su larga scala.Se state sviluppando con C# e .NET, consideratelo ogni volta che la vostra architettura richiede operazioni reversibili o la composizione di azioni complesse.Fonte originale: How to Implement Command Pattern in C# – DevLeader.ca]]></description><link>https://forum.androidiani.net/topic/a3f374d1-8259-452e-9bcb-2ae70c1f51dc/command-pattern-in-c-guida-completa-con-undo-redo-e-dependency-injection</link><guid isPermaLink="true">https://forum.androidiani.net/topic/a3f374d1-8259-452e-9bcb-2ae70c1f51dc/command-pattern-in-c-guida-completa-con-undo-redo-e-dependency-injection</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Sun, 19 Apr 2026 09:12:09 GMT</pubDate></item><item><title><![CDATA[LINQ Max e i tipi valore nullable in C#: il comportamento inatteso che causa eccezioni a runtime]]></title><description><![CDATA[Chi usa LINQ in C# con una certa frequenza prima o poi si imbatte in un comportamento del metodo Max che, a prima vista, appare del tutto irrazionale: due chiamate sintatticamente quasi identiche producono risultati radicalmente diversi, e una delle due lancia un’eccezione a runtime su una sequenza vuota. Vediamo perché accade e come risolverlo.Il problema: Max con proiezioni su tipi diversiConsideriamo questo tipo record:public record WithValues(string Label, int Number, DateTimeOffset Date);Ora proviamo a chiamare Max su un array vuoto con tre proiezioni diverse:WithValues[] empty = [];  string? maxLabel  = empty.Max(x =&gt; x.Label);   // Restituisce null var     maxDate   = empty.Max(d =&gt; d.Date);     // Lancia InvalidOperationException int?    maxNumber = empty.Max(x =&gt; x.Number);   // Lancia InvalidOperationExceptionLa firma del metodo è:public static TResult? Max&lt;TSource, TResult&gt;(     this IEnumerable&lt;TSource&gt; source,     Func&lt;TSource, TResult&gt; selector);Il tipo di ritorno è dichiarato come TResult? — nullable. Eppure il comportamento cambia radicalmente in base a ciò che la funzione di proiezione restituisce.La causa: come Max distingue i tipi internamenteInternamente, l’implementazione di Max usa un test per decidere come comportarsi su una sequenza vuota:TResult val = default; if (val == null) {     // Tipo reference o nullable: restituisce null per sequenze vuote } else {     // Tipo valore non nullable: lancia eccezione per sequenze vuote }Questo porta a tre comportamenti distinti:Tipi reference (string): default(string) è null, quindi il ramo “restituisce null” viene eseguito correttamente.Tipi valore non nullable (int, DateTimeOffset): i loro default non sono null, quindi viene eseguito il ramo che lancia InvalidOperationException.Tipi valore nullable (int?, DateTimeOffset?): default(int?) è null, quindi rientra nel primo ramo e restituisce null.Perché questo design?Il ragionamento alla base è comprensibile: per i tipi valore, restituire il valore di default per una sequenza vuota sarebbe ambiguo. Se Max su una lista vuota di interi restituisse 0, come distinguere tra “il massimo è zero” e “la lista era vuota”? L’eccezione rimuove questa ambiguità, ma lo fa in modo sorprendente data la firma del metodo.Il problema di fondo è storico: C# ha sviluppato la nullabilità in tre fasi distinte che non si integrano uniformemente nel codice generico:I tipi reference erano sempre nullable (fin dalle origini del linguaggio).I tipi valore nullable (Nullable&lt;T&gt;, ovvero T?) sono stati introdotti in C# 2.0.Le nullable reference types (NRT) sono arrivate in C# 8.0 come feature opzionale.La soluzione: cast esplicito al tipo nullableLa correzione è semplice: basta fare il cast esplicito della proiezione al tipo nullable corrispondente.// Invece di: var maxDate    = empty.Max(d =&gt; d.Date); int? maxNumber = empty.Max(x =&gt; x.Number);  // Usare: DateTimeOffset? maxDate    = empty.Max(d =&gt; (DateTimeOffset?)d.Date); int?            maxNumber  = empty.Max(x =&gt; (int?)x.Number);Il cast forza il compilatore a inferire TResult = DateTimeOffset? (o int?), il cui default è null, e Max segue quindi il percorso corretto, restituendo null invece di lanciare un’eccezione.Alternativa: DefaultIfEmptyUn’altra soluzione è preporre DefaultIfEmpty prima di Max:int maxNumber = empty     .Select(x =&gt; x.Number)     .DefaultIfEmpty(0)     .Max();Questa alternativa è utile quando si vuole un valore di fallback esplicito invece di null, ma va usata con attenzione: il valore di default deve essere scelto consapevolmente per non introdurre ambiguità semantica nel risultato.Quando questo si manifesta in produzioneIl problema diventa insidioso quando si lavora con sequenze filtrate dinamicamente che possono risultare vuote in certi contesti, o quando il codice viene testato sempre con dati non vuoti e crasha in produzione su edge case inaspettati. Una buona pratica difensiva è usare sempre il cast a tipo nullable quando si usa Max (o Min, che ha lo stesso comportamento) su tipi valore, a meno che non si abbia la certezza assoluta che la sequenza non sarà mai vuota.ConclusioniIl comportamento di LINQ.Max con i tipi valore è uno di quei casi in cui l’implementazione è tecnicamente coerente, ma la firma del metodo lascia intendere qualcosa di diverso da ciò che avviene a runtime. La regola da ricordare è semplice: se la proiezione restituisce un tipo valore non nullable e la sequenza potrebbe essere vuota, usare sempre un cast esplicito a T?. Un piccolo accorgimento che previene eccezioni difficili da diagnosticare in produzione.Fonte originale: LINQ Max and nullable value types (Ian Griffiths, endjin.com) — tratto dal briefing Dew Drop del 17 aprile 2026]]></description><link>https://forum.androidiani.net/topic/5b9e87b8-d2f4-470c-b430-6bea359bb061/linq-max-e-i-tipi-valore-nullable-in-c-il-comportamento-inatteso-che-causa-eccezioni-a-runtime</link><guid isPermaLink="true">https://forum.androidiani.net/topic/5b9e87b8-d2f4-470c-b430-6bea359bb061/linq-max-e-i-tipi-valore-nullable-in-c-il-comportamento-inatteso-che-causa-eccezioni-a-runtime</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Sat, 18 Apr 2026 20:28:35 GMT</pubDate></item></channel></rss>