Easy asynchronous web notifications

Έχουν δημοσιευτεί 11 Οκτωβρίου 18 06:05 πμ | tolisss 
Today we will discuss another common case, how to create a progress bar to notify our web users about the state of their long running tasks. For this discussion we will work with  DevExpress XAF Business Application Framework. We will develop ONE SMALL CLASS, we can then copy paste to any XAF project.

It is a good idea before starting any work to search the Support Center for ideas and ask the DevExpress support guys. Doing so we found many tickets with ready to work solutions. Some of them are good candidates e.g How to start a long running operation in ASP.NET application using the ThreadPool.QueueUserWorkItem method and show its progress in a browser using a WebService method.

The problem with the previous sample , is that uses a WebService to periodically call back in a Controller. I wanted to create a reusable implementation inside a library and a WebService cannot live in a library.
But I got the idea on how to proceed. I just need to create a ViewItem to host a ASPxProgressBar and with periodic call-backs I will inject JavaScript code to SetPosition on the client side ASPxClientProgressbar

Step 1
First we need a sample project so let’s use the XAF New Solution Wizard to create a new Web Project. We do not need any extra modules or security, just create it as simple as possible. After the solution is created add a very simple Domain Object so XAF can generate a web View for it.

    [DefaultClassOptions]
public class MyObject : BaseObject {
public MyObject(Session session) : base(session) { }
}
Step 2
In addition we  need create a sequence of Tasks that will return the state of our work. You can use any technology you prefer e.g. webservices, TPL tasksetc. as long as it returns asynchronously it fits our case. For this discussion I will use the System.Reactive library. To create the sequence the next line will be enough.

Observable
.Interval(TimeSpan.FromMilliseconds(1000))
.Subscribe(l => Console.WriteLine($"Task {l} completed on Thread:Environment.CurrentManagedThreadId}"));

If you want to test how it behaves add it in a console app and you should see the following output.

image

Step 3
In XAF we use Controllers to communicate with our Views so let’s create a very simple Controller and add our sequence and later connect the progress bar.

    public class MyController : ViewController<DetailView> {
public MyController() {
var action = new SimpleAction(this, "StartLongOperation", PredefinedCategory.Tools);
action.Execute += action_Execute;
}

void action_Execute(object sender, SimpleActionExecuteEventArgs e) {
Observable.Interval(TimeSpan.FromMilliseconds(1000)).Subscribe();
}
}

This controller declares an StartLongOperation Action and starts our sequence, exactly as described on XAF docs.
Currently XAF already generated the web UI and actions. Here how the DetailView of MyObject looks like.
image

Step 4
Now its time to create the progress bar container. For this scenario we do not need to alter the state of MyObject  so we will follow the XAF docs on how to create a ViewItem rather than a PropertyEditor. (How to implement a ViewItem). Our starting ViewItem comes next:

    public interface IModelProgressViewItem : IModelViewItem {
}

[ViewItem(typeof(IModelProgressViewItem))]
public class ProgresViewItem : ViewItem {
public ASPxProgressBar ProgressBar{ get; private set; }

public ProgresViewItem(IModelProgressViewItem info, Type classType)
: base(classType, info.Id){
}

protected override object CreateControlCore() {
ProgressBar = new ASPxProgressBar();
return ProgressBar;
}

}

We override the CreateControlCore method and just return an ASPxProgressBar component included in DevExpress suite.
Step 5
As I mentioned before this sample (How to start a long running operation in ASP.NET application using the ThreadPool.QueueUserWorkItem method and show its progress in a browser using a WebService method) use a Javascript  Timer to periodically call a WebService which communicates with a XAF Controller.

Our scenario is very similar but we need to remove the dependency to the WebService because we need to push the implementation to our ExcelImporter module that is part of the eXpandFramework and is very hard to host a WebService in a library so it can be reusable and with friction-less installation.

So let’s introduce the Javascript timer in our ProgressViewItem and use Callbacks to notify the server instead of the WebService used in the SC sample. This is as always well document in the XAF docs (How to: Raise XAF Callbacks from Client-Side Events and Process these Callbacks on Server).

        private XafCallbackManager CallbackManager => ((ICallbackManagerHolder)WebWindow.CurrentRequestPage).CallbackManager;
public int PollingInterval{ get; set; }
public void Start(int maximum){
var script = CallbackManager.GetScript(_handlerId, $"'{ProgressBar.ClientInstanceName}'","",false);
ProgressBar.ClientSideEvents.Init =
$@"function(s,e) {{
if(window.timer) window.clearInterval(window.timer);
var controlToUpdate = s;
window.timer = window.setInterval(function(){{
var previous = startProgress;startProgress = function () {{ }}; //this line disables the Loading Panel see Q427477 in SC
{script}startProgress = previous;}},
{PollingInterval});}}"
;
}
In short the Start method use the build-in XAF CallBackManager to generate a script with one parameter the ProgressBar.ClientInstance name. We pass this parameter because we may want to use multiple progress-bars in the same view. Next the timer calls this script every PollingInterval.

Whats left is to implement the IXafCallbackHandler as shown.

        public long Position{ get; set; }
public void ProcessAction(string parameter){
var script = $"{parameter}.SetPosition('{Position}')";
WebWindow.CurrentRequestWindow.RegisterStartupScript(_handlerId,script,true);
}
Here we just created a script that uses the client side ASPxProgressBar API to SetPosition based on the new ProgressViewItem Position property.
Step 6
To consume the ProgressViewItem we modify the MyController defined in Step 3 like:

        void action_Execute(object sender, SimpleActionExecuteEventArgs e) {
var progresViewItem = View.GetItems<ProgresViewItem>().First();
progresViewItem.Start(maximum:100);//Start the timer
Observable
.Interval(TimeSpan.FromMilliseconds(1000))
.Subscribe(l => progresViewItem.Position=l );//Update the position for each Task
}
Step 7
Finally let's add our ProgressViewItem to the a View. We will use the Model editor to create it and drag & drop to the Layout.

Below you can see how the ProgressViewItem works in runtime in our sample solution.

I wrote this post as a proof of concept rather than a complete implementation, so I am not posting any samples. However you can download the the complete ProgressViewItem used if  you wish from this gist.
Below you can verify that it works fine in a real world complex module like the ExcelImporter.


XAF can do Mobile and Windows as well, a Mobile implementation for this scenario does not make much sense but have a look how it looks in the Windows platform.
Δημοσίευση στην κατηγορία: ,

Σχόλια:

Χωρίς Σχόλια
Έχει απενεργοποιηθεί η προσθήκη σχολίων από ανώνυμα μέλη

Search

Go

Το Ιστολόγιο

Ιστορικό Δημοσιεύσεων

Συνδρομές