Introduction
This article shows how to implement a basic timer / escalation framework. This framework allows you to set a due date for something and have some code run when the due date is missed. The framework supports modifying an existing due-date entry so that its due date can be pushed back or forward. The due-date entry can be removed when it is no longer applicable (the work has been completed on time).
Background
I've been developing BPM software using K2 Workflow for a while now. They have the concept of an Escalation on tasks that have been assigned to people so that when the task has not been completed by such a date/time, there is an escalation event. The escalation event could run some code to send an email to the person's manager, for example.
One of the limitations of K2 Workflow's framework is that it does not support changing the due-date of an existing escalation. One of my projects had the requirement of modifying an escalation after the initial due date had been set.
Using the Code
There are only a few classes used in this framework.
The web methods here are the important entry point to the framework.
[WebMethod]
public void AddEscalation(EscalationActionEventArgs args)
{
EscalationManager eMgr = new EscalationManager();
eMgr.AddEscalationEvent(args);
}
[WebMethod]
public void UpdateEscalation(EscalationActionEventArgs args)
{
EscalationManager eMgr = new EscalationManager();
eMgr.UpdateEscalationEvent(args);
}
[WebMethod]
public void RemoveEscalationEvent(EscalationActionEventArgs args)
{
EscalationManager eMgr = new EscalationManager();
eMgr.RemoveEscalationEvent(args);
}
When an escalation is created, the AddEscalation
method is called, passing an EscalationActionEventAgs
object.
The EscalationActionEventAgs
object has the necessary information for the framework to:
- Set the Timer's due date
- Set other data specific to your business problem
- Set the Type of your custom class to invoke when the timer is fired/due date is reached
There is an interface defined, IEscalationAction
, with one method, ProcessEscalationAction()
. Objects of this interface are invoked when the timer is fired, due date is reached. When the due date is reached, you want to do something, that something is defined in your custom class that implements the IEscalationAction
interface. You set the System.Type
information in the EscalationActionEventAgs
object (IEscalationActionFullTypeName
and IEscalationActionAssemblyName
). The class diagram shows an example of a custom class, TestActionOne
. The implementation of the TestActionOne
class will run when the due date is reached.
Updating an Existing Timer
In the situation where a Timer has already been set but later, some business rules dictate that the due date be changed, then a Web service call to UpdateEscalation()
is made. The escalationID
of new due date is set in the EscalationActionEventAgs
object that is passed in. The framework will update the Timer to reflect the changes.
Removing an Existing Timer
In the case where the work has been done and the due date is no longer applicable, the Web method, RemoveEscalation
is called with the EscalationActionEventAgs
argument. The EscalationID
property is used to lookup the existing escalation and the timer is removed.
Creating Your Own Client
- Create a new project for your custom Escalation Action classes.
- Add a reference to the
EscalationFramework
assembly. This contains the IEscalationAction
Interface and the EscalationActionEventAgs
class. - Optionally modify the
EscalationActionEventAgs
class so that it has the fields that are required for your escalation framework. In my example, I've included fields like ActivityName
and EventName
.
- In a robust version of this framework, the
EscalationActionEventAgs
object would be serialized to a database, so make sure that your EscalationActionEventAgs
is serializable.
- Create your implementation of the
IEscalation
interface, providing your custom logic for what to do when the escalation fires in the ProcessEscalationAction()
method. - Compile your project and add the build output DLLs' to the bin directory of the
EscalationService
Web service bin folder. The EscalationService
will load your assembly in order to run your EscalationAction
class when the Timer fires.
Example using K2 Workflow
In this example, the activity, SetInitialDueDate
calls the AddEscalation()
Web method that sets a due date. If that activity is finished, the due-date timer needs to be removed. In that case, the K2 Succeeding Rule would have some code to call the RemoveEscalation()
method.
In the case where that activity is not completed and still pending, the due date can be adjusted or removed.
In this example, the ExtendDueDate
activity has some logic that can check some business logic and modify the due date for the SetInitialDueDate
activity. This would be done by calling the UpdateEscalation()
Web method providing the new due-date and the EscalationID
.
Another reason this framework is compelling is that because it is exposed as a Web Service, any application makes a call to the framework to add, modify or remove an escalation. All that is needed is the EscalationID
, which has the value of whatever you initialized it with.
Additional Details
One class that is not shown on the class diagram is the EscalationManager
. This class is my implementation of the details of the timer framework.
Below are the details of the AddEscalation()
and RemoveEscalation()
methods:
public void AddEscalationEvent(EscalationActionEventArgs args)
{
TimeSpan ts = args.DueDate.Subtract(DateTime.Now);
System.Threading.Timer t = new System.Threading.Timer
(ProcessRecord, args, ts, new TimeSpan(-1));
Timer_Args ta = new Timer_Args();
ta.timer = t;
ta.args = args;
timerStructList.Add(ta);
}
Remove Escalation
public void RemoveEscalationEvent(EscalationActionEventArgs args)
{
for (int i = timerStructList.Count - 1; i >= 0; i++)
{
Timer_Args ta = (Timer_Args)timerStructList[i];
if (ta.args.EscalationId == args.EscalationId)
{
ta.timer.Change(System.Threading.Timeout.Infinite, -1);
ta.timer.Dispose();
timerStructList.RemoveAt(i);
break;
}
}
}
If the framework service dies, all the timers will be lost. Though this is a simple example, a robust solution should store the active timer data in a database so that if the service crashes, there is a way to reinitialize all the timers.
Conclusion
I believe this framework can provide a useful part in a BPM solution where notifications must be managed with a BPM server that has limitations in the flexibility of their notification framework.
History
- 10th June, 2007: Initial post
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.