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

Simple Where Query Language

by ipsit 30. September 2009 14:39
Simple Where Query Language
Background
There was this idea that was popped in for creation of CRUD Services for selected entities in the Current Project that I am working on. So, we ended up creating a set of CreateUpdateReadDelete services. Now Create, Update and Delete are fine but Read had two overloads, Read by primary key and Read All. We wanted a way to filter it further. I know a purists is reading this, he would be detesting me about the idea of a CRUD Service, but I am just a developer and I am not the one who calls the shots here, I just do what I am asked to do and try to do it in a best possible way. Ne ways, we wanted to filter the Reads further and we wanted a soultion that was similar to SQL Where part and being limited to just one entity.
Solution
I thought and even suggested, should we create a full blown where part or should we just use Regex friendly strings but was not solving our purpose. So I ended up writing a language and then generating a lexer and parser. So now you can guess, this one is a post about languages and definitely ANTLR.
Below is a simple sub part of the Grammar. This is the first time i am using ANTLR and I am not quite aware of the best practices and please pardon my ignorance.
Grammar:
               
grammar simplewhere;

options
{
    language=CSharp2;
}


tokens {
    
    AND='?
    OR='|';
    EQUALS ='=';
    LIKE='like';
    GREATER='>';
    LESSER='<';
    GREATERTHANEQUALTO = '>=' ;
    LESSERTHANEQUALTO = '<=';
}




clause          :       
                        clauseitem
                        |
                        clauseitem (AND SPACE STARTGROUP SPACE clauseitem ENDGROUP SPACE)+
                        |
                        clauseitem (OR SPACE STARTGROUP SPACE clauseitem ENDGROUP SPACE)+
                        |
                        STARTGROUP SPACE clauseitem ENDGROUP SPACE (AND SPACE STARTGROUP SPACE clauseitem ENDGROUP SPACE)+
                        |
                        STARTGROUP SPACE clauseitem ENDGROUP SPACE (OR SPACE STARTGROUP SPACE clauseitem ENDGROUP SPACE)+
                ;

clauseitem      :
                        criteriaitem
                        |
                        criteriaitem (AND SPACE criteriaitem)+
                        |
                        criteriaitem (OR SPACE criteriaitem)+
                ;       

criteriaitem    :       
                        COLUMN SPACE EQUALS SPACE NUM SPACE
                        |
                        COLUMN SPACE EQUALS SPACE VALUE SPACE
                        |
                        COLUMN SPACE EQUALS SPACE GUID SPACE            
                        |
                        COLUMN SPACE LIKE SPACE VALUE SPACE
                        |
                        COLUMN SPACE GREATER SPACE NUM SPACE
                        |
                        COLUMN SPACE LESSER SPACE NUM  SPACE
                        |
                        COLUMN SPACE GREATERTHANEQUALTO SPACE NUM  SPACE
                        |
                        COLUMN SPACE LESSERTHANEQUALTO SPACE NUM SPACE
                ;



NUM             :    
                        (  '0'..'9')+
                ;

VALUE           :
                        '\''!('a'..'z'|'A'..'Z'|'%')+'\''!
                ;

GUID            :
                        '\''!('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')
                        '-' ('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')
                        '-'('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')
                        '-'('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')
                        '-'('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')('a'..'z'|'A'..'Z'|'0'..'9')
                        '\''!
                ;               
COLUMN          :       
                        ('a'..'z'|'A'..'Z')+
                ;       

STARTGROUP      :       
                        '('
                ;

ENDGROUP        :       
                        ')'
                ;

SPACE           :       (
                        '\t' |
                        ' ' |
                        '\r'|
                        '\n'|
                        '\u000C'
                )+;    
In the coming days I will start writing more about it and explain more on it. This is just an introduction. Keep reading.   :)

Currently rated 5.0 by 2 people

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

Tags: ,

ANTLR | DSL

dynamic - II - Let's ILDASM it

by ipsit 4. August 2009 06:41
 
You remember the previous post on dynamic. We will today look into it in a bit more detail. Yeah, let's ILDASM it and try and identify what is going on under the hood. But for the context of the post let's look at a simpler tale.
 
namespace Coderslog.Net4.Samples
{
    public class SimpleDynamic
    {

        public void CallDoSomething()
        {
            var instance = GetDoSomething();
            instance.DoSometing();
        }

        private dynamic GetDoSomething()
        {
            return new BehaviourA();
        }
    }
}
A very basic class, hmmm which roughly translates to something like, I might not be absolutely correct, but to get a fair idea, it is something like the following
 
public class SimpleDynamic
{
    // Method: A Bit more involved than the reflection way, with Optimization    
    public void CallDoSomething()
    {
        object instance = this.GetDoSomething();
        if (CallDoSometning_SiteContainer.delegatePlaceHolder == null)
        {
            CallDoSometning_SiteContainer.delegatePlaceHolder =
                CallSite<Action<CallSite, object>>.Create
(
                        new CSharpInvokeMemberBinder
(
                                
CSharpCallFlags.None,
                                "
DoSometing",
                                typeof(
SimpleDynamic),
                                null
,
                                new []
                                        {
                                                
new CSharpArgumentInfo(
                                                        CSharpArgumentInfoFlags.None
                                                        , null
                                                )

                                        }
                        
));
        }
        CallDoSometning_SiteContainer.
                delegatePlaceHolder.
                        Target.Invoke(
                                CallDoSometning_SiteContainer.delegatePlaceHolder
                                , instance);
    }

    
    //But theres an attribute on the object of
    //Type:System.Runtime.CompilerServices.DynamicAttribute
    private object GetDoSomething()
    {
        return new BehaviourA();
    }

    // Nested Types
    //Compiler Generated Class- Internal Class
    private static class CallDoSometning_SiteContainer
    {
        // Fields
        public static CallSite<Action<CallSite, object>> delegatePlaceHolder;
    }

}
 
So as you can see, the internal static class is there so as to ensure that, we don't always spend resources on the delegate, once it is evaluated.
If we look at the GetDomethingMethod, the Dydnamic Attribute on the returned object from the method GetDoSomething tells the run time that "Indicates that the use of  Object on a member is meant to be treated as a dynamically dispatched type.". If you notice the type of the static field is of type CallSite<Action<CallSite, object>> is  System.Runtime.CompilerServices..::.CallSite<(Of <(T>)>) { MSDN: A dynamic call site base class. This type is used as a parameter type to the dynamic site targets.}.  
 
Lets look at the place where all the action is happening, i.e. the method  CallDoSomething. the method checks if the class CallDoSometning_SiteContainer.delegatePlaceHolder is populated or not, but if not it initializes it with the Create Method and you can notice ... CSharpInvokeMemberBinder {CompleteNamespace: Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder} {MSDN:  Represents a dynamic method call in C#, providing the binding semantics and the details about the operation. Instances of this class are generated by the C# compiler }.
 
This feels like .Net Reflection and one tends to ask, how optimized is it. Well, something Microsoft can enlighten us more about.

Currently rated 5.0 by 2 people

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

Tags: , , ,

.Net 4.0

Abstract Factory - II Abstract Factory vs Factory

by ipsit 3. August 2009 17:32
The other day we had a discussion related to the abstract factory and let me tell you, flames flew and it was high on melodrama.
Anyways coming back to the main point, the root of whole discussion was,
A: Why should I use the abstract factory, just for a parallel set of classes, what am I getting out of it ?
B: You are abstracting your product, that's why you are creating it. Apart from that, its a you have a mechanism for creating related set of products, which is important here, you see you should not forget that, its for creating a set of related family of products.
C: But say if you are just creating is two set of components, a UI Components for rich mode and the simple mode, could you not do with a static Factory implementation instead of so much of fun fare? And also if you look closely, what you are doing is essentially now binding your classes to the implementation of the Abstract Factories some where, because to get the code running you have to write the Implementation class some where, so would not it be better if we had just a static factory class which gives us what we need.
B: hmmmm...
A: Apart from that so much of code to manage .... That's a good one C:)
C: Thanks! Man
B: Factory Method defines an interface for creating an object, but lets subclasses decide which of those to instantiate. A factory method lets classes defer instantiation to subclasses.By contrast, an Abstract Factory provides an interface for creating families of related or dependent objects without specifying their concrete classes.
C: We have a family of related objects but, ... I don't want to take a purists view or go by book view.. I look at convinience and unless you tell me why, I don't see any reason why I should not go for a Factory than a Abstract Factory here.
A: And if you ask me, I guess you are over complicating, is not this kind of an YAGNI.  
B: Well, let me examine the definitions a bit more:
Factory Method defines an interface for creating an object, but lets subclasses decide which of those to instantiate. A factory method lets classes defer instantiation to subclasses.
Which essentially means, in this case the decision is taken by sub class -> Inheritance is in action.
Abstract Factory provides an interface for creating families of related or dependent objects without specifying their concrete classes. Now in this case the job of creating the instance of the products are delegated -> Composition in Action.
And as you guys already know Composition allows you to delay the creation of back-end objects until (and unless) they are needed, as well as changing the back-end objects dynamically throughout the lifetime of the front-end object. With inheritance, you get the image of the superclass in your subclass object image as soon as the subclass is created, and it remains part of the subclass object throughout the lifetime of the subclass.
So I would say, if you need it dynamically vary, take the composition root and enjoy the benefits of Abstarct Factory else, Factory should be just fine for you.
C: hmmm..
A: ok
Hope you like the dialogue....:)

Currently rated 4.0 by 1 people

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

Tags:

Design Pattern | General | MAPI | 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