WCF XAML injection

During the last years there have been a lot of talks about the influence of dynamic and functional languages on techniques like dependency injection, aspect oriented programming etc.  Both paradigms are having a deep impact in the way we architect applications nowadays. Specifically, the world of Service Orientation has been a champion and early adopter of those techniques. On this post I would like to explore how to complement those techniques on WCF services with declarative mechanisms of expressing logic such as XAML.

As part of one of my previous MSDN WebCast about WCF-WF integration I showed how using XAML only workflows can add a great level of flexibility to the WCF extensibility model. Specifically, the use of XAML workflows provides a lot of interesting cases for injecting dynamic behaviors to the service implementation. Although you can see the recording of the WebCast a few of you have asked me to document the demos and I thought I will give this one a shot :).
Assume that the following interface declares the dynamic behavior that we would like to inject to our service.

public interface IWFExtension
{
        void Execute();
}

There are different points on which an implementation of the previous interface can be injected in order to extend the behavior of a WCF service. The extensible object pattern provides a powerful and simple programming model for extending elements of the WCF runtime such as Hosts, InstanceContext, OperationContext and ContextChannels. Typically, the logic implemented on extensible objects is fairly static. However, we can think on a few scenarios on which a service requires enough flexibility to accommodate dynamic behaviors that are not necessarily created during the service implementation. For those scenarios, the use of declarative mechanisms like XAML-only workflows can be a great complement to the very flexible WCF extensibility programming model. Using XAML-only workflows developers can plug-in dynamic logic into the WCF runtime without the need of developing, compiling, deploying and configuring new WCF extensibility components.

Let’s get to the fun part and write some code…

The following code illustrates an implementation of the IWFExtension interface that executes a XAML-only workflow. In order to incorporate this implementation into the WCF runtime, we also implement the IExtension<InstanceContext>.

public class InstanceContextXAMLExtension: IExtension<InstanceContext>, IWFExtension
    {
        private WorkflowInstance instance;
        private XmlReader markupReader;

        public InstanceContextXAMLExtension(WorkflowInstance wfinstance,  
                                            XmlReader wfReader)
        {
            instance = wfinstance;
            markupReader = wfReader;
        }

        public void Attach(InstanceContext owner)
        { }

        public void Detach(InstanceContext owner)
        { }

        public XmlReader WFMarkup
        {
            get { return markupReader;}
            set { markupReader= value;}
        }

        public void Execute()
        {
            instance.Start();
        }

    }

In order to add the previous object into the WCF runtime we need to implement the IInstanceContextInitializer interface. The following implementation instantiates a workflow instances based on the XAML markup and passes it to the instance context extension so that it can be executed at runtime.

public class XAMLInstanceContextInitializer : IInstanceContextInitializer
{
        private string wfMarkupFile;
        private WorkflowRuntime runtime;
        private WorkflowInstance instance;
        private XmlReader markupReader;

        public XAMLInstanceContextInitializer(string wfmarkupfile)
        {
            wfMarkupFile = wfmarkupfile;
        }

        public string WFMarkupFile
        {
            get { return wfMarkupFile; }
            set { wfMarkupFile = value; }
        }


        public void Initialize(InstanceContext instanceContext,
                               Message message)
        {
            if (null == runtime)
                runtime = new WorkflowRuntime();
            XmlReader reader = XmlReader.Create(wfMarkupFile);
            instance = runtime.CreateWorkflow(reader);
            reader.Close();
            InstanceContextXAMLExtension xamlExtension = new           
            InstanceContextXAMLExtension(instance, markupReader);
            instanceContext.Extensions.Add(xamlExtension);
        }
    }

At this point, we only need to plug the instance context initializer into the WCF runtime. We can accomplish this by implementing a WCF endpoint behavior as illustrated in the following code.

public class XAMLExtensionEndpointBehavior: IEndpointBehavior
    {
        private string wfMarkupFile;

        public XAMLExtensionEndpointBehavior(string wfmarkupfile)
        {
            wfMarkupFile = wfmarkupfile;
        }
       
        public string WFMarkupFile
        {
            get { return wfMarkupFile; }
            set { wfMarkupFile= value; }
        }

        public void AddBindingParameters(ServiceEndpoint endpoint,                                                                             
                              BindingParameterCollection bindingParameters)
        { }

        public void ApplyClientBehavior(ServiceEndpoint endpoint,
                               ClientRuntime clientRuntime) 
        { }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
                               EndpointDispatcher endpointDispatcher)
        {
            XAMLInstanceContextInitializer initializer =
                            New XAMLInstanceContextInitializer(wfMarkupFile);                                                                 
                                endpointDispatcher.DispatchRuntime.
                                InstanceContextInitializers.Add(initializer);
        }

        public void Validate(ServiceEndpoint endpoint)
        { }
    }

In order to enable this behavior declaratively we can implement a behavior extension element that reads the XAML workflow containing the logic to be executed.

    public class XMLBehaviorExtensionElement : BehaviorExtensionElement
    {
        private string wfMarkupFile;

        public override Type BehaviorType
        {
            get{return typeof(XAMLExtensionEndpointBehavior);}
        }

        protected override object CreateBehavior()
        {
            return new XAMLExtensionEndpointBehavior(wfMarkupFile);
        }

         protected override void DeserializeElement(XmlReader reader, bool
                                                    serializeCollectionKey)
        {
            wfMarkupFile = reader.GetAttribute("file");
        }
    }

The following fragment illustrates a sample configuration for this behavior.

      <endpointBehaviors>
        <behavior name="XamlExtensionBehavior">
          <XAMLExtension file="<Directory path>\\consoleoutput.xoml"/>
        </behavior>
      </endpointBehaviors>

As we can see, the XAML workflow containing the extensibility logic can be changed by just altering the configuration file

With all these components in place, we can implement a simple service that iterates through the IWFExtensions executes XAML workflow associated with these ones.

    public class SampleService: ISampleService
    {
        public string Echo(string msg)
        {
            foreach (IWFExtension ext in
                     OperationContext.Current.
                     InstanceContext.Extensions.OfType<IWFExtension>())

            {
                Console.WriteLine("Executing XAML workflow...");
                ext.Execute();
            }
            Console.WriteLine("Message received: {0}", msg);
            return msg;
        }
    }


   [ServiceContract]
    public interface ISampleService
    {
        [OperationContract]
        string Echo(string msg);
    }

The configuration of the service host looks like the following.


<configuration>
  <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="XAMLExtension"
             type="Tellago.ServiceModel.Samples.
                       XMLBehaviorExtensionElement,
                       Tellago.ServiceModel.Samples.XAMLInjection,                                                 
                       Version=1.0.0.0,
                       Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>

    <services>
      <service
 name="Tellago.ServiceModel.Samples.XAMLExtService.SampleService"
 behaviorConfiguration="ServiceMetadata">

        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8081/Services"/>
          </baseAddresses>
        </host>
        <endpoint address="/SampleService"
    binding="basicHttpBinding" 
              contract="Tellago.ServiceModel.Samples.
                       XAMLExtService.ISampleService
              behaviorConfiguration="XamlExtensionBehavior">

        </endpoint>
      </service>
    </services>


    <behaviors>
      <endpointBehaviors>
        <behavior name="XamlExtensionBehavior">
          <XAMLExtension file="<Workflow Directory>\\consoleoutput.xoml"/>
        </behavior>
      </endpointBehaviors>

      <serviceBehaviors>
        <behavior name="ServiceMetadata">
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

</configuration>

Let’s take the following workflow that prints a message to the console.

<SequentialWorkflowActivity
               x:Name="Workflow2"
               xmlns:ns0="clr- namespace:Assembly Name…"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">

 <ns0:WriteLineActivity x:Name="writeLineActivity1"
                             OutputText="XAML Rocks!!!" />

</SequentialWorkflowActivity>

Note: The WriteLine activity is a custom activity attached with the sample code.

When the Echo operation of the service is executed it will output “XAML Rocks!!!” followed by the string passed as a parameter. For instance, After executing the following code on the client side:

service.Echo(“Hello there…”);

We will see the following message as the console output.

XAML rocks!!!
Hello there…

This is just one of multiple techniques that leverage the use of XAML-only workflows and the WCF extensibility programming model. These techniques can be complemented with the use of dependency injection technologies such as Unity or NHibernate.

The source code can be downloaded here.


 

1 Comment

  • XAML only workflows are indeed excellent. At SplendidCRM, we have designed our entire workflow engine around Windows Workflow Foundation and XAML only workflows. It allows SplendidCRM administrators to create new workflows without any developing or compiling.

Comments have been disabled for this content.