Inviare messaggi con risposta tramite l'IEventAggregator di Caliburn Micro in Windows Phone

di Cristian Civera, in Windows Phone,

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

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi