Scheduling workflows in eXpandFrameWork

XAF Team Blog
05 September 2011

Let me describe for a moment how we at DevExpress work. We build and sell software which means that we only sell and provide support for products that have been built and tested by us! However I am here as a framework evangelist and huge XAF fan. This makes it my duty to spread the word as much as I can and make XAF even bigger. To this end through collaboration within the XAF community, we have been building and supporting eXpand. This framework follows XAF to the letter and takes things even further. eXpand gets its inspiration from real life situations and bases itself on examples from DevExpress Support Center. eXpand is the first open source project based on the DevExpress eXpressApp Framework (XAF). More info is available at www.expandframework.com and our very existence relies on your efforts! Anyone is welcome to contribute and enjoy the rewards. It is not necessary to be a XAF guru, we can all manage to create a behavior taken from DevExpress code central. Let’s work together to enhance our beloved XAF!

In this post we are going to extend the functionality of the workflow module. to create a UI that will help us to schedule workflows. Some of you may recall that we looked at using a Delay inside a While activity in Working with CRUD activities – Short Transactions. Recently, DX-Squad member Martin Praxmarer raised an interesting question relating to this topic:

I have the requirement to do a workflow which starts each day on 6 clock - searches for orderdocuments where a specific date is less then X days. I know I will do an do while loop, but workflow definition has 2 options, start when new object, start when criteria, so when do i start this Workflow?

In order to achieve this we will use the WorkFlow demo that ships with our framework. The first thing is to design our custom ScheduledWorkflow persistent object by implementing IWorkflowDefinition. We didn’t derive it from the existing WorkFlowDefinition object because it has properties like TargetObjectType and start up conditions.

public enum StartMode {

    OneTime,

    Daily,

    Weekly

}

 

[DefaultClassOptions]

[Appearance("WeekDays", "StartMode <> 'Weekly'",

    TargetItems = "RecurEveryWeeks;Moday;Tuesday;Wednesday;Thursday;Friday;Saturday;Sunday",

    Visibility = ViewItemVisibility.Hide)]

public class ScheduledWorkflow : BaseObject, IWorkflowDefinition {

    public ScheduledWorkflow(Session session)

        : base(session) {

    }

 

    public bool IsActive {

        get { return GetPropertyValue<bool>("IsActive"); }

        set { SetPropertyValue("IsActive", value); }

    }

 

    public bool RuntASAPIfScheduledStartIsMissed {

        get { return GetPropertyValue<bool>("RuntASAPIfScheduledStartIsMissed"); }

        set { SetPropertyValue("RuntASAPIfScheduledStartIsMissed", value); }

    }

 

    [Association]

    public XPCollection<ScheduledWorkflowLaunchHistory> LaunchHistoryItems {

        get { return GetCollection<ScheduledWorkflowLaunchHistory>("LaunchHistoryItems"); }

    }

 

    [ImmediatePostData]

    public StartMode StartMode {

        get { return GetPropertyValue<StartMode>("StartMode"); }

        set { SetPropertyValue("StartMode", value); }

    }

 

    public TimeSpan StartTime {

        get { return GetPropertyValue<TimeSpan>("StartTime"); }

        set { SetPropertyValue("StartTime", value); }

    }

 

    [Appearance("RecurEveryDays", "StartMode <> 'Daily'", Visibility = ViewItemVisibility.Hide)]

    public int RecurEveryDays {

        get { return GetPropertyValue<int>("RecurEveryDays"); }

        set { SetPropertyValue("RecurEveryDays", value); }

    }

 

    public int RecurEveryWeeks {

        get { return GetPropertyValue<int>("RecurEveryWeeks"); }

        set { SetPropertyValue("RecurEveryWeeks", value); }

    }

 

    public bool Monday {

        get { return GetPropertyValue<bool>("Monday"); }

        set { SetPropertyValue("Monday", value); }

    }

 

    public bool Tuesday {

        get { return GetPropertyValue<bool>("Tuesday"); }

        set { SetPropertyValue("Tuesday", value); }

    }

 

    public bool Wednesday {

        get { return GetPropertyValue<bool>("Wednesday"); }

        set { SetPropertyValue("Wednesday", value); }

    }

 

    public bool Thursday {

        get { return GetPropertyValue<bool>("Thursday"); }

        set { SetPropertyValue("Thursday", value); }

    }

 

    public bool Friday {

        get { return GetPropertyValue<bool>("Friday"); }

        set { SetPropertyValue("Friday", value); }

    }

 

    public bool Saturday {

        get { return GetPropertyValue<bool>("Saturday"); }

        set { SetPropertyValue("Saturday", value); }

    }

 

    public bool Sunday {

        get { return GetPropertyValue<bool>("Sunday"); }

        set { SetPropertyValue("Sunday", value); }

    }

    #region IWorkflowDefinition Members

    public string GetActivityTypeName() {

        return GetUniqueId();

    }

 

    public IList<IStartWorkflowCondition> GetConditions() {

        return new IStartWorkflowCondition[0];

    }

 

    public string GetUniqueId() {

        if (Session.IsNewObject(this)) {

            throw new InvalidOperationException();

        }

        return "ScheduledWorkflow" + Oid.ToString().ToUpper().Replace("-", "_");

    }

 

    [Browsable(false)]

    public bool CanCompile {

        get { return false; }

    }

 

    [Browsable(false)]

    public bool CanOpenHost {

        get { return IsActive && !string.IsNullOrEmpty(Name); }

    }

 

    public string Name {

        get { return GetPropertyValue<string>("Name"); }

        set { SetPropertyValue("Name", value); }

    }

 

    [Size(SizeAttribute.Unlimited)]

    public string Xaml {

        get { return GetPropertyValue<string>("Xaml"); }

        set { SetPropertyValue("Xaml", value); }

    }

    #endregion

    public override void AfterConstruction() {

        base.AfterConstruction();

        Xaml = DCWorkflowDefinitionLogic.InitialXaml;

    }

}

 

In the above class we have added some scheduled specific properties such as StartMode, StartTime, RecurEveryDays etc. The class has been decorated with the Appearance attribute to control the visibility of the Day properties. This means when StartMode <> 'Weekly these properties will be hidden.

Moreover there is a collection LaunchHistoryItems of ScheduledWorkflowLaunchHistory objects, which will be used later to check if the workflow has been launched.

public class ScheduledWorkflowLaunchHistory : BaseObject {

    public ScheduledWorkflowLaunchHistory(Session session) : base(session) {}

    public DateTime LaunchedOn {

        get { return GetPropertyValue<DateTime>("LaunchedOn"); }

        set { SetPropertyValue<DateTime>("LaunchedOn", value); }

    }

    [Association]

    public ScheduledWorkflow Workflow {

        get { return GetPropertyValue<ScheduledWorkflow>("Workflow"); }

        set { SetPropertyValue<ScheduledWorkflow>("Workflow", value); }

    }

}

 

After designing these classes, we now have all the required input in order to schedule our workflows.

The next step is to load our custom workflows by extending the workflow provider service as shown,

public class ScheduledWorkflowDefinitionProvider : WorkflowDefinitionProvider {

    public ScheduledWorkflowDefinitionProvider(Type workflowDefinitionType) : base(workflowDefinitionType) { }

    public ScheduledWorkflowDefinitionProvider(Type workflowDefinitionType, IObjectSpaceProvider objectSpaceProvider) : base(workflowDefinitionType, objectSpaceProvider) { }

    public override IList<IWorkflowDefinition> GetDefinitions() {

        IList<IWorkflowDefinition> result = base.GetDefinitions();

        IObjectSpace objectSpace = ObjectSpaceProvider.CreateObjectSpace(); //don't dispose immediately

        foreach(ScheduledWorkflow workflow in objectSpace.GetObjects<ScheduledWorkflow>()) {

            result.Add(workflow);

        }

        return result;

    }

}

 

After this we are ready to implement our final service that will schedule our workflows,

public class ScheduledWorkflowStartService : BaseTimerService {

    private bool NeedToStartWorkflow(IObjectSpace objectSpace, ScheduledWorkflow workflow) {

        if (workflow.StartMode == StartMode.OneTime) {

            if (workflow.LaunchHistoryItems.Count == 0) {

                return true;

            }

        } else if (workflow.StartMode == StartMode.Daily) {

            var historyItem = objectSpace.FindObject<ScheduledWorkflowLaunchHistory>(CriteriaOperator.Parse("GetDate(LaunchedOn) = ?", DateTime.Today));

            if (historyItem == null && DateTime.Now.TimeOfDay > workflow.StartTime) {

                return true;

            }

        } else if (workflow.StartMode == StartMode.Weekly) {

            throw new NotImplementedException();

        }

        return false;

    }

    public ScheduledWorkflowStartService()

        : base(TimeSpan.FromMinutes(1)) {

    }

    public ScheduledWorkflowStartService(TimeSpan requestsDetectionPeriod) : base(requestsDetectionPeriod) { }

    public override void OnTimer() {

        using (IObjectSpace objectSpace = ObjectSpaceProvider.CreateObjectSpace()) {

            foreach (ScheduledWorkflow workflow in objectSpace.GetObjects<ScheduledWorkflow>(new BinaryOperator("IsActive", true))) {

                WorkflowHost host;

                if (HostManager.Hosts.TryGetValue(workflow.GetUniqueId(), out host)) {

                    if (NeedToStartWorkflow(objectSpace, workflow)) {

                        host.StartWorkflow(new Dictionary<string, object>());

                        var historyItem = objectSpace.CreateObject<ScheduledWorkflowLaunchHistory>();

                        historyItem.Workflow = workflow;

                        historyItem.LaunchedOn = DateTime.Now;

                        objectSpace.CommitChanges();

                    }

                }

            }

        }

    }

}

 

Note; the service is not fully implemented, however its very easy to continue from this point. This code will live in the new Xpand.ExpressApp.Workflow module. Now, for the rest of the implementation I would like to ask the help of our community. Anyone that wants to finish it contribute it is most welcome!

Finally we modify the WorkflowServerStarter class and add this service,

private void Start_(string connectionString, string applicationName) {

    ServerApplication serverApplication = new ServerApplication();

    serverApplication.ApplicationName = applicationName;

    serverApplication.Modules.Add(new WorkflowDemoModule());

    serverApplication.ConnectionString = connectionString;

    serverApplication.Security = new SecurityComplex<User, Role>(

        new WorkflowServerAuthentication(new BinaryOperator("UserName", "WorkflowService")));

    serverApplication.Setup();

    serverApplication.Logon();

 

    IObjectSpaceProvider objectSpaceProvider = serverApplication.ObjectSpaceProvider;

 

    server = new WorkflowServer("http://localhost:46232", objectSpaceProvider, objectSpaceProvider);

 

    server.WorkflowDefinitionProvider = new ScheduledWorkflowDefinitionProvider(typeof(XpoWorkflowDefinition));

 

    //Add the service           

    server.ServiceProvider.AddService(new ScheduledWorkflowStartService());

We are now ready to go!

4-9-2011 12-50-05 μμ

Note; You can watch this approach live in this webinar

Updated: Big thanks to Martin Praxmarer for contributing the missing parts of this implementation. It can be found in eXpand v11.2.11.9 

Related Links
Blog posts
Online documentation
Videos

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.