Easy asynchronous web notifications
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
ASPxClientProgressbarStep 1First 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 2In 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.
Step 3In 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.
Step 4Now 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 5As 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 6To 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 7Finally 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.