Resumable Workflow Activity

by ipsit 14. October 2009 21:19
BACKGROUND
Recently, I have been working on Windows Workflow Foundation. We are trying to create a custom host for workflow, and this workflow host is supposed to have a facility, such that it shold be able to provide a way to  resume from the exact point where a workflow failed from the Admin UI. Something similar to the Biztalk HAT.
Well, having said that the question how can something like this can be achieved ?
What I am going to present here is one of the ways of achieving it. There would be other ways which would be better than the way I am going present here, need less to say, that I am still trying to learn my way through Windows Workflow Foundation.
Well, the idea came to me after going through some of msdn articles about Workflow from Matt Milner. {Personal Opinion: He is damn good at explaining}
1. Error Handling In Workflows - Matt Milner
I would highly recommend to read these articles before reading below. Even if you are not interested, I would recommend you to read the above articles, they give hell of an insight into workflow.
SOLUTION
So now you have the background information of the required stuff and as well as we have a problem at hand that we want to solve. This idea is based of RetryActivity. So we create a general purpose, WFFaultReportingSystem Service. I am registering the service to the run time. This service becomes the medium of reporting the error to the Host, that something wrong happened.
using System;
namespace Coderslog.Workflow.Services
{
    public interface IWFFaultReportingSystem
    {
        void ReportError(Exception ex, object message);
        void ReportResumableError(Guid wfInstanceId, string queueName,Exception ex, object message);
    }
}
Now lets look at the activity itself.
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Linq;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using Coderslog.Workflow.Services;

namespace Coderslog.Workflow.Activities
{
        
[Designer(typeof(SequenceDesigner), typeof(IDesigner))]
        public partial class ResumableActivity : CompositeActivity, IActivityEventListener<QueueEventArgs>
        {
                public ResumableActivity()
                {
                                                  //The call to the partial generated class                         
                                                 InitializeComponent();
            
                        
                                                QueueName               = "ResumableActivity";
            
                        CurrentActivityIndex    = 0;
                }
                               
                                //The Last Error that happened while executing any child activity
        
               [Browsable(false)]
        
               public Exception LastError { get; set; }
                              
                               //The Current Activity that I am trying to process now
        
               [Browsable(false)]
        
               public int CurrentActivityIndex { get; set; }

                
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            if (EnabledActivities.Count == 0)
                return ActivityExecutionStatus.Closed;

            
//try to execute the child activities in a new context
            BeginIteration(executionContext);
            return ActivityExecutionStatus.Executing;
        }
                //Initiating the Iteration
        private void BeginIteration(ActivityExecutionContext executionContext)
        {
            /
/create a new activity execution context
            Activity child = EnabledActivities[CurrentActivityIndex];
            ActivityExecutionContext newContext = executionContext.ExecutionContextManager.CreateExecutionContext(child);

            
//register for both closed and faulted events
            newContext.Activity.Closed += new EventHandler<ActivityExecutionStatusChangedEventArgs>(child_Closed);
            newContext.Activity.Faulting += new EventHandler<ActivityExecutionStatusChangedEventArgs>(Activity_Faulting);

            
//execute the clone activity
            newContext.ExecuteActivity(newContext.Activity);
        }

        void Activity_Faulting(object sender, ActivityExecutionStatusChangedEventArgs e)
        {
            
//if the child faults, clear the exception if retrying
            e.Activity.Faulting -= Activity_Faulting;
            
            
//Get hold of the error
            LastError = (Exception)e.Activity.GetValue(ActivityExecutionContext.CurrentExceptionProperty);
                         //Clear the Error, We don't want anyone to know anything about the Error            
                         e.Activity.SetValue(ActivityExecutionContext.CurrentExceptionProperty, null);
        }

        
/// <summary>
        /// Creates the queue and registers for items to be available.
        /// </summary>
        /// <param name="parentContext"></param>
        /// <param name="parentEventHandler"></param>

        public void Subscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler)
        {
            WorkflowQueuingService qService = parentContext.GetService<WorkflowQueuingService>();
            if (qService != null)
            {
                if (!qService.Exists(QueueName))
                {
                    WorkflowQueue q = qService.CreateWorkflowQueue(QueueName, false);
                    q.RegisterForQueueItemAvailable(parentEventHandler, QualifiedName);
                }

            }
        }
                //Property for holding the QueueName on which to Subscribe and Expect the response
        [Browsable(false)]
        public string QueueName { get; private set; }
        

   
     /// <summary>
        /// Removes the listener from the queue and deletes the queue.  
        /// </summary>
        /// <param name="parentContext"></param>
        /// <param name="parentEventHandler"></param>

        public void Unsubscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler)
        {
            WorkflowQueuingService qService = parentContext.GetService<WorkflowQueuingService>();
            if (qService != null)
            {
                WorkflowQueue queue = qService.GetWorkflowQueue(QueueName);
                if (queue != null)
                {
                    queue.UnregisterForQueueItemAvailable(parentEventHandler);
                    qService.DeleteWorkflowQueue(QueueName);
                }
            }
        }
               
               // Helper Method to Get Hold of the Queue
        private WorkflowQueue GetQueue(ActivityExecutionContext ctx)
        {
            WorkflowQueuingService qService = ctx.GetService<WorkflowQueuingService>();
            if (qService != null && qService.Exists(QueueName))
                return qService.GetWorkflowQueue(QueueName);
            else
                return null;
        }


                //The Big Bang Close Method
        void child_Closed(object sender, ActivityExecutionStatusChangedEventArgs e)
        {
            ActivityExecutionContext thisContext = sender as ActivityExecutionContext;
            ActivityExecutionContext childContext = thisContext.ExecutionContextManager.GetExecutionContext(e.Activity);
            e.Activity.Closed -= child_Closed;

            thisContext.ExecutionContextManager.CompleteExecutionContext(childContext);
                        
                         //if of the EnabledActivities, the current one has completed successfully, move to the next one
            if (e.ExecutionResult == ActivityExecutionResult.Succeeded && CurrentActivityIndex < EnabledActivities.Count - 1)
            {
                this.SetValue(ActivityExecutionContext.CurrentExceptionProperty, null);
                CurrentActivityIndex += 1;
                BeginIteration(thisContext);
                return;
            }

            
//if the child completed successfully, then we can close
            if (e.ExecutionResult == ActivityExecutionResult.Succeeded && CurrentActivityIndex == EnabledActivities.Count - 1)
            {
                this.SetValue(ActivityExecutionContext.CurrentExceptionProperty, null);
                thisContext.CloseActivity();
                return;
            }

            
            
//otherwise, if we are not done retrying
            //we need to resume again and make sure to clean up errors

            if (this.ExecutionStatus == ActivityExecutionStatus.Executing)
            {
                SetResumePoint(e, thisContext);
            }
        }
               
                // We try and get the instance of the Fault Reposrting System, if yes, we report the error and subscribe to the Queue
                // so that on basis of the users input we would be able to resume from the point where it broke or terminate
        private void SetResumePoint(ActivityExecutionStatusChangedEventArgs e, ActivityExecutionContext thisContext)
        {
            IWFFaultReportingSystem wfFRS = thisContext.GetService<IWFFaultReportingSystem>();
            var error = LastError;
            if (wfFRS == null)
            {
                throw new Exception(
"This activity requires that the Service IWFFaultReportingSystem is registered");
            }
            if (error != null)
            {
                this.SetValue(ActivityExecutionContext.CurrentExceptionProperty, null);
                Subscribe(thisContext, this);
                wfFRS.ReportResumableError(WorkflowInstanceId, QueueName, error, null);
            }
        }
               
               // Fires when there is the required actvity in the Queue
        public void OnEvent(object sender, QueueEventArgs e)
        {
            
//start a new iteration after the event happens
            ActivityExecutionContext ctx = sender as ActivityExecutionContext;
            if (sender == null)
                throw new ArgumentException(
"Sender must be ActivityExecutionContext");
            
            WorkflowQueue q = GetQueue(ctx);
            if (q != null)
            {
                object data = q.Dequeue();
                
//use the data - Resume
                if (data != null && bool.Parse(data.ToString()))
                {
                    Unsubscribe(ctx, this);
                    BeginIteration(ctx);
                }
                else
                {
                    throw new ApplicationException(
"The Workflow cannot be Resumed from this point onwards!");
                }
            }
        }
    }
}
The other peripherals:
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Reflection;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;

namespace Coderslog.Workflow.Activities
{
    
        public partial class ResumableActivity
        {
                #region Designer generated code
                
                
/// <summary>
                /// Required method for Designer support - do not modify
                /// the contents of this method with the code editor.
                /// </summary>
        
                [System.Diagnostics.DebuggerNonUserCode]
                private void InitializeComponent()
                {
                        this.Name = "ResumableActivity";
                }

                #endregion
        }
}
This covers the Activity. I have tried and put comments where ever required. Hopefully it should be self explanatory.
IMPORTANT PART OF CLIENT CODE - TEST HARNESS
Sample IWFFaultReportingSystem.cs implementation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CP.Services;
using System.Workflow.Runtime;

namespace Coderslog.Client.ResumableWorkflow
{
        public class FaultReportingSystem:IWFFaultReportingSystem
        {
        WorkflowRuntime _runtime;
        
Guid            _instanceId;
        string          _queueName;



        public FaultReportingSystem(WorkflowRuntime runtime)
        {
            _runtime = runtime;
        }

        #region IWFFaultReportingSystem Members

        public void ReportError(Exception ex, object message)
        {
            Console.WriteLine(ex.Message);
        }

        public void ReportResumableError(Guid wfInstanceId, string queueName, Exception ex, object message)
        {
            Console.WriteLine("WF ID :" +  wfInstanceId.ToString());
            Console.WriteLine("Queue Name :" + queueName);
            Console.WriteLine("Error :" + ex.Message);

            
_instanceId = wfInstanceId;
            _queueName = queueName;


            var action = new Action(ProcessError);
            action.BeginInvoke(CallBack, action);   
        }

        void ProcessError()
        {
            Console.WriteLine("What you want to do ? R for Resuming or any key for Exit");
            var key = Console.ReadLine();
            
var instance = _runtime.GetWorkflow(_instanceId);
            if (key == "R" || key == "r")
            {
                
instance.EnqueueItem(_queueName, true, null, null);
            }
            else
            {
                
instance.EnqueueItem(_queueName, false, null, null);
            }
        }
        #endregion

        static void CallBack(IAsyncResult ar)
        {
            var action = ar.AsyncState as Action;
            if (action != null)
                action.EndInvoke(ar);
        }
    }
}
The Highlighted part shows what the user needs to do to resume the workflow. Just drop a message in the Workflow Queue.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

.Net 3.0 | WF | Custom Activity

Identifying the Name of the local Machine in C#

by editor 2. October 2009 14:36

string name = Environment.MachineName;
string name = System.Net.Dns.GetHostName();
string name = System.Windows.Forms.SystemInformation.ComputerName;
string name = System.Environment.GetEnvironmentVariable(“COMPUTERNAME”);

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

.Net 3.0 | .Net 4.0

Pondering over ASP.NET MVC!

by sanket 5. November 2008 16:38

Have been pondering over the implementation, of the CMS engine on ASP.NET.

What to use and what not to use.

So, here's the list of the Stuff, that we would be using for the CMS engine. 

The list has been biased on "Understanding the Underlying Principles and Learning New Stuff"

UI - ASP.NET MVC

Language - C# 3.0

Dependency Injection - Custom

Persistence - NHibernate + LINQ

OutProc Mode - WCF

 Having set up the expectations, saw the latest video about "The Future of C#" from the PDC, came across:

  • dynamic 
  • IDynamicObject
  • Co/Contra Variance
  • Named/Optional Parameters
  • Intuitive COM interoperability
  • The Dynamic Language Runtime
  • Finally a Senak peek at Compiler as a Service

I must admit, FLOORED!

 

Have a look guys!,

Happy Coding!

Currently rated 1.0 by 1 people

  • Currently 1/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

.Net 3.0 | Asp.Net MVC | WCF

Maintenance! Phew how I used to dislike it...

by ipsit 31. October 2008 05:51

Most of the interviews that we generally tend to attend, we always try to make sure, that we are going for a job role, where in the kind of work is more towards development. And I myself have done this. But untill, recently, there was a task, that had been already written, and it badly needed extension. This piece of code which was already built was doing what it was meant for, effectively.

But the changes that were required, had few contracdictions with its essential design or the core design. Now the challenge was to get the piece moving or doing which it was already doing and then on top of it, add few more extensions to it. A typical maintenance scenario... 

I just questioned my self, was it that I was afraid to tackle such a situation earlier or whether, it was like, i was ignorant of this aspect of Software development. Just to brighten up this idea, I would start up a series to take scenarios and then try to make changes to it and learn and understand the underlying principles of the same...

Happy Coding!

Currently rated 3.0 by 1 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

.Net 3.0 | Refactoring


Inida

About Coderslog

One fine day we thought, we need to be contributing to our community.

So good sense prevailed, and that's how coderslog got started. That's about us.

Calendar

<<  March 2010  >>
MoTuWeThFrSaSu
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar

RecentPosts

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010 CodersLog