Dynamic Workflow Binding Parameters
In Workflow Based projects, we often need to develop custom activities that have the exact set of parameters as an existing method from another class or interface. Since creating a specific activity for each method is a hard work and difficult to maintain, we usually look for a more generic solution.
Windows Workflow Foundation allows you to specify the exact method to use its signature, and can generate a parameter collection according to this signature. A good example for this is the HandleExternalMethodActivity, in which you specify an interface type and a method name, and than the parameters collection refreshes accordingly. Then, you can bind the parameters to values in your workflow.
I’ll take this post as a step-by-step guide to building such an activity: It will guide though creating the Type property, handle the MethodName property, and the dynamic parameters collections. The final activity that this guide builds actually has no logic on it, therefore it does nothing. The reader should implement logic such as calling WCF Services, handling events etc.
You can download a samples project from here.
Building a Dynamic Activity
First, start by creating a new Activity, and change it to inherit from System.Workflow.ComponentModel.Activity instead of inheriting from SequenceActivity. I will use the name DynamicActivity from now on.
public partial class DynamicActivity: Activity
{
...
}
Selecting an Interface Type
Start by creating a new DependencyProperty for the InterfaceType using the appropriate Code Snippet:
public static DependencyProperty TypeProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("Type", typeof(Type), typeof(DynamicActivity));
[Description("Type of the interface")]
[Category("Bursteg")]
[Browsable(true)][Editor(typeof(TypeBrowserEditor), typeof(UITypeEditor))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Type Type
{
get { return ((Type)(base.GetValue(DynamicActivity.TypeProperty))); }
set { base.SetValue(DynamicActivity.TypeProperty, value); }
}
Notice the EditorAttribute above the property. This attribute specifies the type of the UI Editor to use when the developer uses the ellipsis button of this property in the property grid. In this case, the attribute sets the TypeBrowserEditor which opend a new .Net Type Selection Dialog that ships with Windows Workflow Foundation.
Filter Selected Types
Usually you don’t want just to select any .Net type in the Class Browser Dialog. Sometimes you want to select only interfaces, exceptions or classes marked with a custom attribute. In order to control which types are displayed in the Class Browser Dialog and can be selected by the developer, the activity should implement the ITypeFilterProvider interface:
public interface ITypeFilterProvider
{
string FilterDescription { get; }
bool CanFilterType(Type type, bool throwOnError);
}
public
partial class DynamicActivity: Activity, ITypeFilterProvider
{
...
#region ITypeFilterProvider Members
public bool CanFilterType(Type type, bool throwOnError)
{
return type.IsInterface;
}
public string FilterDescription
{
get { return "Please choose an interface"; }
}
#endregion
}
Implementing the MethodName Property
While creating the MethodName dependency property I am placing a TypeConverterAttribute above it. The TypeConverter is this case is responsible to populate a list with the method names that are valid for selection. For this example, all method are valid, but one may want to filter methods according to some logic, such as attribute above methods (DataContractAttribute for example).
This approach is similar to the approach Microsoft had taken when implemented CallExternalMethodActivity. They had created a class called PropertyValueProviderTypeConverter which unfortunately marked an internal.
So, first, I will create the MethodName dependency property. Notice the TypeConverter attribute above it:
public
static DependencyProperty MethodNameProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("MethodName", typeof(string), typeof(DynamicActivity));
[
Description("Method name")]
[Category("Bursteg")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[TypeConverter(typeof(MethodPropertyValueProviderTypeConverter))]
public string MethodName
{
get
{
return ((string)(base.GetValue(DynamicActivity.MethodNameProperty)));
}
set
{
base.SetValue(DynamicActivity.MethodNameProperty, value);
}
}
The core method of the MethodPropertyValueProviderTypeConverter which is the implementation of the TypeConverter that populates the methods names of the type can be found in the GetStandardValues method:
public class MethodPropertyValueProviderTypeConverter : TypeConverter
{
public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
// Get the activity instance
Activity instance = null;
object[] instances = context.Instance as object[];
if (instances != null && instances.Length > 0)
{
instance = (Activity)instances[0];
}
else
{
instance = (Activity)context.Instance;
}
// Get the value of the type property
Type activityType = instance.GetType();
PropertyInfo interfaceTypeProperty = activityType.GetProperty("Type", typeof(Type));
if (interfaceTypeProperty == null)
{
throw new Exception("Cannot find property Type in activity " + activityType.Name);
}
StringCollection validMethods = new StringCollection();
Type type = (Type)interfaceTypeProperty.GetValue(instance, new object[] { });
if (type != null)
{
MethodInfo[] methods = type.GetMethods();
foreach (MethodInfo mi in methods)
{
// Add custom logic here...
validMethods.Add(mi.Name);
}
}
return new TypeConverter.StandardValuesCollection(validMethods);
}
}
Implementing the Parameters Property
In order to handle the event of MethodName property changes, I will implement a ActivityDesigner. This class is typically handles the visual representation of an Activity, but has the ability to handle and event when an activity changes. Additionally this class can dynamically control the properties displayed in the property grid. As an implementation, I will dynamically add parameters to the activity and finally refresh the property grid.
Adding the designer to the activity:
[
Designer(typeof(DynamicActivityDesigner), typeof(IDesigner))]
public partial class DynamicActivity: Activity, ITypeFilterProvider
{
...
}
Implementing the PreFilterProperties, which allows a designer to add items to the
set of properties that it exposes through a TypeDescriptor:
public
class DynamicActivityDesigner : ActivityDesigner
{
protected override void PreFilterProperties(IDictionary properties)
{
base.PreFilterProperties(properties);
DynamicActivity activity = this.Activity as DynamicActivity;
if (activity != null && activity.Type != null && activity.MethodName != null)
{
MethodInfo mi = activity.Type.GetMethod(activity.MethodName);
if (mi != null)
{
//get the parameters and add them as properties
ParameterInfo[] pis = mi.GetParameters();
if (pis != null)
{
foreach (ParameterInfo pi in pis)
{
//add a new parameter
properties[pi.Name] =
new ParameterBindingPropertyDescriptor<DynamicActivity>(
pi.Name,
pi.ParameterType,
new Attribute[] {
new DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
new BrowsableAttribute(true),
new CategoryAttribute("Parameters"),
new DescriptionAttribute(pi.ParameterType.FullName),
new EditorAttribute(typeof(BindUITypeEditor), typeof (UITypeEditor)) });
}
}
}
}
}
}
All together, we get a custom activity that exposes Type and Method properties. When method is selected, the parameters are dynamically appear on the property grid.
You can download a samples project from here.
Hope you find it useful. I did.
Enjoy!