2011-01-25 5 views
7

Digamos que tenemos una aplicación que tiene una arquitectura en capas. En la vista usamos MVC o MVVM. El modelo se trata como el dominio, tiene una buena parte de la lógica comercial.¿Cómo enviar actualizaciones de progreso de una clase de negocio/modelo?

Ahora digamos que tenemos, en el modelo, un método que lleva algún tiempo. Un cálculo complicado o un tratamiento que tiene que hacerse para cada elemento de un objeto, por ejemplo.

En la interfaz de usuario, nos gustaría mostrar una barra de progreso y un texto que mostraría el paso actual del cálculo (por ejemplo, un cuadro de lista con todo el historial de procesos).

¿Cómo harías eso? ¿Cómo enviar desde el modelo la información del progreso del proceso y cómo conectar el Controlador o ViewModel para que actualice el progreso?

Respuesta

5

A menudo implemento esto de la siguiente manera. El proceso de mi capa empresarial, que demora mucho tiempo en ejecutarse, plantea eventos cada cierto tiempo para indicar que está alcanzando "hitos" específicos. Usted decide qué hitos señalar a través de los eventos y cuántos de ellos. Si su proceso que consume mucho tiempo es un ciclo simple, puede elegir, por ejemplo, plantear el mismo evento una y otra vez cada 10% de los elementos en el ciclo. Si se trata de un proceso con distintas fases, puede optar por plantear un evento diferente a medida que se completa cada fase.

Ahora, su capa de presentación se suscribe a esos eventos y actúa en consecuencia, actualizando la barra de progreso, el texto o lo que sea.

Este mecanismo es bueno porque:

  1. La capa de negocio se mantenga independiente de lo que puede ir de arriba en la capa de presentación.
  2. Es fácil de extender para la comunicación bidireccional. Puede modificar fácilmente los eventos para que la capa de presentación (o cualquier otro suscriptor) pueda devolver un indicador de cancelación para que la capa empresarial sepa que se debe cancelar un proceso prolongado.
  3. Admite trabajo síncrono o asíncrono. Es decir, puede usarlo para bloquear llamadas (es decir, la presentación y la capa empresarial comparten el mismo hilo) o llamadas sin bloqueo (es decir, su capa empresarial utiliza un hilo de trabajador en segundo plano). La clase System.ComponentModel.BackgroundWorker se puede utilizar en este último caso, pero no es demasiado buena si quiere plantear varios tipos de eventos.

Espero que esto ayude.

+0

Gracias por la respuesta muy completa.Tenía la visión de que los eventos eran una cuestión muy técnica que no tenía su lugar en la capa de negocios. Pero al final tiene que haber una forma de enviar información sobre cómo va el proceso. – Gimly

+0

@Gimly: De nada. Creo que los eventos son útiles en la capa empresarial para implementar objetos "activos"; el escenario en su pregunta es un buen ejemplo. – CesarGon

0

Deberá explorar el patrón Observer (http://en.wikipedia.org/wiki/Observer_pattern). Es un enfoque bastante común para aplicaciones de escritorio. Es un poco más complejo para la Web. También puede consultar Comet [http://en.wikipedia.org/wiki/Comet_(programming)] para ver cómo se hace para la Web.

+0

pensé en el patrón de observador por supuesto, ya que es bastante común en .Net. Pero no creo que sea correcto, porque significaría tener cosas técnicas en el negocio, lo que me hace sentir mal. – Gimly

0

Tomé el siguiente enfoque para un caso similar. Esta vista tiene una acción que puede llevar mucho tiempo y me gustaría mostrar el progreso periódicamente. La acción de larga duración se reduce a otra clase, Trabajador. Algunas acciones del usuario inician la llamada al DoSomething en TestViewModel.

TestView.xaml

... 
<!-- Progress bar --> 
<ProgressBar Visibility="Visible" Height="10" Value="{Binding SomeValue}"/> 
... 

TestViewModel.cs extiende BaseViewModel, BaseViewModel simplemente implementa INotifyPropertyChanged

... 
private void DoSomething(){ 
    Worker worker = new Worker(); 
    worker.ProgressChanged += new EventHandler<WorkerEventArgs>(OnProgressChanged); 
    worker.Start(); 
} 

private void OnProgressChanged(object sender, WorkerEventArgs args){ 
    SomeValue = args.Progress; 
} 

private const String SomeValuePropertyName = "SomeValue"; 
private double someValue; 
public double SomeValue 
{ 
    get 
    { 
     return someValue; 
    } 
    set 
    { 
     if (someValue == value) 
     { 
      return; 
     } 
     someValue = value; 
     NotifyPropertyChanged(SomeValuePropertyName); 
    } 
} 
... 

Worker.cs

... 
public event EventHandler<WorkerEventArgs> ProgressChanged; 
public void Start(){ 
    //This will take a long time. Periodically call NotifyProgress 
} 

private void NotifyProgress() 
{ 
    if (ProgressChanged != null) 
    { 
     double progress = ...; //calculate progress 
     ProgressChanged(this, new WorkerEventArgs(progress)); 
    } 
} 
... 

WorkerEventArgs.cs

public class WorkerEventArgs : EventArgs 
{ 
    public double Progress { get; private set; } 

    public WorkerEventArgs(double progress) 
    { 
     Progress = progress; 
    } 
} 
2

Yo recomendaría que buscan en la clase BackgroundWorker proporcionado en el espacio de nombres System.ComponentModel.

El trabajador fondo proporciona los métodos que necesita para ejecutar su operación intensiva en un hilo separado, y recibir actualizaciones de estado en su progreso (a través de ReportProgress, ProgressChanged y RunWorkerCompleted).

En realidad, personalmente he estado experimentando con el uso del BackgroundWorker en un entorno web, con el fin de ejecutar tareas programadas. Decidí publicar el trabajo que he hecho hasta ahora en Codeplex. Siento que el espíritu de mi código podría ser útil para tu situación. 'Web Scheduled Task Framework' codeplex project.

Si elige descargar el proyecto, verá cómo estoy usando la clase BackgroundWorker en la clase ScheduledTaskRunner. Mi implementación no adjunta eventos de progreso al trabajador, pero sería muy fácil hacerlo. Además, mi implementación actual se enfoca en ejecutar una tarea en un intervalo dado, pero modificarla para que sea más una cola de procesamiento 'a pedido' no sería muy difícil. Incluso puedo agregar eso como una característica ahora que lo pienso :)

Asumiendo que seguiste el enfoque de mi código anterior, sería fácil crear una acción en un controlador tuyo que fue despedido inspeccionaría la lista de 'tareas' (o una tarea específica que le interese) e informe la información como un tipo de ActionResult. ¡Configura algunos javascript para sondear la acción en un intervalo específico y tendrás tu progreso!

Buena suerte y avíseme si tiene alguna pregunta sobre mi código.

1

Según sus otros comentarios, trata de mantener la capa empresarial lo más limpia posible.

Entonces el enfoque de modelo Ver modelo de vista puede encajar: http://en.wikipedia.org/wiki/Model_View_ViewModel

Como se realiza la calcualtion, se lanza eventos que se han realizado progresos.

Estos eventos se detectan en ViewModel y se actualiza el importe del progreso.

la vista se actualiza entonces, debido al enlace de datos entre el modelo de vista y la Vista (patrón de observador)

Cuestiones relacionadas