Wednesday, February 24, 2010

Keeping focus on AJAX elements in an update panel

In a recent project a had to create an updatepanel in ASP.NET.
The creation of the panel was not the problem, the panel had a couple of textboxes and when the user 'tabs' between them, then a server-call was done to validate the input through a service.

It was an address-validation control. User inputs his address and it is checked on the fly against a service.

The problem was that when the user tabs, and the AJAX callback came back, then the focus of the next selected element was lost, resulting in irritating behavior that the user had to click with his mouse to the next field.

After a lot of searching I came up with a solution.

"Record which element has focus and put the focus back after the AJAX library destroyed the panel and rebuilds all the controls."
Let's jump into code:
- First I had to register a little script in the OnInit function of the ASP.NET Page:

protected override void OnInit(EventArgs e)
{
base.OnInit(e);

string scriptToRegister = "function SaveFocus(fieldId) { lastField"
+ UpdatePanel1.ClientID + " = fieldId; }
";

ScriptManager.RegisterClientScriptBlock(this, typeof(UserControl), "SaveScriptAddress", scriptToRegister, true);
....
}

Then I had to add this script as an attribute to all the textboxes in the updatepanel, that needed the focus. Those elements get updated when the ajax call returns.


protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// onfocus=""
txtBoxNumber.Attributes.Add("onfocus", "SaveFocusAdv('" + txtBoxNumber.ClientID + "')");
txtHouseNumber.Attributes.Add("onfocus", "SaveFocusAdv('" + txtHouseNumber.ClientID + "')");
txtZipcode.Attributes.Add("onfocus", "SaveFocusAdv('" + txtZipcode.ClientID + "')");
txtddlStreets.Attributes.Add("onfocus", "SaveFocusAdv('" + txtddlStreets.ClientID + "')");
txtddlCities.Attributes.Add("onfocus", "SaveFocusAdv('" + txtddlCities.ClientID + "')");
txtddlStreets.Focus();
}
....
}


The C# coding is done at this point. All I had to do next was inserting a little javascript snippet in the ASP.NET Usercontrol markup:
This script was placed outside of the updatepanel! Because it may not be destroyed when the partial rendering kicks in.
Put the following code between a script tag:

var lastField<%= UpdatePanel1.ClientID %>;
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequestHandler<%= UpdatePanel1.ClientID %>);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler<%= UpdatePanel1.ClientID %>);

function BeginRequestHandler<%= UpdatePanel1.ClientID %>(sender, args) {
document.body.style.cursor = 'wait';
}
function EndRequestHandler<%= UpdatePanel1.ClientID %>(sender, args) {
document.body.style.cursor = 'default';
if(lastField<%= UpdatePanel1.ClientID %> != undefined)
{
setTimeout('DoFocusOnLastElement(lastField<%= UpdatePanel1.ClientID %>)', 1);
}
}
function DoFocusOnLastElement( fieldId )
{
var element = document.getElementById(fieldId);
if(element != null) element.focus();
}

That's it, now the 'tabbing' will occur normally, like nothing else happens....
When the service takes a little longer then expected, then the user input is replaced by the content that the service sends back (like a city name) but that's not really a probem.
I hope this can be usefull to someone ;)
Regards,
W.

Sunday, January 24, 2010

How to Track the RuleEngine rules in WF without starting a workflow runtime

Gents,

Recently I had a problem at hands, I wanted to run run the RuleEngine from Workflow Foundation outside WF.
It is possible and easy to run the RuleEngine outside of WF, but the possibility to track which rules were executed was skipped, as far as I found and saw in the API.

You can track executed rules when you run the workflow runtime itself and attach a TrackingService, but it came out to slow in the test we did.

The problem was that every time we called a WCF service method, the runtime was started for executing a couple of rules, sounds like overkill to me.
So I created a little ‘hack’ to execute the rules with tracking without actually starting the workflow.

Let’s jump into code :)

I created a RuleHelper<T> that executes my rules.
Below you can find the execute method of the helper class:

public void Execute(T objectToRunRulesOn, bool trackData)
{
if (trackData)
{
// Create dummy ActivityExecutionContext and see that trackData is intercepted.
// Initialize with own activity and insert IWorkflowCoreRuntime

Type activityExecutionContext = typeof(Activity).Assembly.GetType("System.Workflow.ComponentModel.ActivityExecutionContext");
var ctor = activityExecutionContext.GetConstructor(BindingFlags.Instance BindingFlags.NonPublic, null,
new[] { typeof(Activity) }, null);
var activity = new InterceptingActivity();
var context = ctor.Invoke(new object[] { activity });

_ruleEngine = new RuleEngine(_ruleSet, typeof(T));
lock (SyncRoot)
{
InterceptingActivity.Track += InterceptingActivity_Track;
_ruleEngine.Execute(objectToRunRulesOn, (ActivityExecutionContext) context);
InterceptingActivity.Track -= InterceptingActivity_Track;
}
}
else
{
Execute(objectToRunRulesOn);
}
}


The intercepting activity does actually all the work.

We first create our own (dummy) ActiviyExecutionContext, problem was, that it is an ‘internal’ class.

The argument to the context is our own InterceptingActivity.
After the instantiation we create our RuleEngine (from the WF assemblies) with a loaded or created RuleSet and the type where to run the RuleSet on.
Then some plumbing code for multiple threads (SyncRoot) and then we attach a static event to the activity.
Next we will execute the rules with the RuleEngine on the specified object that needs checking, and with the self created context which has knowledge of our own created Activity.

Let’s see what the InterceptingActivity does:

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Workflow.Activities.Rules;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Runtime;

namespace Whizzo.Framework.Rules
{
public class InterceptingActivity : Activity
{
// Some caching variables (ExecutionEvent)
private static FieldInfo _argsFieldInfo;
// Some caching variables (InjectAllTheNeededHandlers)
private static ConstructorInfo _executorCtr;
private static PropertyInfo _currActivity;
private static Delegate _handlerDelegate;
private static FieldInfo _workflowExecutionEventFieldInfo;
private static FieldInfo _workflowCoreRuntimeFieldInfo;

// Static event for tracking rules
public static event EventHandler<TrackingEventArgs> Track;

public InterceptingActivity()
: base("InterceptingActivity")
{
InjectAllTheNeededHandlers();
}

private void InjectAllTheNeededHandlers()
{
if (_handlerDelegate == null)
{
// Get the type of the WorkflowExecutor
Type executorType =
typeof (WorkflowEventArgs).Assembly.GetType("System.Workflow.Runtime.WorkflowExecutor");
// Get eventargs type, the event and the handler type
Type eventTypeType =
typeof (WorkflowEventArgs).Assembly.GetType(
"System.Workflow.Runtime.WorkflowExecutor+WorkflowExecutionEventArgs");
EventInfo evt = executorType.GetEvent("WorkflowExecutionEvent",
BindingFlags.Instance BindingFlags.NonPublic);
Type handlerType = TypeProvider.GetEventHandlerType(evt);
// Get current activity of WorkflowExecutor
_currActivity = executorType.GetProperty("CurrentActivity",
BindingFlags.Instance BindingFlags.NonPublic);
// Get the constructor
_executorCtr = executorType.GetConstructor(BindingFlags.Instance BindingFlags.NonPublic, null,
new[] {typeof (Guid)}, null);

// Get field which has the event handler
_workflowExecutionEventFieldInfo = executorType.GetField("_workflowExecutionEvent",
BindingFlags.Instance BindingFlags.NonPublic);
// Get workflowCoreRuntime field of activity
_workflowCoreRuntimeFieldInfo = typeof (Activity).GetField("workflowCoreRuntime",
BindingFlags.Instance BindingFlags.NonPublic);

// Create dynamic method in module of workflow
Module m = typeof (WorkflowEventArgs).Assembly.GetModules()[0];
DynamicMethod dm = new DynamicMethod("MyHandler", null, new[] {typeof (object), eventTypeType}, m, true);
MethodInfo execMethod = GetType().GetMethod("ExecutionEvent");

// Generate method body
ILGenerator ilgen = dm.GetILGenerator();
ilgen.Emit(OpCodes.Nop);
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Ldarg_1);
ilgen.Emit(OpCodes.Call, execMethod);
ilgen.Emit(OpCodes.Nop);
ilgen.Emit(OpCodes.Ret);

// Create delegate
_handlerDelegate = dm.CreateDelegate(handlerType);
}

// Create instance of WorkflowExecutor
object executor = _executorCtr.Invoke(new object[] { Guid.NewGuid() });
// Set current activity of WorkflowExecutor
_currActivity.SetValue(executor, this, null);

// Attach delegate to event
_workflowExecutionEventFieldInfo.SetValue(executor, _handlerDelegate);

// Set executor as workflowCoreRuntime
_workflowCoreRuntimeFieldInfo.SetValue(this, executor);
}

public static void ExecutionEvent(object sender, EventArgs eventArgs)
{
if(Track != null)
{
if (_argsFieldInfo == null)
{
_argsFieldInfo = eventArgs.GetType().GetField("_args",
BindingFlags.NonPublic BindingFlags.Instance);
}
var argsValue = _argsFieldInfo.GetValue(eventArgs);
// Extract args
RuleActionTrackingEvent args = (RuleActionTrackingEvent) argsValue;
Track(sender, new TrackingEventArgs(args));
}
}
}
}

InjectAllTheNeededHandlers is the most important method,
it will create a System.Workflow.Runtime.WorkflowExecutor and it will initialize all it’s needed variables to be able to execute the rules.

It will also attach a self created DynamicMethod converted to a Delegate as an event handler in the WorkflowExecutor.

The event will be fired by the executor (internal class of Microsoft) and this way we can intercept the result of the executed rules.

The method that will get fired is ExecutionEvent, which will fire another event if attached (in the Execute method of the helper) and which will pass the RuleName and the Result of execution.

Voila, hopefully this was a little bit clear :)

There are probably other ways to do this (without starting the runtime) but this way was good enough for me :)

At a blog (http://blogs.msdn.com/moustafa/archive/2006/08/05/689776.aspx) I also found another method to track the executed rules outside the workflow,
But it involved Tracing while this method uses the tracking inside the workflow, without actually executing it. It's a lot simpler, and probably cleaner.

You will have to enable tracing for WF and write a custom TraceListener.

But I prefer to do it without diagnostics tracing put on.

Some feedback in the comments indicated that there were a couple of thing short in the code I displayed above. I'll try to complete it a little more.

This is the TrackingEventArgs class, used for triggering the 'Track' event, which we subscribe to, to track the rules.



using System;
using System.Workflow.Activities.Rules;

namespace Whizzo.Framework.Rules
{
public class TrackingEventArgs : EventArgs
{

public TrackingEventArgs(RuleActionTrackingEvent args, string rulesetName)
{
Args = args;
RulesetName = rulesetName;
}

public RuleActionTrackingEvent Args { get; private set; }

public string RulesetName { get; private set; }

}
}

This is the complete RuleHelper class, I will show below how to call it:


using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Workflow.Activities.Rules;
using System.Workflow.Activities.Rules.Design;
using System.Workflow.ComponentModel;
using System.Reflection;
using System.Collections.Generic;

namespace Whizzo.Framework.Rules
{
public class RuleHelper<T>
{
private RuleSet _ruleSet;
private RuleEngine _ruleEngine;
private static readonly object SyncRoot = new object();
private List<TrackingEventArgs> _ruleMessages;

public RuleHelper()
{
_ruleMessages = new List<TrackingEventArgs>();
}

public void SetRules(RuleSet ruleSet)
{
_ruleSet = ruleSet;
}

public void Execute(T objectToRunRulesOn)
{
_ruleEngine = new RuleEngine(_ruleSet, typeof(T));
_ruleEngine.Execute(objectToRunRulesOn);
}

public void Execute(T objectToRunRulesOn, bool trackData)
{
if (trackData)
{
// Create dummy ActivityExecutionContext and see that trackData is intercepted.
// Initialize with own activity and insert IWorkflowCoreRuntime

Type activityExecutionContext = typeof(Activity).Assembly.GetType("System.Workflow.ComponentModel.ActivityExecutionContext");
var ctor = activityExecutionContext.GetConstructor(BindingFlags.Instance BindingFlags.NonPublic, null,
new[] { typeof(Activity) }, null);
var activity = new InterceptingActivity();
var context = ctor.Invoke(new object[] { activity });

_ruleEngine = new RuleEngine(_ruleSet, typeof(T));
lock (SyncRoot)
{
InterceptingActivity.Track += InterceptingActivity_Track;
_ruleEngine.Execute(objectToRunRulesOn, (ActivityExecutionContext)context);
InterceptingActivity.Track -= InterceptingActivity_Track;
}
}
else
{
Execute(objectToRunRulesOn);
}
}

public List<TrackingEventArgs> GetRuleMessages()
{
return _ruleMessages;
}

private void InterceptingActivity_Track(object sender, TrackingEventArgs e)
{
#if DEBUG
Console.WriteLine("{0} Rule result of {1} = {2}", e.RulesetName, e.Args.RuleName, e.Args.ConditionResult);
#endif
_ruleMessages.Add(e);
}
}
}

To use the RuleHelper<>, do something like this, it's pretty self-explainatory:

// Call the rules helper
RuleHelper ruleHelper = new RuleHelper();
// Set the rules
ruleHelper.SetRules(ruleSet);
// Execute the rules.
ruleHelper.Execute(rr, true);
// Get the tracked rules
var ruleMessages = ruleHelper.GetRuleMessages();

Regards,

W.