Welcome to MSDN Blogs Sign in | Join | Help

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 = 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.

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