Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to send progress updates from a business/model class?

Let's say we have an application that has a layered architecture. On the view we use a MVC or MVVM. The model is treated as the domain, it has a good part of the business logic.

Now let's say we have, in the model, a method that takes some time. A complicated calculation or a treatment that has to be done to each items of an object for example.

In the UI, we would like to display a progress bar and a text that would display the current step of the calculation (for example a listbox with all the process history).

How would you do that? How to send from the model the information of the progress of the process and how to hook up the Controller or ViewModel so that it will update the progress?

like image 881
Gimly Avatar asked Jan 25 '11 11:01

Gimly


4 Answers

I often implement this in the following manner. My business-layer process, which takes a long time to run, raises events every so often to indicate that it's hitting specific "milestones". You decide what milestones to signal through events and how many of them. If your time-consuming process is a plain loop, you may choose, for example, to raise the same event again and again every 10% of the items in the loop. If it is a process with distinct phases, you may choose to raise a different event as each phase is completed.

Now, your presentation layer subscribes to those events and acts in consequence, updating the progress bar, the text or whatever.

This mechanism is good because:

  1. The business layer stays independent of what may go on up in the presentation layer.
  2. It is easy to extend for bi-directional communication. You can easily alter events so that the presentation layer (or any other subscriber) can return a cancel flag so that the business layer knows that a long-running process must be cancelled.
  3. It admits either synchronous or asynchronous work. That is, you can use it on blocking calls (i.e. your presentation and business layer share the same thread) or non-blocking calls (i.e. your business layer uses a background worker thread). The System.ComponentModel.BackgroundWorker class can be used in the latter case, but it's not too good if you want to raise multiple types of events.

Hope this helps.

like image 113
CesarGon Avatar answered Nov 17 '22 06:11

CesarGon


I would recommend looking at the BackgroundWorker class provided in the System.ComponentModel namespace.

  • BackgroundWorker Class (System.ComponentModel)
  • BackgroundWorker Component Overview
  • C# BackgroundWorker Tutorial (winforms example, but the premise holds true)

The background worker provides the methods you need to run your intensive operation on a separate thread, and receive status updates on it's progress (via ReportProgress, ProgressChanged and RunWorkerCompleted).

I actually personally have been experimenting with using the BackgroundWorker in a web environment, for the purpose of running scheduled tasks. I decided to publish the work I've done so far on codeplex. I feel that the spirit of my code could be useful for your situation. 'Web Scheduled Task Framework' codeplex project.

If you choose to download the project, you will see how I am using the BackgroundWorker class in the ScheduledTaskRunner class. My implementation does not attach progress events to the worker, but it would be very easy to do so. Also, my current implementation focuses around running a task on a given interval, but modifying it to be more of an 'on demand' processing queue would not be very difficult. I may even add that as a feature now that I think about it :)

Assuming you followed the approach of my code above, it would be easy to create an action on a controller of yours that went fired would inspect the list of 'tasks' (or a specific task you are interested in) and report the information as some sort of ActionResult. Setup some javascript to poll the action on a specified interval and you'll have your progress!

Good luck and let me know if you have any questions about my code.

like image 22
Nathan Anderson Avatar answered Nov 17 '22 06:11

Nathan Anderson


I took the following approach to a similar case. This view has an action on it that can take a long time and I would like to show progress periodically. The long running action is pushed down into another class, Worker. Some user action initiates the call to DoSomething in TestViewModel.

TestView.xaml

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

TestViewModel.cs extends BaseViewModel, BaseViewModel just implements 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;
    }
}
like image 21
Adam Avatar answered Nov 17 '22 07:11

Adam


Based on your other comments you are trying to keep your business layer as clean as possible.

Then the Model View ViewModel approach may fit: http://en.wikipedia.org/wiki/Model_View_ViewModel

As the calcualtion is done, you throw events that progress has been made.

These events are caught in the ViewModel, and the progress amount updated.

The View is then updated, due to databinding between the ViewModel and the View (Observer Pattern)

like image 26
Shiraz Bhaiji Avatar answered Nov 17 '22 07:11

Shiraz Bhaiji