A couple of days ago, I was asked to figure out if it’s possible to integrate Ext.Net DirectMethods with the ASP.NET asynchronous pages to achieve scalability with long running DirectMethod calls. For the starters, if you are not aware of asynchronous pages in ASP.NET or the overall asynchronous processing support for pages, http handlers and web-services in ASP.NET, first check out these two excellent articles on the topic (available here and here) on MSDN magazine.

Essentially asynchronous processing support in ASP.NET allows you to scale-up your apps with existing server resources by tying up core ASP.NET resources for the minimum possible amount of time, and freeing them by the earliest to handle incoming requests, thus increasing overall server throughput.

My first response was why would you do this? Why have long-running tasks in a page DirectMethod, and why not move them off to a web-service or http handler, because they would suit such tasks better. However, the existing design of the code used DirectMethod calls extensively and moving them off required some refactoring which was not desirable currently, I was informed.

Hmmm.. so my next feedback was, well if that’s a static DirectMethod call, forget about it because static DirectMethod calls do not go through the normal ASP.NET request pipeline eliminating any chances for hooking into the asynchronous framework for pages. And I was told, that these were normal non-static DirectMethod calls, that actually updated the Ext.Net UI controls during the call.

Well then, I said, let’s try it. My first concern was to find out when exactly in the page processing pipeline Ext.Net invokes the desired DirectMethod? And I was able to figure out that this happens after the Load event for components on the page. Straight-away, I saw a ray of hope, because the asynchronous framework required us to register handlers anytime in or before the PreRender event for page components.

So, firstly I created a routine custom IAsyncResult implementation for tracking the progress of the asynchronous processing and notifying ASP.NET appropriately. This is routine plumbing code that you would find attached below.

Then, coming onto the actual code, the real thing was recognizing that Ext.Net calls DirectMethod on the main ASP.NET thread pool thread itself. So, instead of doing processing there, it should be delegated to a background thread as in the code below:

 

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }private AsyncResult sync;
private string someData;

protected void Page_Load (object sender, EventArgs e)
{
if (Ext.Net.X.IsAjaxRequest)
{
this.AddOnPreRenderCompleteAsync(new BeginEventHandler(this.entryPoint), new EndEventHandler(this.exitPoint));
}
}

protected void Page_PreRenderComplete (object sender, EventArgs e)
{
if (Ext.Net.X.IsAjaxRequest)
{
this.label1.Text = this.someData;
}
}

[Ext.Net.DirectMethod(ShowMask = true, Msg = “Please wait for Method 1…”)]
public void directMethod1 ()
{
//Do nothing here.
}

private IAsyncResult entryPoint (object sender, EventArgs e, AsyncCallback cb, object extraData)
{
this.sync = new AsyncResult();
this.sync.beginAsyncProcess(cb, this.processingMethod, extraData);
return (this.sync);
}

private void processingMethod ()
{
//Do your time consuming processing here.
System.Threading.Thread.Sleep(5000);

this.someData = “Hello from async DirectMethod processing…”;

this.sync.endAsyncProcess();
}

private void exitPoint (IAsyncResult result)
{
//Any post processing required should go here.
}{/syntaxhighlighter}

 

This worked perfectly, and we were able to do processing in the background, releasing the ASP.NET thread pool thread for handling further requests. A critical point in the above code is to notice that the controls are not updated from the background thread but in the Page’s PreRenderComplete event. Also, the ordering of events is:

Init > Load > DirectMethod > entryPoint > processingMethod > exitPoint > Page_PreRenderComplete

So, the DirectMethod has already been called when you enter the async processing, and you should extract any required submitted data and save to private page variables in the DirectMethod itself.

 

So far so good, I was told, but what if you have multiple DirectMethods on the page. How do you differentiate between which was called while processing in the background thread.

Simple, I said, maintain a flag variable to indicate which DirectMethod was called. Code below:

 

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }private AsyncResult sync;
private int flag=0;
private string someData;

protected void Page_Load (object sender, EventArgs e)
{
if (Ext.Net.X.IsAjaxRequest)
{
this.AddOnPreRenderCompleteAsync(new BeginEventHandler(this.entryPoint), new EndEventHandler(this.exitPoint));
}
}

protected void Page_PreRenderComplete (object sender, EventArgs e)
{
if (Ext.Net.X.IsAjaxRequest)
{
this.label1.Text = this.someData;
}
}

[Ext.Net.DirectMethod(ShowMask = true, Msg = “Please wait for Method 1…”)]
public void directMethod1 ()
{
this.flag = 1;
}

[Ext.Net.DirectMethod(ShowMask = true, Msg = “Please wait for Method 2…”)]
public void directMethod2 ()
{
this.flag = 2;
}

private IAsyncResult entryPoint (object sender, EventArgs e, AsyncCallback cb, object extraData)
{
this.sync = new AsyncResult();
this.sync.beginAsyncProcess(cb, this.processingMethod, extraData);
return (this.sync);
}

private void processingMethod ()
{
//Do your time consuming processing here.
System.Threading.Thread.Sleep(5000);

if (this.flag == 1)
{
this.someData = “Data returned from async method1”;
}
else if (flag == 2)
{
this.someData = “Data returned from async method2”;
}

this.sync.endAsyncProcess();
}

private void exitPoint (IAsyncResult result)
{
//Any post processing required should go here.
}{/syntaxhighlighter}

The above code is attached as Solution1.zip below.

 

This also works, I was told. But simultaneously they said, this does not look clean, and a solution on the line of asynchronous web services with something like a BeginDirectMethod and a EndDirectMethod call would be the ideal solution.

What the heck, I thought… This works and works perfectly. Still, let’s give the new spec a try too. And so, I created a PageBase class to manage the Begin and End DirectMethod calls. Here’s the code for the PageBase class:

 

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }public class AsyncDirectMethodPageBase : System.Web.UI.Page
{
#region Private Members
//private int flag=0;
//private string someData;
private string directMethodName;
private IAsyncResult result;
#endregion

#region Event Handlers
protected void Page_PreRenderComplete (object sender, EventArgs e)
{
if (Ext.Net.X.IsAjaxRequest && !string.IsNullOrEmpty(this.directMethodName) && this.result != null)
{
string methodName=”End” + this.directMethodName;
this.GetType().InvokeMember(methodName, BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, this, new object[] { this.result });
}
}
#endregion

#region Direct Method support
protected void directMethodCalled ()
{
StackTrace st=new StackTrace();
StackFrame sf=st.GetFrame(1);
MethodBase mb= sf.GetMethod();
this.directMethodName = mb.Name;
this.AddOnPreRenderCompleteAsync(new BeginEventHandler(this.entryPoint), new EndEventHandler(this.exitPoint));
}
#endregion

#region Private async processing
private IAsyncResult entryPoint (object sender, EventArgs e, AsyncCallback cb, object extraData)
{
string methodName=”Begin” + this.directMethodName;
return ((IAsyncResult) this.GetType().InvokeMember(methodName, BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, this, new object[] { sender, e, cb, extraData }));
}

private void exitPoint (IAsyncResult result)
{
this.result = result;
//Any post processing required should go here.
}
#endregion
}{/syntaxhighlighter}

And finally, the .aspx page was also modified as follows:

 

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }private AsyncResult result;

#region Direct Method1
[Ext.Net.DirectMethod(ShowMask = true, Msg = “Please wait for method 1…”)]
public void DirectMethod1 ()
{
base.directMethodCalled();
}

public IAsyncResult BeginDirectMethod1 (object sender, EventArgs e, AsyncCallback cb, object extraData)
{
this.result = new AsyncResult();
this.result.beginAsyncProcess(cb, this.method1Processing, extraData);
return (this.result);
}

private void method1Processing ()
{
System.Threading.Thread.Sleep(5000);
this.result.endAsyncProcess();
}

public void EndDirectMethod1 (IAsyncResult result)
{
this.label1.Text = “Result from Method1”;
}
#endregion

#region Direct Method2
[Ext.Net.DirectMethod(ShowMask = true, Msg = “Please wait for method 2…”)]
public void DirectMethod2 ()
{
base.directMethodCalled();
}

public IAsyncResult BeginDirectMethod2 (object sender, EventArgs e, AsyncCallback cb, object extraData)
{
this.result = new AsyncResult();
this.result.beginAsyncProcess(cb, this.method2Processing, extraData);
return (this.result);
}

private void method2Processing ()
{
System.Threading.Thread.Sleep(5000);
this.result.endAsyncProcess();
}

public void EndDirectMethod2 (IAsyncResult result)
{
this.label1.Text = “Result from Method2”;
}
#endregion{/syntaxhighlighter}

And viola, this works perfectly too. Here, we call a boilerplate method in the base class to notify it that a direct method has been called on the page, which does all the routine plumbing to convert that call into a sequence of Begin and End DirectMethod calls.

The above solution is attached as Solution2.zip below.

However, I would definitely recommend Solution1 as more suitable because Solution2 involves Reflection which can be avoided easily as demonstrated in the first solution. However, any of these you choose, both the solutions enable you to harness the full power of ASP.NET asynchronous pages from your non-static DirectMethod calls.

UPDATE:

  • Sep 17, 2010 – For your long running DirectMethod calls, its important to specify a suitable timeout in your client-side DirectMethod invocation, or your browser will timeout and return an error before your server-side DirectMethod completes its execution. For the complete reference of options available for invoking a DirectMethod together with the timeout option, please see the DirectMethof overview here:
    http://examples.ext.net/#/Events/DirectMethods/Overview/