locked
Custom Activity that wraps a Receive Activity RRS feed

  • Question

  • I need to create an activity that is part of workflow that runs on the server-side of a client server model. The requirements are as follows:
     1. Uses a receive activity (we're using WCF for client to communicate with server workflow)
     2. At design time the developer specifies a list of possible string values that the client is allowed to send as a parameter in the receive call. This parameter will then be made available to the rest of the workflow.
     3. The custom activity must be able to be associated with a custom designer.
     4. The correlation handle variable associated with the workflow must  be used as the correlation handle for the internal receive. 

    To my knowledge CodeActivity and NativeActivity will not meet the needs of requirement 1, since they deal with activity execution, not activity definition (I can't specify that they contain a receive activity)

    IActivityTemplateFactory will not meet the needs of requirement 3.

    So I am left with deriving from Activity and defining a custom implementation. Below is an example of my current implementation.

    Questions:
    1. When I set the CorrelatesWith property of the receive to WorkflowCorrelationHandle the workflow throws a design time error (shown below). How do I consume the WorkflowCorrelationHandle InArgument in my Receive?
    2. Is there a different technique that will satisfy the requirements above?

    WorkflowCorrelationHandle Error:

    The private implementation of activity '42: MyCustomActivity' has the following validation error:   The activity 'Receive' cannot reference activity 'VisualBasicValue<CorrelationHandle>' because activity 'VisualBasicValue<CorrelationHandle>' is already referenced elsewhere in the workflow and that reference is not visible to activity 'Receive'.  In order for activity 'VisualBasicValue<CorrelationHandle>' to be visible to activity 'Receive', it would have to be a child or imported child (but not an implementation child) of activity 'MyCustomActivity'.  Activity 'VisualBasicValue<CorrelationHandle>' is originally referenced by activity 'MyCustomActivity' and activity 'Receive' is in the implementation of activity 'MyCustomActivity'.

    public sealed class MyCustomActivity : Activity
        {
            public MyCustomActivity()
            {
                this.Implementation = this.InternalImplementation;
            }
         
            // Variables used by receive
            public InArgument<CorrelationHandle> WorkflowCorrelationHandle { get; set; }
            public string OperationName { get; set; }
            public string ServiceContractName { get; set; }
            public bool CanCreateInstance { get; set; }
    
    
            public string SelectedMethod { get; set; }        
            public InArgument<Variable<string>[]> AvailableMethods { get; set; }
    
            // Define an activity input argument of type string
            private Activity InternalImplementation()
            {
                Variable<WorkflowOperationArg> requestMessage = new Variable<WorkflowOperationArg> { Name = "requestArgs" };
                Dictionary<string, OutArgument> paramList = new Dictionary<string, OutArgument>();
                paramList.Add("WorkflowArgs", new OutArgument<WorkflowOperationArg>(requestMessage));
    
                Receive receive = new Receive
                {
                    OperationName = this.OperationName,
                    CorrelatesWith = WorkflowCorrelationHandle, // Causes error shown above
                    CanCreateInstance = CanCreateInstance,
                    Content = ReceiveContent.Create(paramList),
                    ServiceContractName = ServiceContractName,
                };
    
                return new Sequence
                {
                    Activities = 
                    {
                        new WriteLine { Text = "In Custom Activity" },
    
                        new TransactedReceiveScope
                        {
                            Variables = { requestMessage },
                            Request = receive,
                            Body = new Sequence
                            {
                                Activities =
                                {
                                    new WriteLine { Text = new InArgument<string>("Do other work here") },
                                    
                                },
                            },
                        },
    
                        new WriteLine { Text = "Custom Activity Complete" },
                    },
                };
            }
        }
    
    
    Wednesday, February 3, 2010 3:28 PM

Answers

  • What I determined is that you don't need to pass in the correlation handle to get this to work; so long as you match the service contract name and the keys associated with the workflow's correlation token, it works just fine. I have a single variable defined for the workflow that gets passed into each custom state and used. Then each state can change data in that workflow variable to allow communication between states. Here is some code:

     

    public class MyStateActivity : Activity
        {
           
            public MyStateActivity()
            {
                Init();
            }
        
            private void Init()
            {
                IncomingData = new InOutArgument<OperationData>();
                Variables = new List<Variable>();
                PreReceiveActivities = new List<Activity>();
                PostReceiveActivities = new List<Activity>();
              
                this.Implementation = this.InternalImplementation;
            }
           
            [Category("Input")]
            [RequiredArgument()]
            public string StateCodeName { get; set; }
           
            [Category("Input")]
            [RequiredArgument()]
            public string ServiceContractName { get; set; }

            [Category("Input")]
            public string AvailableMethods { get; set; }

               // OperationData is a class that holds all of my parameters being passed to the workflow
            [Category("InOut")]
            public InOutArgument<OperationData> IncomingData { get; set; }

            [Category("Option Flags")]
            public bool IsFinalState { get; set; }

            [Category("Option Flags")]
            public bool IsCreateState { get; set; }

            protected List<Variable> Variables { get; set; }
            protected List<Activity> PreReceiveActivities { get; set; }
            protected List<Activity> PostReceiveActivities { get; set; }

            protected virtual Activity InternalImplementation()
            {
                Sequence StateActivitySequence = new Sequence();

                List<Variable> localVariables = new List<Variable>(Variables);

                Variable<OperationData> requestMessage = new Variable<OperationData> { Name = "OperationData" };
                Dictionary<string, OutArgument> paramList = new Dictionary<string, OutArgument>();
                paramList.Add("OperationData", new OutArgument<OperationData>(requestMessage));


                // Define a message context
                XPathMessageContext messageContext = new XPathMessageContext();
                messageContext.AddNamespace("local", "http://tempuri.org/");
                messageContext.AddNamespace("xg0", "http://schemas.datacontract.org/2004/07/MyProject.Common");

                // This query retrieves the output argument sent from the SendReply
                MessageQuerySet MyUniqueIdQuery = new MessageQuerySet
                {
                    { "WorkflowOperation",        
                        new XPathMessageQuery
                            ("sm:body()/local:" + StateCodeName + "/local:OperationData/xg0:MyUniqueId",            
                            messageContext) }
                };


                Receive receive = new Receive
                {
                    OperationName = this.StateCodeName,
                    CanCreateInstance = IsCreateState,
                    Content = ReceiveContent.Create(paramList),
                    ServiceContractName = XName.Get(ServiceContractName, ""),
                    CorrelatesOn = MyUniqueIdQuery
               };

                if (!IsCreateState)
                {
                    // Change the State Name to reflect this new state
                    PreReceiveActivities.Insert(0,
                       new Assign<string>
                       {
                           Value = new InArgument<string>(StateCodeName),
                           To = new OutArgument<string>(env => IncomingData.Get(env).StateName)
                       });
                }

                StateActivitySequence.Variables.Add(requestMessage);
                localVariables.ForEach(i => StateActivitySequence.Variables.Add(i));

                StateActivitySequence.Activities.Add(new WriteLine() { Text = new InArgument<string>(env => "Entering " + StateCodeName )});

                PreReceiveActivities.ForEach(i => StateActivitySequence.Activities.Add(i));

                // A final state does not have a receive on entry; it just goes through the motions
                if (!IsFinalState)
                {
                    StateActivitySequence.Activities.Add(receive);
                    StateActivitySequence.Activities.Add(
                        new Assign<OperationData>
                        {
                            Value = new InArgument<OperationData>(env => requestMessage.Get(env)),
                            To = new OutArgument<OperationData>(env => this.IncomingData.Get(env))
                        });
                }

                PostReceiveActivities.ForEach(i => StateActivitySequence.Activities.Add(i));

                return StateActivitySequence;
            }
        }

    Hope it helps!

    Monday, June 28, 2010 12:52 PM

All replies

  • Any luck with this? I am also stuck with the same error.
    Monday, June 28, 2010 12:20 AM
  • What I determined is that you don't need to pass in the correlation handle to get this to work; so long as you match the service contract name and the keys associated with the workflow's correlation token, it works just fine. I have a single variable defined for the workflow that gets passed into each custom state and used. Then each state can change data in that workflow variable to allow communication between states. Here is some code:

     

    public class MyStateActivity : Activity
        {
           
            public MyStateActivity()
            {
                Init();
            }
        
            private void Init()
            {
                IncomingData = new InOutArgument<OperationData>();
                Variables = new List<Variable>();
                PreReceiveActivities = new List<Activity>();
                PostReceiveActivities = new List<Activity>();
              
                this.Implementation = this.InternalImplementation;
            }
           
            [Category("Input")]
            [RequiredArgument()]
            public string StateCodeName { get; set; }
           
            [Category("Input")]
            [RequiredArgument()]
            public string ServiceContractName { get; set; }

            [Category("Input")]
            public string AvailableMethods { get; set; }

               // OperationData is a class that holds all of my parameters being passed to the workflow
            [Category("InOut")]
            public InOutArgument<OperationData> IncomingData { get; set; }

            [Category("Option Flags")]
            public bool IsFinalState { get; set; }

            [Category("Option Flags")]
            public bool IsCreateState { get; set; }

            protected List<Variable> Variables { get; set; }
            protected List<Activity> PreReceiveActivities { get; set; }
            protected List<Activity> PostReceiveActivities { get; set; }

            protected virtual Activity InternalImplementation()
            {
                Sequence StateActivitySequence = new Sequence();

                List<Variable> localVariables = new List<Variable>(Variables);

                Variable<OperationData> requestMessage = new Variable<OperationData> { Name = "OperationData" };
                Dictionary<string, OutArgument> paramList = new Dictionary<string, OutArgument>();
                paramList.Add("OperationData", new OutArgument<OperationData>(requestMessage));


                // Define a message context
                XPathMessageContext messageContext = new XPathMessageContext();
                messageContext.AddNamespace("local", "http://tempuri.org/");
                messageContext.AddNamespace("xg0", "http://schemas.datacontract.org/2004/07/MyProject.Common");

                // This query retrieves the output argument sent from the SendReply
                MessageQuerySet MyUniqueIdQuery = new MessageQuerySet
                {
                    { "WorkflowOperation",        
                        new XPathMessageQuery
                            ("sm:body()/local:" + StateCodeName + "/local:OperationData/xg0:MyUniqueId",            
                            messageContext) }
                };


                Receive receive = new Receive
                {
                    OperationName = this.StateCodeName,
                    CanCreateInstance = IsCreateState,
                    Content = ReceiveContent.Create(paramList),
                    ServiceContractName = XName.Get(ServiceContractName, ""),
                    CorrelatesOn = MyUniqueIdQuery
               };

                if (!IsCreateState)
                {
                    // Change the State Name to reflect this new state
                    PreReceiveActivities.Insert(0,
                       new Assign<string>
                       {
                           Value = new InArgument<string>(StateCodeName),
                           To = new OutArgument<string>(env => IncomingData.Get(env).StateName)
                       });
                }

                StateActivitySequence.Variables.Add(requestMessage);
                localVariables.ForEach(i => StateActivitySequence.Variables.Add(i));

                StateActivitySequence.Activities.Add(new WriteLine() { Text = new InArgument<string>(env => "Entering " + StateCodeName )});

                PreReceiveActivities.ForEach(i => StateActivitySequence.Activities.Add(i));

                // A final state does not have a receive on entry; it just goes through the motions
                if (!IsFinalState)
                {
                    StateActivitySequence.Activities.Add(receive);
                    StateActivitySequence.Activities.Add(
                        new Assign<OperationData>
                        {
                            Value = new InArgument<OperationData>(env => requestMessage.Get(env)),
                            To = new OutArgument<OperationData>(env => this.IncomingData.Get(env))
                        });
                }

                PostReceiveActivities.ForEach(i => StateActivitySequence.Activities.Add(i));

                return StateActivitySequence;
            }
        }

    Hope it helps!

    Monday, June 28, 2010 12:52 PM
  • Thanks Joe, your explaination does help. Now I am able to host the workflow. Thanks.
    Thursday, July 1, 2010 5:56 AM