Il framework Caliburn Micro, di cui ormai abbiamo parlato ampiamente con articoli e script, dispone di un servizio, rappresentato dall'interfaccia IEventAggregator, per inviare messaggi e gestirli tramite mediator pattern. Questa tecnica è utile quando chi manda un messaggio e chi lo riceve non si conoscono, in particolar modo quando il ViewModel deve comunicare con la View.
L'implementazione del framework permette l'invio di messaggi tramite il metodo Publish, mentre chi lo vuole processare deve implementare l'interfaccia IHandle<T> e sottoscrivere il messaggio chiamando il metodo Subscribe. Nel caso in cui mandiamo un messaggio per notificare l'utente, però, se vogliamo ricevere un'ipotetica scelta fatta sulla notifica, siamo obbligati a creare un nuovo messaggio di ritorno, che viene quindi lanciato dalla View e il ViewModel lo gestisce.
In questo script proponiamo un helper che ci facilita questo lavoro, con un apposito metodo PublishAsync, che ci permette di aspettare la risposta semplicemente usando async/await. Ponendo quindi di voler notificare l'utente e aspettare la risposta, quello che potremo scrivere sarà:
var r = await this.EventAggregator.PublishAsync(new AskMessage("Continuare?"));
Per farlo creiamo un extension metod di nome PublishAsync che estende IEventAggregator. Al suo interno lanciamo il messaggio e creiamo un handler specifico che gestirà il messaggio di ritorno.
private readonly static List<TaskCompleteHandler> handlers = new List<TaskCompleteHandler>(); public static async Task<R> PublishAsync<T, R>(this IEventAggregator eventAggregator, T message) where T : IDuplexMessage<R> { if (eventAggregator == null) throw new ArgumentNullException("eventAggregator"); if (message == null) throw new ArgumentNullException("message"); var handler = new TaskCompleteHandler(eventAggregator, message); handlers.Add(handler); eventAggregator.Subscribe(handler); eventAggregator.Publish(message); var r = await handler.Task; return (R)r; }
Possiamo vedere che richiediamo che il messaggio sia di tipo IDuplexMessage. E' un modo per tipizzare la chiamata, dato che l'interfaccia è vuota, e così definita.
public interface IDuplexMessage { } public interface IDuplexMessage<T> : IDuplexMessage { }
La classe TaskCompleteHandler è invece così definita.
internal class TaskCompleteHandler : IHandle<TaskCompletedMessage> { private readonly IEventAggregator eventAggregator; private readonly object originalMessage; private readonly TaskCompletionSource<object> taskSource; public TaskCompleteHandler(IEventAggregator eventAggregator, object message) { this.eventAggregator = eventAggregator; this.originalMessage = message; taskSource = new TaskCompletionSource<object>(); } public Task<object> Task { get { return taskSource.Task; } } public void Handle(TaskCompletedMessage message) { if (ReferenceEquals(message.OriginalMessage, this.originalMessage)) { handlers.Remove(this); eventAggregator.Unsubscribe(this); taskSource.TrySetResult(message.Result); } } }
Essa mantiene il riferimento al messaggio inviato e nel momento in cui riceve il messaggio di risposta, termina il TaskCompletionSource per completare l'operazione asincrona. Il messaggio di risposta generico è di tipo TaskCompletedMessage, così definito.
public class TaskCompletedMessage { public object OriginalMessage { get; private set; } public object Result { get; private set; } public TaskCompletedMessage(object originalMessage, object result) { OriginalMessage = originalMessage; Result = result; } }
Creiamo infine un secondo extension method di nome CompleteAsync, il quale riceve il messaggio originale da marcare come completato, e il risultato dell'operazione.
public static void CompleteAsync<T, R>(this IEventAggregator eventAggregator, T message, R result) where T : IDuplexMessage<R> { if (eventAggregator == null) throw new ArgumentNullException("eventAggregator"); if (message == null) throw new ArgumentNullException("message"); eventAggregator.Publish(new TaskCompletedMessage(message, result)); }
A questo punto abbiamo tutto per rendere funzionante il messaggio con la risposta. Chi gestisce il messaggio dovrà notificare il risultato con il seguente codice:
void IHandle<AskMessage>.Handle(AskMessage message) { var r = MessageBox.Show(message.Message, "Conferma", MessageBoxButton.OKCancel) == MessageBoxResult.OK; eventAggregator.CompleteAsync(message, r); }
Come possiamo vedere, una volta sviluppato l'helper, le chiamate da fare sono due: PublishAsync per lanciare il messaggio e CompleteAsync, per notificare la risposta.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
3 metodi JavaScript che ogni applicazione web dovrebbe contenere
Utilizzare flat e flatMap per appiattire array innestati in array
Utilizzare HiLo per ottimizzare le insert in un database con Entity Framework
Effettuare il deploy di immagini solo da container registry approvati in Kubernetes
Utilizzare la libreria Benchmark.NET per misurare le performance
Ottimizzare serializzazione e deserializzaione tramite le options con System.Text.Json
Utilizzare parametri a livello di controller nel routing di ASP.NET Core
Usare il versioning con i controller di ASP.NET Core Web API
ChatOps con GitHub
Aggiungere le issue di più repository in una board in GitHub
Innestare una query nel metodo Contains di Entity Framework Core
Effettuare chiamate con versioning da Blazor ad ASP.NET Core