Mapping WF 3 activities to WF 4

Mapping WF 3 activities to WF 4

First, this post is not about any automated tools for mapping your activities, so don’t get too excited. :)   Instead, I wanted to take the opportunity to talk about the changes to the base activity library in the context of the activities available today in WF 3.  If you are using WF today, there are some activities that have direct counterparts, while others are split into multiple activities and yet others disappear completely.  Of course there are also some new activities that were not available in WF 3.  In the tables below, I outline each of these different categories with some notes.  Hopefully this mapping helps you see what is changing, what is new, and what goes away. 

NOTE: this information is based on Beta 1 of WF 4 and some changes are possible, though nothing big is likely, between now and the RTM. 

Activities with direct or indirect equivalents

WF3 Activity WF4 Activity Notes
Delay Delay The activity works the same, but the timers are handled differently in the framework.
Sequence Sequence  
Parallel Parallel Similar, but the internals of execution may differ slightly by RTM. 
Replicator ForEach<T>, ParallelForEach<T>,
ForEach, ParallelForEach
These provide <optional> typed access to the instance data and truly declarative authoring experience.  Each represents the different execution modes of the Replicator activity – so it has been split into four similar activities.  I think most people will use the generic versions more than the others, but time will tell.
CallExternalMethod InvokeMethod, InvokeMethod<T> Provides .NET method invocation and optional return of a typed return value.  This can be a call on an instance stored in a variable or a static method.
HandleExternalEvent Receive, ReceiveAndSendReply WCF messaging activities replace the Local communications model, even for host-> workflow communication. 
Listen Pick The Pick activity is the primary WF4 activity that replaces the Listen and the State which were both containers for EventDriven activities. 
EventDriven PickBranch This is an indirect mapping and you don’t use the PickBranch outside the pick, but it serves the same basic purpose of the EventDriven. 
Compensate Compensate Though the mechanism are slightly different, the activity serves the same purpose – to execute the compensation handler for a compensable scope. 
CompensatableSequence CompensableActivity The new activity includes a ConfirmationHandler which can execute when a confirmation is signaled using the Confirm activity. 
FaultHandler(s) TryCatch The try catch logic is more explicit now and you use the TryCatch activity to model your fault handling instead of using fault handlers on the composite activities. 
IfElse If In WF 4, this can only have two branches, the If and the Else.  For more branches, use the switch activity.
InvokeWebService Send, SendAndReceiveReply All web service communication in WF4 uses WCF. 
Throw Throw  
TransactionScope TransactionScopeActivity  
WebServiceInput (output and fault) Receive, ReceiveAndSendReply WCF is THE messaging system to use with WF.
While While/DoWhile WF4 introduces the DoWhile in addition to the While to ensure the first iteration executes.

 

WF3 Activities with no direct WF4 equivalent

WF3 Activity Note
ConditionedActivityGroup Based on limited use (my guess) this activity was not moved to WF4.
Code There is no code-behind file for workflows so there is no place to write code in the workflow.  Create custom activities or use expressions where appropriate. 
EventHandlingScope No real equivalent, probably b/c this is an activity that gets overlooked or people use the state machine instead. 
InvokeWorkflow In the Beta, there is no activity like this one.  One option is to host child workflows as WCF services and use the Send or SendAndReceiveReply messaging activities to start the child workflows. 
Policy In order to use rules in WF4, create a WF3 activity with a Policy activity inside it.  Create properties on the activity and use them in the policy definition.  Then use the InteropActivity to invoke the WF3 activity and execute the policy.  You can use the properties on the activity as input and outputs to the policy.
Suspend In WF4 there is more focus on having a “suspend on error” style exception handling, so direct suspend is not currently supported in the form of an activity.
SynchronizationScope Again, my assumption here, this was not used a lot by folks so didn’t get moved over. 
Terminate No direct option to terminate, but exception handling has changed so that when a workflow throws an exception, you can abort, terminate or cancel it. 
CompensatableTransactionScope In WF4, use a Compensable activity and put a TransactionScopeActivity in the body. 
State, StateInitialization, StateFinalization There is no State Machine workflow in WF 4. 

 

WF4 activities with no direct WF3 equivalent

AddToCollection<T> Helper activity to simplify declarative workflow development and manipulation of collection variables.
Assign Assigns a value to a variable – useful for declarative workflows. 
CancellationScope Allows you to define a scope of work and the steps to take if that work is canceled.  Replaces the cancelation handler in WF3. 
ClearCollection<T>  
Confirm Schedules the Confirmation logic for a Compensable activity. 
ExistsInCollection<T>  
Persist Explicit declaration of persistence from the workflow. Replaces the need for the PersistOnClose attribute on activities.
RemoveFromCollection<T>  
Switch<T> Provides multiple branches of execution each based on a specific result from evaluating an expression. 
Interop Executes a WF3 activity in the context of a WF4 workflow.  All public properties on the activity become In/Out arguments.  Custom designers are not supported. 

Dallas DevCares – Parallelism in .NET 4.0

Dallas TechFest is behind me, and now it is time to look forward.  Forward to Visual Studio 2010, Forward to .NET 4.0.

Tomorrow (Friday, June 26th) I will be presenting at the Dallas DevCares event on Parallelism in .NET 4.0.  This talk is one I’ve given before, but two things will be different this time.  First, I will be presenting with the use of Visual Studio 2010 Beta 1, so you will see how this actually works on actual .NET 4.0 bits.  Second, I will be presenting for a somewhat longer time than my usual User Group talk, we will be have time to take lots of questions, and to cover some fundamentals that I might not normally cover at this talk.

I’d love to see any of my readers and followers there, so cruise over to their website and register.

I’ll be speaking at Code Camp San Diego this weekend

San Diego Code Camp is this weekend! I’ll be there on Saturday, doing two presentations:

  • A Technical Drilldown into “All Things M” (part of the forthcoming Oslo platform)
  • A First Look at BizTalk Server 2009: Integration Server, SOA Foundation, Gateway to Azure

If you’ve never been to a Code Camp before, you should check it out. It’s an “anything goes” community-driven conference-ish event. Lots of high quality sessions, at a recession-friendly cost (free). Lots of learning opportunities. What’s not to like?

As usual, this will be at UCSD La Jolla. See you there!

Full details are at http://www.socalcodecamp.com/

Technorati Tags: Azure,Cloud,SOA,ESB,BizTalk,Oslo,M,Models

Exposing Custom WCF Headers through WCF Behaviors – Part 3

.style1 {
font-family: “Courier New”, Courier, monospace;
font-size: x-small;
}
.style2 {
font-family: “Courier New”, Courier, monospace;
font-size: x-small;
}
.style3 {
font-size: x-small;
}
.style5 {
font-size: x-small;
}
.style6 {
text-align: center;
}

In part 1, I covered how to create a custom behavior to inject headers into the dynamically created WSDL.  In part 2, I showed how to either promote or write the header data to the BizTalk context. 


What happens if I want different headers for different end points?  What if I don’t want to create a custom header component for each end point?  What if I want to set, through configuration, weather I want to promote or write to the context?  What if I want to add a new header item without the need to recompile?


In this part of the series we will look at the ability to create a behavior that exposes the properties through configuration to let you dynamically, per end point, set the header items.  The configuration is not through a configuration file but instead will hook into the end point behavior dialog box that appears in the adapter configuration in BizTalk.


The finished configuration will look like this:



Let’s start looking at code. 


First we are going to look at a new class file that will represent the data that we need to set in the configuration section of the dialog box.  The CustomHeader class is where much of the configuration dialog magic happens.  The way you define the properties will define the way they appear in the configuration dialog box.  If you define your property as a boolean or an enum then it will display as a drop down list box.  If you define it as a class then you will get the ellipses. 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace
Services.WCF.Behavior
{
    public class CustomHeader
   
{
        public string Name { get; set; }

        public string Namespace { get; set; }

        public bool Required { get; set; }

        public ContextAction Action { get; set; }
   
}
}


Where the ContextAction type is the enum listed below:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace
Services.WCF.Behavior
{
    public enum ContextAction
   
{
        None = 0,
        Write = 1,
        Promote = 2
   
}
}


The CustomHeader class is accessed and ‘bound’ to the dialog box through the CustomHeaderEndpointBehavior class.  This class is just a renamed and modified version of the SoapHeaderEndpointBehavior class we saw in the previous two articles.


using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.Configuration;
using System.ServiceModel;

namespace
Services.WCF.Behavior
{
    public class CustomHeaderEndpointBehavior : BehaviorExtensionElement, IEndpointBehavior
   
{
        #region BehaviorExtensionElement Methods

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

        protected override object CreateBehavior()
       
{
            return new CustomHeaderEndpointBehavior(this.Headers);
       
}

        //Copies the content of the specified configuration element to this configuration element
        public override void CopyFrom(ServiceModelExtensionElement extFrom)
       
{
            base.CopyFrom(extFrom);
            CustomHeaderEndpointBehavior element = extFrom as CustomHeaderEndpointBehavior;
            if (element != null)
           
{
                Headers = element.Headers;
           
}
       
}

        //Both properties are returned as a collection.
        protected override ConfigurationPropertyCollection Properties
       
{
            get
           
{
                if (_properties == null)
               
{
                    _properties = new ConfigurationPropertyCollection();

                   
_properties.Add(new ConfigurationProperty(“Headers”, typeof(List<CustomHeader>), null,
                    new SerializationConverter(typeof(List<CustomHeader>)), null, ConfigurationPropertyOptions.None));
                    base[“Headers”] = new List<CustomHeader>();
               
}
                return _properties;
           
}
       
}

       
#endregion

        #region IEndpointBehavior Members

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
       
{
            bindingParameters.Add(this);
       
}

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
       
{
            //throw new NotImplementedException();
       
}

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
       
{
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomHeaderMessageInspector());
       
}

        public void Validate(ServiceEndpoint endpoint)
       
{
            //throw new NotImplementedException();
       
}

       
#endregion

        #region Class properties
       
[ConfigurationProperty(“Headers”)]
        public List<CustomHeader> Headers
       
{
            get
           
{
                return (List<CustomHeader>)base[“Headers”];
           
}
            set
           
{
                base[“Headers”] = value;
           
}
       
}

       
#endregion

        #region Class Fields
        private ConfigurationPropertyCollection _properties;
       
#endregion

        #region Constructors
        public CustomHeaderEndpointBehavior(List<CustomHeader> headers)
       
{
            this.Headers = headers;
       
}
        public CustomHeaderEndpointBehavior() : base()
        {}

       
#endregion
   
}
}


First you will notice that there is a number of new methods.  The first is the CopyFrom method.  This method is needed to copy the contents of the configuration data entered in the dialog box so that we can gain access to it within our class.  We then override ConfigurationPropertyCollection since the header properties are returned as a collection.  We will add the properties to our internal collection based on the List<CustomHeader> object.  We also add the class to the AddBindingParamters method.


One thing to note when creating the configuration class (in our case the CustomHeader class) is that the default behavior of the Transport Properties dialog box and underlying code expects that all configuration information will be of type string.  If you are using other types then you need to create your own type converter to convert to a string representation and back.  I have the code for the type converter at the bottom of this post called SerializationConverter.


In the code above, in the CustomHeaderEndpointBehavior class, there were a couple of custom objects.  The first one we will dig into is in the ApplyDispatchBehavior where we add a new CustomHeaderMessageInspector. 


The class implementation for the CustomHeaderMessageInspector looks like:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Xml;

namespace
Services.WCF.Behavior
{
    class CustomHeaderMessageInspector : IDispatchMessageInspector
   
{
        #region IDispatchMessageInspector Members

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
       
{
            List<KeyValuePair<XmlQualifiedName, object>> writeProps = new List<KeyValuePair<XmlQualifiedName, object>>();
            List<KeyValuePair<XmlQualifiedName, object>> promoteProps = new List<KeyValuePair<XmlQualifiedName, object>>();
            string writeKey = “http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/WriteToContext”;
            string promoteKey = “http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/Promote”;

            CustomHeaderEndpointBehavior bhv = null;

            if (instanceContext.Host.Description.Endpoints.Find(channel.LocalAddress.Uri) != null)
               
bhv = instanceContext.Host.Description.Endpoints.Find(channel.LocalAddress.Uri).Behaviors.Find<CustomHeaderEndpointBehavior>();

            if (bhv != null)
           
{
               
foreach (CustomHeader hdr in bhv.Headers)
               
{
           
        int headerPos = OperationContext.Current.IncomingMessageHeaders.FindHeader(hdr.Name, hdr.Namespace);

                    if (headerPos < 0)
                   
{
                        if (hdr.Required)
                       
{
                            //Fault Condition
                            throw new ArgumentNullException(hdr.Name, “Required soap header not found.”);
                       
}
                   
}
                    else
                   
{
                        if (hdr.Action != ContextAction.None)
                       
{
                            // Get an XmlDictionaryReader to read the header content
                           
XmlDictionaryReader reader = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(headerPos);
                           
XmlDocument d = new XmlDocument();

                           
d.LoadXml(reader.ReadOuterXml());
                           
XmlQualifiedName PropName1 = new XmlQualifiedName(hdr.Name, hdr.Namespace);

                            if (hdr.Action == ContextAction.Write)
                           
{
                               
writeProps.Add(new KeyValuePair<XmlQualifiedName, object>(PropName1, d.DocumentElement.InnerText));
                           
}
                            else if (hdr.Action == ContextAction.Promote)
                           
{
                               
promoteProps.Add(new KeyValuePair<XmlQualifiedName, object>(PropName1, d.DocumentElement.InnerText));
                           
}
                       
}
                   
}
               
}
           
}
            else
           
{
                //Debug.WriteLine(“*****AfterReceiveRequest: No Behavior found of type CustomHeaderEndpointBehavior.*****”);
           
}

            if (writeProps.Count > 0)
           
{
               
request.Properties[writeKey] = writeProps;
           
}

            if (promoteProps.Count > 0)
           
{
               
request.Properties[promoteKey] = promoteProps;
           
}

            return null;
       
}

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
       
{
       
}

       
#endregion
   
}
}


As we look at this code, in the AfterReceiveRequest method we have included both the writeProps and the promoteProps variables as well as both namespaces.  We then look in the instanceContext.Host.Description.Endpoints collection to find, within the behaviors collection, our CustomerHeaderEndPointBehavior object.  Then we loop through each of the headers that was setup through configuration and look for them in the message headers collection.  Finally, check if the header was required and if we need to write or promote the values.


Lastly, here is the code that does our type conversion from the CustomHeader class to a string representation (in our case this will be XML).


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Globalization;

namespace
Services.WCF.Behavior
{
    public class SerializationConverter : TypeConverter
   
{
        private Type _type;
        public SerializationConverter(Type type)
       
{
            _type = type;
       
}

        #region Helper Utilities

        private object Deserialize(object value)
       
{
            if (_type == null) throw new ArgumentNullException(“”, “Serialization type was not set. Use the SerializationConvertor(Type) constructor.”);
            return Deserialize(value, _type);
       
}

        private object Deserialize(object value, Type destinationType)
       
{
            StringReader strRdr = null;
            XmlReader xmlRdr = null;

            try
           
{
                if (!(value is string))
                throw new ApplicationException(“Expecting parameter ‘value’ to be of type string. ‘Value’ is of type ” + value.GetType().ToString());

                XmlSerializer serializer = new XmlSerializer(destinationType);
                strRdr = new StringReader(value.ToString());
                xmlRdr = XmlReader.Create(strRdr);

                return serializer.Deserialize(xmlRdr);
           
}
            finally
           
{
                strRdr.Close();
                xmlRdr.Close();
           
}
       
}

        private object Serialize(object value)
       
{
            StringWriter wtr = null;

            try
           
{
                if (value == null)
                throw new ArgumentNullException(“value”);

                StringBuilder sb = new StringBuilder();
                XmlSerializer serializer = new XmlSerializer(value.GetType());
                wtr = new StringWriter(sb);
                serializer.Serialize(wtr, value);

                return sb.ToString();
           
}
            finally
           
{
                wtr.Close();
           
}
       
}

       
#endregion
   
        #region Collection code.
        //Class to string
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
       
{
            if (value is string)
           
{
                return Deserialize(value);
           
}
            else if (value.GetType() == _type)
           
{
                return Serialize(value);
           
}
            else
           
{
                string msg = (_type == null ? “Value is not of type System.string and no type was specified at construction.” : “Value is not of type System.string or ” + _type.ToString() + “.”);
                throw new ApplicationException(msg);
           
}
       
}

        //String to class
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
       
{
            if (value is string)
           
{
                return Deserialize(value, destinationType);
           
}
            else if (value.GetType() == _type)
           
{
                return Serialize(value);
           
}
            else
           
{
                string msg = (_type == null ? “Value is not of type System.string and no type was specified at construction.” : “Value is not of type System.string or ” + _type.ToString() + “.”);
                throw new ApplicationException(msg);
           
}
       
}
       
#endregion
   
}
}


At this point we have all the code that is part of our project.  Once this is compiled, we need to add the assembly to the machine config and add it to the BizTalk WCF endpoint.  The process to implement this remains the same as that described at the bottom of part 1 of this series.


As always, the code in this post and this series are for reference only and are provided as is.  Now that we have that out of the way, I hope that these posts have been helpful and have shown how to deal with header values in the WCF stack and  shown the ability to create a behavior that exposes the properties through configuration to let you dynamically, per end point, set the header items.


 

SOA and Business Process Partner of the Year

[Source: http://geekswithblogs.net/EltonStoneman]

Hitachi Consulting UK has won worldwide SOA and Business Process Partner of the Year in Microsoft’s 2009 WPC awards program. The award was presented for the ESB Guidance solution I’ve been working on for the last 18 months, which has also featured in a walkthrough at the UK SOA/BPM User Group, and in various blog posts.

This is great news for Hitachi, and Microsoft gave us a ringing endorsement:

“Microsoft is proud to recognize Hitachi Consulting UK as the SOA and Business Process Partner of the Year. Hitachi Consulting UK’s solutions have significantly reduced the time and investment needed to develop, test, and deploy new projects for its clients.”

Can Software Architecture Attributes Also Be Applied to Business Processes?

Can Software Architecture Attributes Also Be Applied to Business Processes?

I’m in San Diego attending the Drug Information Association conference with the goal of getting smarter in the functional areas that make up a bio-pharma company.  I’m here with two exceptional architecture colleagues which means that most meals have consisted of us talking shop. 
During dinner tonight, we were discussing the importance (or imperative, really) […]

Spring and Functional Tests

Recently on our project we created a “functional” unit test for some services. The test was designed to check the entire call stack from the service layer to the DAO and return some expected results. We are using Spring for dependency injection through our entire application. This functional test initially only referenced the service DLL’s […]