How to Use a Singleton WCF Proxy to Call Different Workflow Service Instances in .NET 3.5?

In .NET 3.5, a new type WorkflowServiceHost is introduced to provide the integration of WF and WCF. On the server-side, ReceiveActivty plays the main role to implement WCF service operations. With this, you can have WCF clients to talk to WF services. Also ContextChannel is introduced to flow the context between the client and the service so that a call to the service from the client can be routed correctly to the right WF instance.

One question that people asked is:

How can we use a single WCF proxy to call different Workflow service instances and flow the context information to the right instances? Is it doable?

Yes, it is doable. By default, the proxy (the context channel) caches the context internally and flows the same context to the service. In order to use the same proxy to flow different contexts, the first step is to disable the context on the IContextManager property of the proxy before it’s opened:

IContextManager contextManager = ((IClientChannel)helloProxy).GetProperty<IContextManager>();

contextManager.Enabled = false;

Now, you can simply attach the context to the ContextMessageProperty property of the outgoing message:

using (OperationContextScope scope = new OperationContextScope( (IContextChannel)helloProxy))

{

    ContextMessageProperty property = new ContextMessageProperty(context);

  OperationContext.Current.OutgoingMessageProperties.Add( "ContextMessageProperty", property);

    helloProxy.Hello("Hello");

}

Below is the sample code that shows how to achieve this. You can see how the client initiates two calls to get two different contexts and then calls the WF service with the contexts interleaved. The sample output is as following:

You said: Hello

You said: Hello 43f5aa31-9b71-4978-9a84-ad19ea727026

You said: Hello

You said: Hello f4962a99-3ec5-4467-8330-a96f0d7aaa21

You said: Hello 43f5aa31-9b71-4978-9a84-ad19ea727026

You said: Hello f4962a99-3ec5-4467-8330-a96f0d7aaa21

You can also store the context data in a file or a database and reconstruct it once you need to connect to the existing WF instance with that context. Here is the code snippet that shows how to reconstruct the context given only the InstanceId (a GUID) data:

// Argument 'contextData' is the InstanceId (a GUID, for example,

// "ad79216c-768f-4f1a-a9b8-3bb6cc512bb4") that you stored earlier

// for the WF instance that you want to reconnect to.

IDictionary<XmlQualifiedName, string> ReconstructContext(string contextData)

{

string contextNamespace = "https://schemas.microsoft.com/ws/2006/05/context";

string contextName = "InstanceId";

XmlQualifiedName xqn = new XmlQualifiedName(contextName, contextNamespace);

Dictionary<XmlQualifiedName, string> context = new Dictionary<XmlQualifiedName, string>();

context.Add(xqn, contextData);

return context;

}

 

Sample code:

namespace UseContextPerCall

{

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.ServiceModel;

    using System.Workflow.Activities;

    using System.Workflow.Activities.Rules;

    using System.ServiceModel.Channels;

    using System.Collections;

    using System.Xml;

    using System.Workflow.ComponentModel.Compiler;

    using System.Workflow.ComponentModel;

    using System.ServiceModel.Description;

    class Program

    {

        const string url = "https://localhost/foo/bar.svc";

        WorkflowServiceHost serviceHost;

        IHelloService helloProxy;

        ChannelFactory<IHelloService> factory;

        static void Main(string[] args)

        {

            try

            {

                Program p = new Program();

                p.Start();

            }

            catch (Exception ex)

            {

                Exception inner = ex;

                while (inner.InnerException != null)

                    inner = inner.InnerException;

                if (inner is WorkflowValidationFailedException)

                {

                    WorkflowValidationFailedException valE = inner as WorkflowValidationFailedException;

                    for (int i = 0; i < valE.Errors.Count; i++)

                    {

                        Console.WriteLine(valE.Errors[i].ErrorText);

                }

                }

                else

                {

                    Console.WriteLine(inner.ToString());

                }

            }

        }

        void StartService()

        {

            serviceHost = new WorkflowServiceHost(typeof(MyWorkflow), new Uri(url));

            serviceHost.AddServiceEndpoint(typeof(IHelloService), new WSHttpContextBinding(), "");

            ServiceDebugBehavior sdb = serviceHost.Description.Behaviors.Find<ServiceDebugBehavior>();

            sdb.IncludeExceptionDetailInFaults = true;

            serviceHost.Open();

        }

        IDictionary<XmlQualifiedName, string> FirstCall()

        {

            IHelloService proxy = factory.CreateChannel();

            Console.WriteLine(proxy.Hello("Hello"));

            IContextManager contextManager = ((IClientChannel)proxy).GetProperty<IContextManager>();

            IDictionary<XmlQualifiedName, string> context = contextManager.GetContext();

            ((IClientChannel)proxy).Close();

            return context;

        }

        void Call(IDictionary<XmlQualifiedName, string> context)

        {

            using (OperationContextScope scope = new OperationContextScope( (IContextChannel)helloProxy))

            {

                ContextMessageProperty property = new ContextMessageProperty(context);

  OperationContext.Current.OutgoingMessageProperties.Add( "ContextMessageProperty", property);

                foreach (string val in context.Values)

                {

                    Console.WriteLine(helloProxy.Hello("Hello " + val));

                    break;

                }

            }

        }

        void CreateProxy()

        {

            factory = new ChannelFactory<IHelloService>(new WSHttpContextBinding(), new EndpointAddress(url));

            helloProxy = factory.CreateChannel();

            // Disabling default context switching

            IContextManager contextManager = ((IClientChannel)helloProxy).GetProperty<IContextManager>();

            contextManager.Enabled = false;

            ((IClientChannel)helloProxy).Open();

        }

        void Clenaup()

        {

            if (helloProxy != null)

            {

                ((IClientChannel)helloProxy).Abort();

            }

            if (factory != null)

            {

                factory.Abort();

            }

            serviceHost.Abort();

        }

        void Start()

        {

            StartService();

            CreateProxy();

            IDictionary<XmlQualifiedName, string> context = FirstCall();

            Call(context);

            IDictionary<XmlQualifiedName, string> context1 = FirstCall();

            Call(context1);

            // Intermixed

            Call(context);

            Call(context1);

            Clenaup();

        }

    }

    [ServiceContract]

    interface IHelloService

    {

        [OperationContract]

        string Hello(string message);

    }

    public class MyWorkflow : SequentialWorkflowActivity

    {

        public MyWorkflow()

        {

            ReceiveActivity receiveActivity = new ReceiveActivity("HelloReceive");

            receiveActivity.CanCreateInstance = true;

            receiveActivity.ServiceOperationInfo = new TypedOperationInfo(typeof(IHelloService), "Hello");

            CodeActivity codeActivity = new CodeActivity("HelloCode");

            codeActivity.ExecuteCode += new EventHandler(codeActivity_ExecuteCode);

            receiveActivity.Activities.Add(codeActivity);

            WhileActivity whileActivity = new WhileActivity("HelloWhile");

            CodeCondition condition = new CodeCondition();

            condition.Condition += new EventHandler<ConditionalEventArgs>(condition_Condition);

            whileActivity.Condition = condition;

            whileActivity.Activities.Add(receiveActivity);

            // adding binding

            ActivityBind activityBindArg = new ActivityBind("MyWorkflow");

            activityBindArg.Path = "HelloArg1";

            WorkflowParameterBinding bindingArg1 = new WorkflowParameterBinding("message");

            bindingArg1.SetBinding(WorkflowParameterBinding.ValueProperty, activityBindArg);

            ActivityBind activityBindReturn = new ActivityBind("MyWorkflow");

            activityBindReturn.Path = "HelloResult";

            WorkflowParameterBinding bindingResult = new WorkflowParameterBinding("(ReturnValue)");

            bindingResult.SetBinding(WorkflowParameterBinding.ValueProperty, activityBindReturn);

            receiveActivity.ParameterBindings.Add(bindingArg1);

            receiveActivity.ParameterBindings.Add(bindingResult);

            this.Activities.Add(whileActivity);

        }

        public static readonly DependencyProperty HelloArg1Property = DependencyProperty.Register(

         "HelloArg1",

            typeof(string),

            typeof(MyWorkflow),

            new PropertyMetadata(DependencyPropertyOptions.Default));

        public string HelloArg1

        {

            get

            {

                return (string)base.GetValue(MyWorkflow.HelloArg1Property);

            }

            set

            {

                base.SetValue(MyWorkflow.HelloArg1Property, value);

            }

        }

        public static readonly DependencyProperty HelloResultProperty = DependencyProperty.Register(

            "HelloResult",

            typeof(string),

            typeof(MyWorkflow),

            new PropertyMetadata(DependencyPropertyOptions.Default));

        public string HelloResult

        {

            get

            {

                return (string)base.GetValue(MyWorkflow.HelloResultProperty);

            }

            set

            {

                base.SetValue(MyWorkflow.HelloResultProperty, value);

            }

        }

        void condition_Condition(object sender, ConditionalEventArgs e)

        {

            e.Result = true;

        }

        void codeActivity_ExecuteCode(object sender, EventArgs e)

        {

            this.HelloResult = "You said: " + this.HelloArg1;

        }

    }

}