In
C# è possibile passare una
funzione come
argomento ad un metodo.
Bisogna prima di tutto definire la
firma del metodo usato come parametro, tramite la keyword
delegatepublic delegate void ExecCodeBlock();
a questo punto va definita una funzione che accetta come
parametro il delegate
private static async Task ExecuteAsync(ExecCodeBlock fnUpdate)
{
....istruzioni prima...
// invoco la funzione passata
fnUpdate();
....istruzioni dopo...
}
che può essere richiamata passando una funzione
anonima che rispetta la firma/delegato
await ExecuteAsync(() =>
{
...istruzioni...
});
Esempio
Posso ad esempio creare un metodo per
C# CSOM SharePoint per gestire un retry in caso di
conflitto di salvataggio sull'update di un item.
Definisco il delegato
ExecCodeBlockpublic delegate void ExecCodeBlock();
creo il
context per collegarmi
using Microsoft.SharePoint.Client;
// Microsoft.SharePointOnline.CSOM
// PnP.Framework
private static ClientContext GetClientContext(string siteUrl, string clientId, string clientSecret)
{
ClientContext ctx = new PnP.Framework.AuthenticationManager()
.GetACSAppOnlyContext(siteUrl, clientId, clientSecret);
return ctx;
}
creo il metodo principale dove andrò a richiamare la funzione che gestrisce il conflitto di versione
string siteUrl = "https://tenantName.sharepoint.com/sites/xxx";
string clientId = "57a3...3d6d246";
string clientSecret = "kLR...Tk6k=";
using (ClientContext ctx = GetClientContext(siteUrl, clientId, clientSecret))
{
List list = ctx.Web.GetList("/sites/xxx/lists/listName");
// leggo l'item da aggiornare
ListItem item = list.GetItemById(5);
ctx.Load(item);
// await ctx.ExecuteQueryRetryAsync(); // attivare solo per debug, vedi nota alla fine
// richiamo il metodo passando la funzione anonima
await ExecuteQueryUpdateItemAsync(ctx, item, () =>
{
item["Title"] = $"Test {DateTime.Now}";
item.Update();
});
}
definisco la funzione
ExecuteQueryUpdateItemAsync che si occupa di gestire il retry in caso di fallimento
private static async Task ExecuteQueryUpdateItemAsync(ClientContext ctx, ListItem item, ExecCodeBlock fnUpdate)
{
const int MAX_RETRY = 10; // numero massimo di tentativi
int retry = -1;
do
{
try
{
// invoco la funzione con gli update da eseguire
fnUpdate();
// scrivo gli update su SharePoint
await ctx.ExecuteQueryRetryAsync();
// se l'update è andato a buon fine esco dal ciclo
retry = -1;
}
catch (Microsoft.SharePoint.Client.ServerException ex)
{
// TODO: log exception
retry++;
if (retry >= MAX_RETRY || ex.ServerErrorTypeName != "Microsoft.SharePoint.Client.VersionConflictException")
// se ho raggiunto il numero massimo di tetativi
// oppure l'eccezione non è del tipo "Version conflict"
// esco con errore
throw;
}
// se ho avuto un conflitto di salvataggio,
// devo ricaricare l'item con la nuova versione aggiornata
// prima di riprovare a salvarlo
item.RefreshLoad();
await ctx.ExecuteQueryRetryAsync();
// continuo con il do/while, riprovo a salvare
}
} while (retry != -1);
}
In questo caso alla funzione,
oltre al delegato, passo anche i parametri ClientContext e ListItem
SharePoint.
La tecnica di ricaricare l'item in caso di conflitto di versione è solo una delle strategie possibili per gestire questi conflitti.
Non è detto che vada bene in ogni caso in quanto si rischia di sovrascrivere i dati precedenti aggiornati da altri.
Se applicare o meno questa strategia, va valutato caso per caso.
Nota
Il metodo
ExecuteQueryRetryAsync commentato dopo la
load, va usato solo per simulare il conflitto di versione.
Per simulare un conflitto:
- metti un breakpoint dopo l'ExecuteQueryRetryAsync
- metti un beask point nel metodo ExecuteQueryUpdateItemAsync nel catch
- modifica a mano un qualsiasi campo dell'item SharePoint
- continua l'esecuzione