Perché estendere la piattaforma con i plug-in

In Microsoft Dataverse i plug-in sono la forma di estensibilità server-side più profonda: codice .NET compilato registrato su un message (evento) che viene eseguito dalla piattaforma quando quel message viene processato. A differenza delle Power Automate cloud flow (asincrone, low-code, latenza variabile), il plug-in gira dentro la transazione della piattaforma ed è la scelta corretta quando serve logica transazionale, validazione bloccante o coerenza garantita indipendentemente dal canale (UI, API, import, flow).

L’event execution pipeline

Ogni operazione su una tabella genera un message (Create, Update, Delete, Retrieve, Associate, Disassociate, o message custom via custom API). La piattaforma processa il message attraverso una pipeline in stage:

  • Pre-validation (stage 10) — gira prima della transazione del database e prima dei controlli di sicurezza. È il posto giusto per validazioni che devono valere anche quando l’utente non ha i privilegi, o per logica che deve poter annullare l’operazione con il minor costo. Attenzione: non essendo ancora in transazione, ciò che scrivi qui non fa rollback automatico.
  • Pre-operation (stage 20) — dentro la transazione, dopo la validazione di sicurezza ma prima della scrittura su DB. Ideale per modificare i valori del record in ingresso (via Target) senza un update aggiuntivo: cambi la Entity Target e la piattaforma la persiste.
  • Post-operation (stage 40) — dentro la transazione, dopo la scrittura. Qui il record esiste già con il suo GUID; è il posto per creare record correlati, aggiornare altre tabelle o chiamare servizi esterni.

Synchronous vs asynchronous

Uno step synchronous blocca la chiamata dell’utente fino al completamento: un’eccezione fa rollback dell’intera transazione e mostra l’errore. Uno step asynchronous viene accodato come system job ed eseguito dopo il commit: non blocca l’utente, non partecipa alla stessa transazione e non può annullarla. Scegli sincrono per validazioni e coerenza immediata; asincrono per side-effect pesanti o integrazioni tollerabili al ritardo.

Context, images e registrazione

Il plug-in implementa IPlugin.Execute(IServiceProvider). Da lì recuperi IPluginExecutionContext, che espone tra l’altro MessageName, Stage, Depth, PrimaryEntityName, e le collection InputParameters (dove vive Target) e OutputParameters. La primary entity è la tabella su cui gira il message; una secondary entity appare solo in message relazionali come SetState o Associate.

Le images sono snapshot del record registrati sullo step:

  • Pre-image — stato del record prima dell’operazione. Disponibile su Update e Delete. Serve per confrontare vecchio/nuovo o per leggere campi non presenti nel Target.
  • Post-image — stato dopo l’operazione. Non disponibile su Delete, né su Create in pre-operation (il record non è ancora scritto).

Registri assembly e step con il Plug-in Registration Tool (PRT) o, in modo moderno e scriptabile, con pac plugin. Puoi registrare l’assembly nella sandbox (isolation mode: Sandbox), obbligatoria per il cloud: il codice gira con partial trust, senza accesso a file system, registro, socket arbitrari; sono ammesse solo chiamate HTTP/HTTPS in uscita. Filtra sempre gli attributi su cui lo step si attiva (filtering attributes) per non eseguire logica inutile.

Trappole tipiche d’esame

  • Scenario: un plug-in sincrono in post-operation chiama un servizio esterno lento e va in timeout. → La causa è il limite circa 2 minuti di esecuzione: sposta la logica pesante/integrazione su uno step asynchronous (o Power Automate), tenendo il sincrono solo per validazioni rapide.
  • Scenario: un plug-in su Update che a sua volta fa un Update sullo stesso record entra in loop. → Previeni la ricorsione controllando context.Depth e, soprattutto, restringendo i filtering attributes o modificando il Target in pre-operation invece di lanciare un nuovo update.
  • Scenario: devi confrontare il valore precedente di un campo, ma leggi da Target e trovi null. → Target contiene solo i campi modificati; il valore vecchio va letto da una pre-image registrata con quell’attributo.
  • Scenario: ti serve il GUID del record appena creato per collegare figli. → Non usare pre-operation: in Create il GUID è disponibile solo in post-operation (o via OutputParameters["id"]).
  • Scenario: l’assembly deve scrivere su file locale del server. → Impossibile in sandbox: la isolation impedisce I/O su disco; usa storage esterno via chiamata HTTPS o Azure integration.