Welcome to MSDN Blogs Sign in | Join | Help

It is time for a change

After 8 years working on BizTalk and Windows Workflow Foundation I have decided it is time to work on testing a new product.  Starting yesterday I am now working on the Live Mesh team.  I really enjoyed helping answer questions and putting together samples to help people better understand Windows Workflow Foundation, and I hope to be able to do the same thing with Live Mesh once I get up to speed.
Posted by tomlake | 0 Comments
Filed under:

Are you getting a "The root activity type is invalid" error with the Custom Activity Designer sample?

Several peolple have gotten a "The root activity type is invalid" error when using the Custom Activity Designer sample that can be found at http://wf.netfx3.com/files/folders/activity_behavior/entry840.aspx.  I created an update project that should resolve the problem, which can be downloaded from the link below.
Posted by tomlake | 1 Comments
Attachment(s): FlowChart.zip

State machine workflow web service example

Being able to have a StateMachineWorkflow hold state across multiple different web service calls can be very useful.  This example will show you how to get start.  The first step is to create your interface.  For this example I will be using the following:

 

public interface IOrderService

{

    string CreateOrder(string id);

    string UpdateOrder(string id);

    string ProcessOrder(string id);

    void CancelOrder(string id);

    string ShipOrder(string id);

    string GetOrderStatus();

}

 

To implement one of the methods add a WebServiceInputActivity, which is an IEventActivity, into an EventDriven on the state machine workflow or any of the states contained within it.  After setting the InterfaceType and the Method name the input parameters will appear in the property browser.  Bind them to workflow level variables so that you can use them across the workflow.  Add any activities needed to do the work for the method followed by a WebServiceOutputActivity.  After setting the InputActivityName the out parameters for the method are displayed so that you can bind to the variables containing the results.  After implementing all the methods you need right click on the project and choose Publish as Web Service to create the web service project.

 

In order to make multiple calls to the same web service you need to make sure that you have added a CookieContainer like below:

 

localhost.StateMachineWorkflow_WebService webservice = new localhost.StateMachineWorkflow_WebService();

webservice.CookieContainer = new CookieContainer();

 

After the host restarts If you want to send a web service method call to an already started instance you need the instance id for the workflow.  Then you add it to the cookie container like the folowing:

 

webservice.CookieContainer.Add(new Cookie("WF_WorkflowInstanceId", instanceId, "/", "localhost"));

 

If you are using other items in the cookie container serializing / deserializing the entire container would contain the required workflow instance id and you would not need to use the suggestion above.

 

Before running the example below make sure you follow the steps in the readme.txt file.

Posted by tomlake | 0 Comments
Filed under: ,

Attachment(s): StateMachineWebServiceExample.exe

How to use custom ActivityCondition or filter which ActivityConditions are available

If you have your own custom ActivityCondition you are only going to be able to use it with your custom activities, not out of the box activities like While or IfElseBranch.  First start by creating your custom ActivityCondition like the following:

 

[DisplayName("Custom Activity Condition")]

public class CustomActivityCondition : ActivityCondition

{

    public override bool Evaluate(Activity activity, IServiceProvider provider)

    {

        return (this.customCondition.Equals("foo", StringComparison.CurrentCultureIgnoreCase));

    }

 

    private string customCondition = string.Empty;

    public string CustomCondition

    {

        get{ return this.customCondition; }

        set{ this.customCondition = value; }

    }

}

 

Next create a custom TypeConverter that will add your activity condition to the list of available activity conditions for your user to select like below:

 

public class CustomActivityConditionTypeConverter : TypeConverter

{

    private Hashtable conditionDecls = new Hashtable();

 

    public CustomActivityConditionTypeConverter()

    {

        AddTypeToHashTable(typeof(RuleConditionReference));

        AddTypeToHashTable(typeof(CodeCondition));

        AddTypeToHashTable(typeof(CustomActivityCondition));

    }

 

    private void AddTypeToHashTable(Type typeToAdd)

    {

        string key = typeToAdd.FullName;

        object[] attributes = typeToAdd.GetCustomAttributes(typeof(DisplayNameAttribute), false);

        if (attributes != null && attributes.Length > 0 && attributes[0] is DisplayNameAttribute)

            key = ((DisplayNameAttribute)attributes[0]).DisplayName;

        this.conditionDecls.Add(key, typeToAdd);

    }

 

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)

    {

        if (sourceType == typeof(string))

            return true;

        return base.CanConvertFrom(context, sourceType);

    }

 

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)

    {

        if (value is string)

        {

            if (((string)value).Length == 0 || ((string)value) == "(None)")

                return null;

            else

                return Activator.CreateInstance(this.conditionDecls[value] as Type);

        }

 

        return base.ConvertFrom(context, culture, value);

    }

 

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)

    {

        if (destinationType == typeof(string))

            return true;

        else

            return base.CanConvertTo(context, destinationType);

    }

 

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)

    {

        if (value == null)

            return "(None)";

 

        object convertedValue = null;

        if (destinationType == typeof(string) && value is ActivityCondition)

        {

            foreach (DictionaryEntry conditionTypeEntry in this.conditionDecls)

            {

                if (value.GetType() == conditionTypeEntry.Value)

                {

                    convertedValue = conditionTypeEntry.Key;

                    break;

                }

            }

        }

 

        if (convertedValue == null)

            convertedValue = base.ConvertTo(context, culture, value, destinationType);

 

        return convertedValue;

    }

 

    public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)

    {

        ArrayList conditionDeclList = new ArrayList();

 

        conditionDeclList.Add(null);

        foreach (object key in this.conditionDecls.Keys)

        {

            Type declType = this.conditionDecls[key] as Type;

            conditionDeclList.Add(Activator.CreateInstance(declType));

        }

        return new StandardValuesCollection((ActivityCondition[])conditionDeclList.ToArray(typeof(ActivityCondition)));

    }

 

    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)

    {

        return true;

    }

 

    public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)

    {

        return true;

    }

 

    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)

    {

        PropertyDescriptorCollection props = new PropertyDescriptorCollection(new PropertyDescriptor[] { });

 

        TypeConverter typeConverter = TypeDescriptor.GetConverter(value.GetType());

        if (typeConverter != null && typeConverter.GetType() != GetType() && typeConverter.GetPropertiesSupported())

        {

            return typeConverter.GetProperties(context, value, attributes);

        }

 

        return props;

    }

 

    public override bool GetPropertiesSupported(ITypeDescriptorContext context)

    {

        return true;

    }   

}   

 

Then to use your custom type converter you just need to add the TypeConverterAttribute to any properties that are ActivityCondition like below:

 

[Browsable(true)]

[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]

[TypeConverter(typeof(CustomActivityConditionTypeConverter))]

public ActivityCondition MyCustomCondition

{

    get

    {

        return ((ActivityCondition)(base.GetValue(CustomActivityWithCustomActivityCondition.MyCustomConditionProperty)));

    }

    set

    {

        base.SetValue(CustomActivityWithCustomActivityCondition.MyCustomConditionProperty, value);

    }

}

 

To filter the types of ActivityCondition that can be used update the type converter’s constructor to add only the types you want to use like the following which will only display RuleConditionReference and the custom activity condition:

 

public CustomActivityConditionTypeConverter()

{

    AddTypeToHashTable(typeof(RuleConditionReference));

    AddTypeToHashTable(typeof(CustomActivityCondition));

}

 

 

Switch activity

The Switch activity I created is based on the switch statement in C# or the Select Case statements in VB.  You have an expression and one or more case statements, one of which can be the default.  To model within workflow the case becomes a property that must be on the children of the Switch activity, which you can do this a couple of ways.  One would be to have a Case activity with a Cases property that would be a required child.  Another would be to have an attached Cases property that would be added to all children of the Switch.  I chose to use an attached property because I think it gives a better user experience and it makes for a better sample since I don’t think there are that many out there for advance attached property scenarios.

 

When you use have an attached property it will not be displayed in the property browser until you create an IExtenderProvider and implement the CanExtend method like the following:

 

bool IExtenderProvider.CanExtend(object extendee)

{

    return (extendee is Activity) && (((Activity)extendee).Parent is Switch);

} 

 

Along with the CanExtend method you also need to implement methods used for getting and setting the value of the property.  The naming convention that must be followed is the “Get” / “Set” followed by the name of the property.  For the switch activity I have the following:

 

[TypeConverter(typeof(ActivityBindTypeConverter))]

[CategoryAttribute("Switch"), ]

      [DescriptionAttribute("If any of the values in Cases equals the Switch's Expression then this activity will be run.")]

public object GetCases(Activity theActivity)

{

    object value = null;

 

    if (theActivity.Parent is Switch)

    {

        if (theActivity.IsBindingSet(Switch.CasesProperty))

            value = theActivity.GetBinding(Switch.CasesProperty) as object;

        else

            value = theActivity.GetValue(Switch.CasesProperty) as object;

    }

 

    return value;

}

 

public void SetCases(Activity theActivity, object value)

{

    if (theActivity.Parent is Switch)

    {

        if (value is ActivityBind)

            theActivity.SetBinding(Switch.CasesProperty, value as ActivityBind);

        else

            theActivity.SetValue(Switch.CasesProperty, value);

    }

}

 

Notice that any attribute that you would normally place on the property you will be adding above the GetPropertyName method.  Once you have the IExtenderProvider you link it up with the activity in the designer’s Initialize method:

 

protected override void Initialize(Activity activity)

{

    base.Initialize(activity);

 

    IExtenderListService extenderListService = (IExtenderListService)GetService(typeof(IExtenderListService));

    if (extenderListService != null)

    {

        bool foundExtender = false;

        foreach (IExtenderProvider extenderProvider in extenderListService.GetExtenderProviders())

        {

            if (extenderProvider.GetType() == typeof(CasesExtenderProvider))

                foundExtender = true;

        }

 

        if (!foundExtender)

        {

            IExtenderProviderService extenderProviderService = (IExtenderProviderService)GetService(typeof(IExtenderProviderService));

            if (extenderProviderService != null)

                extenderProviderService.AddExtenderProvider(new CasesExtenderProvider());

        }

    }

}

 

With attached properties you are also able to include a validator:

 

public static DependencyProperty CasesProperty = DependencyProperty.RegisterAttached("Cases", typeof(object), typeof(Switch), new PropertyMetadata(null), typeof(CasesValidator));

 

The validator in this case is validating that the Cases property is only set on a direct child of a Switch:

 

public class CasesValidator : Validator

{

    public override ValidationErrorCollection  Validate(ValidationManager manager, object obj)

    {

        ValidationErrorCollection errors = base.Validate(manager, obj);

 

        Activity activity = manager.Context[typeof(Activity)] as Activity;

        if (activity != null)

        {

            Switch switchAct = activity.Parent as Switch;

            if (switchAct == null)

                errors.Add(new ValidationError("The Cases property can only be set on a direct child of a Switch activity.", 123, false, "Cases"));

        }

 

        return errors;

    }

}

 

Since you can bind the Cases and Expression properties the value might not be set until runtime so validation of the values should be done in the Switch activity’s Initialize method.  If either are set to invalid values an exception is thrown to the user.  The current implementation only allows the properties to be set to a primitive, string or enum value.

Posted by tomlake | 4 Comments
Attachment(s): SwitchActivity.exe

Basics of Working with Custom Types in Workflow

If you have used a custom type for an activity / workflow property you have probably ran into a few issues.  You might have had problems trying to get it serialized correctly or to be able to set the value in the property browser.  With this post I will try and shed some light on some of the basics.  I will be working with the following custom type:

 

[Serializable()]

public class MyType

{

    string paramOne;

    string paramTwo;

 

    public MyType(string paramOne, string paramTwo)

    {

        ParamOne = paramOne;

        ParamTwo = paramTwo;

    }

 

    public string ParamOne

    {

        get { return paramOne; }

        set { paramOne = value; }

    }

 

    public string ParamTwo

    {

        get { return paramTwo; }

        set { paramTwo = value; }

    }

}

 

The default workflow serialization works pretty good for types that have a default constructor.  However, if your type is like the one above, which doesn’t, you aren’t going to get what you expect is you use the activity in a code only workflow.  If the property has a default value other than null in the designer.cs file you will see something like the following:

 

this.activity11.MyTypeProperty = ((MyType)(resources.GetObject("activity11.MyTypeProperty")));

 

And if you are using code separation it will look like the following:

 

<ns0:Activity1 x:Name="activity11">

      <ns0:Activity1.MyTypeProperty>

            <ns0:MyType ParamTwo="" ParamOne="" />

      </ns0:Activity1.MyTypeProperty>

</ns0:Activity1>

 

The code separation version looks good, but when you do anything that causes deserialization, like building or closing & reopening the workflow, you will end up with the following error message:

 

CreateInstance failed for type 'ActivityLibrary1.MyType'. No parameterless constructor defined for this object.

First, I will start with the code only workflow.  To get the proper serialization in the designer.cs file you need to have a CodeDomSerializer like the following:

 

public class MyTypeCodeDomSerializer : CodeDomSerializer

{

    public override object Serialize(IDesignerSerializationManager manager, object value)

    {

        MyType customType = value as MyType;

        return new CodeObjectCreateExpression(typeof(MyType), new CodeExpression[] { new CodePrimitiveExpression(customType.ParamOne), new CodePrimitiveExpression(customType.ParamTwo) });

    }

}

 

To link the serializer to the type use the following DesignerSerializerAttribute on the type:

 

[DesignerSerializer(typeof(MyTypeCodeDomSerializer), typeof(CodeDomSerializer))]

 

Now, below is what you will get in the designer.cs file:

 

this.activity11.MyTypeProperty = new ActivityLibrary1.MyType("", "");

 

For the code separation workflow issue you need to create a WorkflowMarkupSerializer to deserialize the custom type but you will also need to do the serialization.  Start by overriding the ToString method on your type.  For this example I have the following:

 

public override string ToString()

{

    return string.Format("ParamOne={0}, ParamTwo={1}", this.paramOne, this.paramTwo);

}

 

This will get your type serialized in a format that you know how to deal with and gives you the side benefit of having the property browser display the value of your type instead of its name.  The workflow markup serializer would look like the following:

 

public class MyTypeSerializer : WorkflowMarkupSerializer

{

    protected override bool CanSerializeToString(WorkflowMarkupSerializationManager serializationManager, object value)

    {

        return true;

    }

 

    protected override object DeserializeFromString(WorkflowMarkupSerializationManager serializationManager, Type propertyType, string value)

    {

        MyType myType = null;

 

        if (!value.Contains("x:Null"))

        {

            string[] parameters = value.Split(new char[] { ',' }, 2);

            if (parameters.Length == 2)

            {

                string paramOneMatch = "ParamOne=";

                string paramTwoMatch = "ParamTwo=";

                string paramOneVal = parameters[0].Trim();

                string paramTwoVal = parameters[1].Trim();

 

                if (paramOneVal.StartsWith(paramOneMatch, StringComparison.OrdinalIgnoreCase)

                    && paramTwoVal.StartsWith(paramTwoMatch, StringComparison.OrdinalIgnoreCase))

                {

                    paramOneVal = paramOneVal.Substring(paramOneMatch.Length);

                    paramTwoVal = paramTwoVal.Substring(paramTwoMatch.Length);

                    myType = new MyType(paramOneVal, paramTwoVal);

                }           

            }

        }

 

        return myType;

    }

}

 

You link it to the type with the following attribute:

 

[DesignerSerializer(typeof(MyTypeSerializer), typeof(WorkflowMarkupSerializer))]

 

Now you should be able to add your custom activity to either a code only or seperated workflow and you should be able build without getting any errors, assuming you don’t have some other validation issue.

 

You can now add an activity with a custom type to a workflow and build it, but to set the value you have to edit the code or xoml file by manually.  The value displayed in the property browser isn’t editable so you can see what it is set to but not change it.  To allow the user to edit the value you need to add a TypeConverter.  I’ll start with making the string value of the type editable.  For this we need to override two sets of methods, one for converting to and one for from.  Because we aren’t going to supply a list of values we also need to say we don’t support standard values.  What we end up with is the following:

 

public class MyTypeTypeConverter : TypeConverter

{

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)

    {

        if (destinationType == typeof(MyType))

        {

            return true;

        }

        return base.CanConvertTo(context, destinationType);

    }

 

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)

    {

        if (context.PropertyDescriptor.PropertyType.IsAssignableFrom(typeof(MyType)) && destinationType == typeof(string))

        {

            if (value == null)

            {

                return string.Empty;

            }

            else

            {

                return (value as MyType).ToString();

            }

        }

        return base.ConvertTo(context, culture, value, destinationType);

    }

 

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)

    {

        if (context.PropertyDescriptor.PropertyType == typeof(MyType))

        {

            return true;

        }

        return base.CanConvertFrom(context, sourceType);

    }

 

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)

    {

        if (context.PropertyDescriptor.PropertyType == typeof(MyType))

        {

            return Helper.Deserialize(value as string);

        }

        return base.ConvertFrom(context, culture, value);

    }

 

    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)

    {

        return false;

    }

}

 

You link it to the type with the following attribute:

 

[TypeConverter(typeof(MyTypeTypeConverter))]

 

This is nice to be able to quickly edit here, but it isn’t the user experience that most people would expect.  If you have more than two or three properties making the property browser wider just isn’t going to work.  For this we need to create a separate line in the property browser for each property.  You do this my adding the following pair of methods:

 

public override bool GetPropertiesSupported(ITypeDescriptorContext context)

{

    return true;

}

 

public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)

{

    ArrayList properties = new ArrayList();

    MyType customType = value as MyType;

    if (customType != null && context != null)

    {

        PropertyDescriptorCollection props = TypeDescriptor.GetProperties(value, new Attribute[] { BrowsableAttribute.Yes });

        

        properties.Add(props["ParamOne"]);

        properties.Add(props["ParamTwo"]);

 

        return new PropertyDescriptorCollection((PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor)));

    }

    return base.GetProperties(context, value, attributes);

}

 

What we have in place now works great if you have a default value that is not null.  However, if it is null your user would need to know the correct string format or they would still need to manually edit the code or xoml file. To fix this the final step is to create an editor. Start by creating add a Windows Form to you project that will allow users to set all the needed properties.  When you are done with that it is time to create a UITypeEditor and override the EditValue method.  This is where you will launch the form and return a new instance of your custom type.

 

There is a link below that contains this complete project with sample workflows showing the serialization.  When you are working on developing this project you may find that the property browser and / or the editor is not behaving as you would expect.  The easiest way to resolve this is to do a Build Clean, close VS and reopen the project.  It can be a pain but the reason is VS is keeping a reference to an older version of the type.
Posted by tomlake | 7 Comments
Attachment(s): WorkingWithCustomTypes.EXE

Do you want to invoke another workflow but you won't know which one until runtime?

The InvokeWorkflowActivity that ships with WF requires you to set the type of the target workflow at design time.  If you won’t know which workflow to invoke until runtime you need to write a custom activity. To invoke a workflow need to use the IStartWorkflow service that you can get from the ActivityExecutionContext, like the following:

 

IStartWorkflow startWorkflow = executionContext.GetService(typeof(IStartWorkflow)) as IStartWorkflow;

 

Guid instanceId = startWorkflow.StartWorkflow(this.TargetWorkflow, this.Parameters);

 

Since the parameters won’t be known until runtime you can’t use the WorkflowParameterBindingsCollection because you wouldn’t be able to change in at runtime.  Instead I have have a parameters property that is type Dictionary<string, object>.

Do you need to launch another process from your workflow?

When start another process from your workflow you can’t just not return from the Execute method until the process completes.  This would block the entire workflow from receiving any other events and would hold onto the thread.  Instead you need to use a queue to get notification when the process completes.

Posted by tomlake | 1 Comments
Attachment(s): ProcessActivity.EXE

How to set activity property from a custom input form

Sometime you might want to create an input form for your user to set properties on your custom activity.  Unfortunately when you set the properties directly on the activity from the input form the values don’t always get reflected on your activity.  To resolve this you need to set the value on the PropertyDescriptor for the property instead, like the following:

 

PropertyDescriptor firstPropDescr = TypeDescriptor.GetProperties(this.activity)["FirstName"];

firstPropDescr.SetValue(this.activity, txtFirstName.Text);

 

Below is a link to a sample employee activity that when you double click in it a input form pops up to enter the first and last name.  Clicking the OK button sets the values on the activity.

Posted by tomlake | 1 Comments
Attachment(s): InputActivity.exe

How to send data to a workflow

Sometimes you need to send data to a workflow from the host.  To do this you need create an interface that is decorated with the ExternalDataExchange attribute and contains an event delegate like the following:

 

[ExternalDataExchange]

public interface IMyService

{

    event EventHandler<CompletedEventArgs> Completed;

}

 

In your workflow add a HandleExternalEventActivity and set its InterfaceType to the interface that contains the event delegate.  Next select the EventName for the event that you want to handle.  To raise the event from the host you need to implement the interface like the following:

 

public class MyService : IMyService

{

    public void RaiseEvent(CompletedEventArgs args)

    {

        EventHandler<CompletedEventArgs> completed = this.Completed;

        if (completed != null)

            completed(null, args);

    }

 

    public event EventHandler<CompletedEventArgs> Completed;

}

 

Then after creating the workflow runtime you need to add your custom service to the ExternalDataExchangeService and it needs to be added to the workflow runtime.

 

ExternalDataExchangeService dataExchange = new ExternalDataExchangeService();

workflowRuntime.AddService(dataExchange);

MyService myService = new MyService();

dataExchange.AddService(myService);

 

Below is a link to a simple sample that just traces out the string that is passed into the workflow.

 

Posted by tomlake | 4 Comments
Filed under:

Attachment(s): SendDataToStateMachine.exe

Project template for XAML Activation Console Application

If you work with XAML activation from time to time and like being able to debug your workflow using a console application you should install these templates.  However, you don’t like having to modify the default console application template each time.  I created sequential and state machine console application project templates for both Visual Basic and C#.   

 

This posting is provided "AS IS" with no warranties, and confers no rights.
Posted by tomlake | 1 Comments
Attachment(s): XAMLActivationTemplates.exe

Examples of using Persistence and Tracking in ASP.NET

When you are working with ASP.NET you may have the need to persistence and / or tracking.  I have put together two examples, one with shared databases and the other with separate, and included the command scripts to create the databases for you.  There are VB and C# versions of each sample.

 

When you run the project you will start on Default.aspx.  If there are no existing orders that have not been completed you are redirected to Order.aspx.  If there are any existing orders there will be a link for each existing order.  Click on the link to start processing the order from its current state.  The order number will be retrieved from the tracking database for any order that is at least in the Created state.  You can click on the enabled button to transition the order through the order process.  

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Using XAML activation in ASP.NET with a StateMachineWorkflow

If you want to use XAML activation in ASP.NET you will need to be using Windows Workflow Foundation Beta 2.2, that can be found here.  The reason for this is that although XAML activation works in most cases with Beta 2 it isn’t officially supported in that build because it was added as a feature at the last minute and was not fully test yet.

 

Below is a sample that uses XAML activation to start a StateMachineWorkflow in ASP.NET.  

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by tomlake | 3 Comments
Attachment(s): ASPNETXAMLActivation.EXE

Couple of custom activities that you can use to synchronize branches of a ParallelActivity

You could use a SynchronizationScopeActivity, but this would block execution all together in the other branches since the thread is locked until the activity is done executing.  If that is not the behavior you are looking for here are two options.  First, WaitFor, will block the branch that it is in until a selected activity in another branch is Closed or Executing.  With this activity in the execute method you find out the execution status for the activity you are waiting for and if it is not status you subscribe for the StatusChanged event.  Once the activity changes to the correct status the WaitFor is closed and the next child in that branch will execute. 

 

protected override ActivityExecutionStatus Execute(ActivityExecutionContext context)

{

      ActivityExecutionStatus status;

      Activity activity = GetExecutableActivity(this.Activity);

 

      switch (activity.ExecutionStatus)

      {

            case ActivityExecutionStatus.Executing:

            if (this.Status == "Closed")

                  {

                        activity.StatusChanged += this.OnStatusChangeHandler;

                        status = ActivityExecutionStatus.Executing;

                  }

                  else

                  {

                        status = ActivityExecutionStatus.Closed;

                  }

                  break;

            case ActivityExecutionStatus.Closed:

                  status = ActivityExecutionStatus.Closed;

                  break;

            default:

                  activity.StatusChanged += this.OnStatusChangeHandler;

                  status = ActivityExecutionStatus.Executing;

                  break;

      }

 

      return status;

}

 

The other, SynchronizeActivity, you put one or more, must be equal, in each branch.  When the activity executes is checks the other synchronize activities at the same level to see what there status is.  If they are executing it ignores them and if they are initialized it will subscribe for the Executing event on that sybling.  Once they are all executing then they are all closed allowing the next child in each branch to execute.

 

private ActivityExecutionStatus SubscribeSibling(ActivityExecutionContext context)

{

    this.Properties.Clear();

     

    ActivityExecutionStatus childStatus;

    bool closed = false;

    int count = 0;

    int index = 0;

      CompositeActivity parent = this.Parent;

 

    //  First find out if there are more than one syncronize in each branch

    //  and if so which one this one is.

    foreach (Activity child in parent.Activities)

    {

        if (child is Synchronize)

        {

                  if (child == this)

            {

                index = count;

            }

            ++count;                   

        }

    }

 

      foreach (CompositeActivity sequence in parent.Parent.EnabledActivities)

    {

        if (sequence != parent)

        {

            count = 0;

 

                  foreach (Activity child in sequence.EnabledActivities)

            {

                if (child is Synchronize)

                {

                    if (count == index)

                    {

                        childStatus = child.ExecutionStatus;

                        switch (childStatus)

                        {

                            case ActivityExecutionStatus.Executing:

                                break;

                            case ActivityExecutionStatus.Closed:

                                closed = true;

                                break;

                            default:

                                                this.Properties.Add(child.Name, child.Name);

                                child.Executing += this.OnStatusChangeHandler;

                                break;

                        }

                        break;

                    }

                    else

                    {

                        ++count;

                    }

                }

            }

        }

    }

 

      if (this.Properties.Count == 0)

    {

        return ActivityExecutionStatus.Closed;

    }

    else

    {

            if (closed)

            {

                  throw new Exception("One of the syblings is already closed but there are '" + this.Properties.Count + "' syblings that are still being waited on.");

            }

            else

            {

                  return ActivityExecutionStatus.Executing;

            }

    }

}

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by tomlake | 1 Comments
Attachment(s): SynchronizationActivities.exe

XAML activation and how to use it

XAML activation is when you use the serialized xoml file to create and run a workflow instance.  You will use one of the following WorkflowRuntime methods to create the workflow instance:

 

public WorkflowInstance CreateWorkflow(XmlReader workflowDefinitionReader);

public WorkflowInstance CreateWorkflow(XmlReader workflowDefinitionReader, XmlReader rulesReader, Dictionary<string, object> namedArgumentValues);

public WorkflowInstance CreateWorkflow(XmlReader workflowDefinitionReader, XmlReader rulesReader, Dictionary<string, object> namedArgumentValues, Guid instanceId);

 

Activation can be used for sequential or statemachine workflow, samples for each are attached to this post.  You are not able to use any code beside.  This will require you to add any needed variables or handlers to a compiled type, like your base activity. 

 

The only difference between a xoml file used with activation and one that is compiled into a type is the x:Class attribute.  The attribute  is used by the workflow author to explicitly state that the workflow must be compiled into a type and to define the class name for that type.

 

If you are getting an exception when the call to CreateWorkflow is made try the following to help resolve the error(s):

 

try

{

    WorkflowInstance instance = workflowRuntime.CreateWorkflow(xomlReader, ruleReader, parameters);

    instance.Start();

 

    waitHandle.WaitOne();

}

catch (WorkflowValidationFailedException exp)

{

    StringBuilder errors = new StringBuilder();

    foreach (ValidationError error in exp.Errors)

    {

        errors.AppendLine(error.ToString());

    }

    MessageBox.Show(errors.ToString(), "Workflow Validation Errors");

}

 

To bind to an event or condition handler in the base workflow you will need to manually update the xoml file since you can’t use the designer to do this.  The syntax to use is the following:

 

      EventOrConditionName="{ActivityBind NameOfWorkflowInstance,Path=EventOrConditionHandler}"

 

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by tomlake | 4 Comments
Attachment(s): XAMLActivation.exe
More Posts Next page »
 
Page view tracker