SOAP per creare un log persistente su Business Central
Una delle cose forse più belle di Business Central è la sua capacità di gestire in modo estremamente robusto le transazioni verso il database.
La regola di base è un po’ quella del “tutto o niente, sempre e in ogni caso”: io comincio a interagire con i dati e la bestia alla prima modifica avvia una transazione implicita che termina con la corretta esecuzione (laggasi “sono arrivato in fondo senza errori”…) di tutto il codice che le mie attività si portano dietro; se in un qualsiasi puto dell’eaborazione c’è una piccola minima possibilità che si manifesti un errore, ecco che a tutto inesorabilmente viene imposto un rollback.
E questo comportamento, normalmente, è cosa buona, direi quasi incredibilmente buona.
Al programmatore è lasciata un’unica possibilità di derogare da questo comportamento che è l’istruzione (unica nel suo genere) che si chiama COMMIT.
Ovviamente questa istruzione fa esattamente quello che ci aspettiamo leggendone la sintassi: da per buono tutto quello che ha fatto a quel momento committando la transazione sul DB.
Ora, mettiamoci nei panni di un processo automatico che ha bisogno di scrivere qualche cosa nel DB per lasciare traccia delle cose he ha fatto ANCHE in condizioni di errore, per far capire a qualcuno in che contesto si è trovato ad operare il processo; immaginiamo di aggiungere a Business Central una tabella di log delle attività: se mi salvo un log nella transazione, esso subirà il rollback se il processo va in errore e con questo evento TUTTO il log scritto sarà perso insieme a tutte le modifiche ai dati che il processo ha apportato.
“Cul de sac” è il francesismo che si riferisce a questa situazione: sono sano da punto di vista dei processi, ma sono “impedito” a scrivere qualcosa di permanente.
Come fare per aggirare l’ostacolo?
Risposta uno: SQLClient
Ammesso che io abbia l’accesso al database (istanza SQL raggiungibile e credenziali di scrittura disponibili) potrei inserire nella tabella del log, via insert sql la riga di log; essendo questa ina una connessione dedicata la riga sarebbe persistente perché committata separatamente e questo è un pro; il contro è che se per caso dovessi estendere la tabella con u nuovo campo, magari cambiarla di nome, dovrei modificare anche il codice dell’insert prevedendo il nuovo campo, ecc. ecc.
Risposta due: servizi web
Ecco, questa è un po’ più laboriosa ma più intelligente: chiamare l’istanza di Business Central tramite un servizio web pubblicato, per esempio una codeunit, capace di ricevere la chiamata di e scrivere il log separatmente.
Questa strategia mi consentirebbe di avere la persistenza del log perchè l’insert del record sarebbe eseguito in una transazione separata.
Se non fosse che…
… Business Central è molto bravo a farsi chiamare da procedure esterne ma poco bravo a chiamare egli stesso procedure esterne, men che meno esso stesso!
Cosa mette a disposizione Microsoft?
Per eseguire chiamate SOAP verso altri ambienti mamma Microsoft ci offre la CU 1.290 che si chiama, neanche a dirlo, “SOAP Web Service Request Mgt.”, sulla carta una figata pazzesca, peccato che manchino dei pezzi.
- Pezzo uno: manca l’autenticazione base verso l’istanza da chiamare; in altre parole se l’istanza da chiamare non è basata sull’autenticazione Windows AD, il servizio richiede di avere un utente/password validi prima ancora di validare la richiesta.
- Pezzo due: manca una header importante, la SOAPAction che serve a esplicitare che metodo della CU remota deve essere chiamato.
In definitiva se faccio una chiamata senza modificare la CU ho questo problema:
Che si riferisce all’autenticazione; se riesco comunque a superare l’autenticazione ho questo secondo problema:
dovuto alla mancanza della header.
Come posso aggirare l’ostacolo?
Per il primo punto si può modificare la CU per consentire di aggiungere un’autenticazione utente/password; laddove nel codice abbiamo questo:
possiamo integrare con alcune righe di codice immediatamente dopo, che valutano una coppia di variabili globali aggiunte alla CU e impostate con un metodo personalizzato:
e quindi, dopo la riga evidenziata:
Dove la variabile IlcNetCredentials è definita come oggetto DotNet, nello specifico: System.Net.NetworkCredential.’System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
Analogo discorso per la header mancante nella chiamata; aggiungiamo un metodo per popolare una variabile globale:
che aggiungeremo nel blocco modificato:
Il blocco aggiunto completo è quello evidenziato in rosso.
Eseguire la chiamata
Per eseguire la chiamata, ho creato un pezzo di codice di esempio:
Questo codice chiama il metodo CheckUserPermission (è il nome della funzione dichiarata nella CU pubblicata come external) che deve essere usato nel body della chiamata (se avesse parametri sarebbero elementi XML compresi nell’elemento <NomeMetodo> e che DEVE essere presente anche nella header della chiamata.
Le credenziali per l’autenticazione base all’URL sono impostate con SetUriCredentials, mentre l’autenticazione “interna” alla chiamata SOAP è impostata dal metodo SetGlobals standard, indicato due righe prima.
In definitiva posso ora pubblicare una CodeUnit che provveda a rendere disponibile un metodo “ScriviLog” da usare per rendere persistente le informazioni pre-errore.