Il presente articolo è stato tradotto automaticamente.

Cutting Edge

Una barra di avanzamento sensibile al contesto per ASP.NET MVC

Dino Esposito

Scaricare il codice di esempio

Dino EspositoMaggior parte degli utenti di un'applicazione di computer desidera ricevere un feedback appropriato dall'applicazione ogni volta che intraprende un'operazione potenzialmente di lunga durata.Implementare questo pattern in un'applicazione Windows non è nessun grosso problema, anche se non è semplice come ci si potrebbe aspettare.Implementazione del pattern di stesso sul Web genera alcune difficoltà supplementari, che sono per lo più legati alla natura intrinsecamente apolide del Web.

In particolare per gli sviluppatori Web, visualizzando un feedback statico all'inizio dell'operazione è facile.Così il focus di questa colonna non è su come visualizzare una semplice stringa di testo che dice "Attendere prego" o un'immagine animata.Invece, questa colonna affronta la questione della segnalazione lo status delle operazioni remote, fornendo commenti sensibili al contesto che fedelmente rappresenta lo stato dell'operazione per una determinata sessione.In applicazioni Web, lunga attività eseguite sul server e non ci sono incorporate per spingere le informazioni sullo stato al browser del client.Per raggiungere questo obiettivo, è necessario costruire il proprio quadro con un mix di codice client e server.Librerie come jQuery sono utili, ma non forniscono una soluzione integrata — nemmeno nella vasta selezione di jQuery plug-in.

In questa colonna — il primo di una serie di breve — che tratterò il modello più comune di AJAX troverete dietro qualsiasi quadro progresso sensibile al contesto e costruire una soluzione su misura per le applicazioni ASP.Applicazioni di NET MVC.Se sei uno sviluppatore Web Form, si potrebbe voler dare un'occhiata a un paio di colonne che ho scritto nell'estate del 2007 targeting Web Form e AJAX (vedere il mio elenco di colonne a bit.ly/psNZAc).

Il modello 'Indicatore di progresso'

Il problema fondamentale con una relazione basata su AJAX sta fornendo risposte circa lo stato dell'operazione, mentre l'utente è in attesa di una risposta del server.Mettere un altro modo: L'utente inizia un'operazione di AJAX che vuole un po' per completare; nel frattempo, l'utente riceve aggiornamenti circa i progressi compiuti.Il problema architettonico è che l'utente non ha intenzione di ricevere risposte parziali; l'operazione restituisce la sua risposta solo quando tutto il lato server il lavoro è stato fatto.Di portare risposte parziali al client, una sorta di sincronizzazione tra il AJAX client e l'applicazione server devono essere definiti.Il modello "Indicatore di progresso" affronta questo punto.

Il modello suggerisce che architetto le operazioni del server ad hoc che scrivere informazioni sul loro status in un percorso noto.Lo stato viene sovrascritto ogni volta che l'operazione fa una quantità significativa di progresso.Allo stesso tempo, il cliente apre un secondo canale e rende letture periodiche dalla stessa posizione conosciuta.In questo modo, tutte le modifiche sono prontamente rilevate e segnalate al client.Ancora più importante, il feedback è sensibile al contesto e reale.Rappresenta efficaci progressi compiuti sul server e non è basato su supposizioni o stime.

L'implementazione del modello richiede che si scrive le operazioni del vostra server, quindi sono consapevoli dei loro ruoli.Si supponga, ad esempio, che si implementa un'operazione server basata su un flusso di lavoro.Prima di iniziare ogni passo significativo del workflow, l'operazione richiamerà qualche codice che aggiorna un archivio durevole con alcune informazioni relative alle attività.Archivio durevole può essere una tabella di database o un pezzo di memoria condivisa.Le informazioni relative alle attività possono essere un numero che indica la percentuale di lavoro compiuto o un messaggio che descrive l'operazione in corso.

Allo stesso tempo, avete bisogno di un servizio basato su JavaScript che contemporaneamente legge il testo dall'archivio durevole e la porta al client.Infine, il client utilizzerà il codice JavaScript per unire il testo all'interfaccia utente esistente.Questo può causare qualche semplice testo visualizzato in un tag DIV o in qualcosa di più sofisticato, come una barra di avanzamento basato su HTML.

Indicatore di progresso in ASP.NET MVC

Vediamo che cosa ci vuole per implementare il modello di indicatore di progresso in ASP.NET MVC.Il funzionamento del server è essenzialmente un metodo di azione controller.Il controller può essere sincrona o asincrona.Asincronia in controller è utile solo per la salute e la reattività dell'applicazione server; non ha alcun impatto sul tempo che l'utente dovrà attendere per ottenere una risposta.Il modello di indicatore di progresso funziona bene con qualsiasi tipo di controller.

Per richiamare e quindi monitorare un'operazione di server, è necessario un po' di AJAX.Tuttavia, la libreria AJAX non dovrebbe limitarsi all'immissione della richiesta e in attesa per la risposta.La biblioteca dovrebbe anche essere in grado di impostare un timer che periodicamente gli incendi una chiamata a qualche endpoint che restituisce lo stato corrente dell'operazione.Per questo motivo, jQuery o l'oggetto XMLHttpRequest nativo sono necessaria ma non sufficiente.Così ho creato un semplice oggetto di JavaScript per nascondere la maggior parte dei passaggi aggiuntivi necessari con una chiamata AJAX monitorata.All'interno di una pagina ASP.Vista NET MVC, si richiama l'operazione tramite l'oggetto JavaScript.

Il metodo controller responsabile dell'operazione utilizzerà la parte server del quadro per archiviare lo stato corrente in un percorso noto (e condiviso), come l'ASP.Cache di NET.Infine, il controller deve esporre un endpoint comune per le richieste temporizzate di chiamare per leggere lo stato in tempo reale.Prima vediamo il quadro intero in azione e poi spostare su per esplorare l'interno.

Introducendo il quadro SimpleProgress

Scritte appositamente per questo articolo, il SimpleProgress Framework (SPF) consiste in un file JavaScript e una libreria di classi.La libreria di classi viene definita una classe chiave — la classe ProgressManager — che governa l'esecuzione del compito e ogni attività di monitoraggio.Figura 1 mostra un controller campione metodo di azione che utilizza il framework.Si noti che questo codice (potenzialmente di lunga durata) dovrebbe effettivamente andare in un controller asincrono per evitare di bloccare un'applicazione ASP.NET thread per troppo tempo.

Metodo di azione Controller figura 1 utilizzando il Framework SimpleProgress

public String BookFlight(String from, String to)
{
  var taskId = GetTaskId();
  // Book first flight
  ProgressManager.SetCompleted(taskId,
    String.Format("Booking flight: {0}-{1} ...", from, to));
  Thread.Sleep(2000);
  // Book return flight
  ProgressManager.SetCompleted(taskId,
    String.Format("Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(1000);
  // Book return
  ProgressManager.SetCompleted(taskId,
    String.Format("Paying for the flight ..."));
  Thread.Sleep(2000);
  // Some return value
  return String.Format("Flight booked successfully");
}

Come potete vedere, l'operazione si basa su tre fasi principali. Per ragioni di semplicità, l'effettiva azione viene omesso e viene sostituito con un Sleep chiamata. Ancora più importante, è possibile vedere tre chiamate al SetCompleted che in realtà scrivere lo stato corrente del metodo in un percorso condiviso. I dettagli della posizione sono sepolti nella classe ProgressManager. Figura 2 mostra ciò che ha richiesto per richiamare e monitorare un metodo controller.

Figura 2 richiamando e monitoraggio di un metodo tramite JavaScript

<script type="text/javascript">
  $(document).ready(function () {
    $("#buttonStart").bind("click", buttonStartHandler);
  });
  function buttonStartHandler() {
    new SimpleProgress()
    .setInterval(600)
    .callback(
      function (status) { $("#progressbar2").text(status); },
      function (response) { $("#progressbar2").text(response); })
    .start("/task/bookflight?from=Rome&to=NewYork", "/task/progress");
  }
</script>

Si noti che per motivi di leggibilità, ho mantenuto la buttonStartHandler di Figura 2 fuori del gestore pronto del documento. Così facendo, tuttavia, aggiungere un po' di inquinamento la portata globale di JavaScript definendo una nuova funzione visibile globalmente.

È innanzitutto impostare alcuni parametri come ad esempio l'URL di essere richiamato per afferrare lo stato corrente e i metodi di callback da richiamare per aggiornare la barra di avanzamento e per aggiornare l'interfaccia utente, una volta completata l'operazione lunga. Infine, si avvia il compito.

La classe controller deve incorporare alcune funzionalità extra. In particolare, essa deve esporre un metodo per essere richiamato. Questo codice è relativamente standard ed hardcoded in una classe base da cui è possibile ereditare il tuo controller, come illustrato di seguito:

public class TaskController : SimpleProgressController
{
  ...
public String BookFlight(String from, String to)
  {
    ...
}
}

Tutta la classe SimpleProgressController è mostrata in Figura 3.

Figura 3 la Base Class per i controller di incorporare le azioni di monitoraggio

public class SimpleProgressController : Controller
{
  protected readonly ProgressManager ProgressManager;
  public SimpleProgressController()
  {
    ProgressManager = new ProgressManager();
  }
  public String GetTaskId()
  {
    // Get the header with the task ID
    var id = Request.Headers[ProgressManager.HeaderNameTaskId];
    return id ??
String.Empty;
  }
  public String Progress()
  {
    var taskId = GetTaskId();
    return ProgressManager.GetStatus(taskId);
  }
}

La classe dispone di due metodi. GetTaskId recupera l'ID univoco che rappresenta la chiave per recuperare lo stato di una particolare chiamata. Come si vedrà più in dettaglio più avanti, l'ID attività viene generato dal framework JavaScript e inviato tramite con ogni richiesta utilizzando un'intestazione HTTP personalizzata. L'altro metodo che trovate sulla classe SimpleProgressController rappresenta l'endpoint pubblico (e comune) che il framework JavaScript chiamerà indietro per ottenere lo stato di un'istanza di particolare compito.

Prima di entrare in alcuni dettagli di implementazione, Figura 4 vi darà un'idea concreta dei risultati SPF consente di raggiungere.

The Sample Application in Action
Figura 4 l'applicazione di esempio in azione

La classe ProgressManager

La classe ProgressManager fornisce l'interfaccia per leggere e scrivere lo stato corrente di un archivio condiviso. La classe è basata sull'interfaccia seguente:

public interface IProgressManager
{
  void SetCompleted(String taskId, Int32 percentage);
  void SetCompleted(String taskId, String step);
  String GetStatus(String taskId);
}

Il metodo SetCompleted memorizza lo stato come una percentuale o una stringa semplice. Il metodo GetStatus legge qualsiasi contenuto indietro. L'archivio condiviso è sottratto dall'interfaccia IProgressDataProvider:

public interface IProgressDataProvider
{
  void Set(String taskId, String progress, Int32 durationInSeconds=300);
  String Get(String taskId);
}

L'implementazione corrente di SPF fornisce solo un provider di dati di progresso che salva il suo contenuto in ASP.Cache di NET. La chiave per identificare lo stato di ogni richiesta è l'ID di attività. Figura 5 mostra un provider di dati di progresso di campione.

Figura 5 progresso Provider di dati basato su ASP.Oggetto Cache NET

public class AspnetProgressProvider : IProgressDataProvider
{
  public void Set(String taskId, String progress, Int32 durationInSeconds=3)
  {
    HttpContext.Current.Cache.Insert(
      taskId,
      progress,
      null,
      DateTime.Now.AddSeconds(durationInSeconds),
      Cache.NoSlidingExpiration);
  }
  public String Get(String taskId)
  {
    var o = HttpContext.Current.Cache[taskId];
    if (o == null)
      return String.Empty;
    return (String) o;
  }
}

Come accennato, ogni richiesta per un'attività monitorata è associato a un ID univoco. L'ID è un numero casuale generato dal framework JavaScript e passati dal client al server attraverso un'intestazione di richiesta HTTP.

Il Framework JavaScript

Una delle ragioni per che la libreria jQuery divenne così popolare è l'API di AJAX. JQuery AJAX API è potente e ricco di funzionalità, e ha dotato un elenco delle funzioni di stenografia che rendono l'immissione di una chiamata AJAX un gioco da ragazzi. Tuttavia, jQuery nativo AJAX API non supporta il monitoraggio dei progressi. Per questo motivo, avete bisogno di un wrapper API che utilizza jQuery (o qualsiasi altri framework JavaScript piacerti) per effettuare la chiamata AJAX garantendo nel contempo che per ogni chiamata, viene generato un ID attività casuale e viene attivato il servizio monitor. Figura 6 mostra un estratto il file SimpleProgress-Fx.js in accompagnamento download di codice che illustra la logica dietro l'inizio di una chiamata remota.

Figura 6 il codice dello Script per richiamare il quadro SimpleProgress

var SimpleProgress = function() {
  ...
that.start = function (url, progressUrl) {
    that._taskId = that.createTaskId();
    that._progressUrl = progressUrl;
    // Place the AJAX call
    $.ajax({
      url: url,
      cache: false,
      headers: { 'X-SimpleProgress-TaskId': that._taskId },
      success: function (data) {
        if (that._taskCompletedCallback != null)
          that._taskCompletedCallback(data);
        that.end();
      }
    });
    // Start the callback (if any)
    if (that._userDefinedProgressCallback == null)
      return this;
    that._timerId = window.setTimeout(
      that._internalProgressCallback, that._interval);
  };
}

Una volta il compito che ID viene generato, la funzione aggiunge l'ID come un'intestazione HTTP personalizzata al chiamante di AJAX. Subito dopo innescando la chiamata AJAX, la funzione imposta un timer che richiama periodicamente un callback. Il callback di legge lo stato corrente e passa il risultato a una funzione definita dall'utente per l'aggiornamento dell'interfaccia utente.

Sto utilizzando jQuery per eseguire la chiamata AJAX; a questo proposito, è importante che si spegne browser cache per le chiamate AJAX. In jQuery, memorizzazione nella cache è attivata per impostazione predefinita e viene automaticamente disattivata per determinati tipi di dati come script e JSON con imbottitura (JSONP).

Non è un compito facile

Monitoraggio operazioni in corso, non è un compito facile nelle applicazioni Web. Soluzioni basate su sondaggi sono comuni, ma non inevitabile. Un interessante progetto GitHub che implementa le connessioni persistenti in ASP.NET è SignalR (github.com/signalr). Utilizzando SignalR, si può risolvere lo stesso problema discusso qui senza polling per le modifiche.

In questo articolo, ho parlato di un quadro di campione (client e server) che tenta di semplificare l'implementazione di una barra di avanzamento sensibile al contesto. Mentre il codice è ottimizzato per le applicazioni ASP.NET MVC, il modello sottostante è assolutamente generale e può essere impiegato in ASP.Applicazioni nonché NET Web Form. Se si scarica e sperimentare con il codice sorgente, sentiti libero di condividere i vostri pensieri e commenti.

Dino Esposito è l'autore di "Programming Microsoft ASP.NET MVC3 "(Microsoft Press, 2011) e coautore di"Microsoft.NET: Architettura delle applicazioni per l'impresa"(Microsoft Press, 2008). Con sede in Italia, Esposito è un oratore frequenti a eventi del settore in tutto il mondo. È possibile seguire lui su Twitter a twitter.com/despos.

Grazie ai seguenti esperti tecnici per la revisione di questa colonna: Damian Edwards, Phil Haack e Scott Hunter