Dynamic Workflow Binding Parameters

29 באוקטובר 2006

תגיות: , ,
10 תגובות

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!

הוסף תגובה
facebook linkedin twitter email

10 תגובות

  1. Sandy Place8 בדצמבר 2006 ב 19:03

    I did find this useful and was almost what I was looking for. Is there anyway to make these dynamic properties a bindable source for another property? I would like them to show up in the "Bind 'some property' to an existing member" dialog. You see I am trying to write a "QueryActivity" and would like to expose the selected items in the query as bindable properties.

  2. Robert te Kaat2 בינואר 2007 ב 21:29

    Great article. I think I'm going to use it to create a WCF-service invoker activity. First thing I did is add the return-value as well, so it can store the output of the method after execution.

    Too bad there's not much official documentation on these advanced workflow topics. Good thing we have Reflector, though :)

  3. Ethan Hunt17 בינואר 2007 ב 16:25

    Hi,
    Thanks for your article. I now can extract the parameters of my method from Activity. However, how do i acheive to get the Event Args from my class ? I am now writing one Customized Handle External Event Activity but I still cannot extract the Event Args….?
    Can u pls help me ???

  4. Rajan3 בספטמבר 2007 ב 9:18

    Can any one tell me how to dynamically add Property Grid items at run time in c# ?

  5. Peter Schmid14 במרץ 2008 ב 18:04

    Hello Guy

    This is an excellent article! It showed me exactly what I needed! Many Thanx!

  6. Peter Schmid18 במרץ 2008 ב 11:57

    One more comment:
    I defined my workflow in a XOML file. When using the WorkflowParameterBindingCollection class I get into trouble, because it does not provide a parameterless constructor, and it's – how could it be else – seald.
    I guess this means I have to wrap the class and replace the parameter (which is the activity component) with a Property.

  7. ERU15 באפריל 2008 ב 16:20

    I tried to use your DynamicActivity in the re-hosted designer sample. The designer shows the properties and you can add properties dynamically. When you save the workflow you even see the dynamic property values in the XOML file.
    However, if you read the XOML file back into the designer the values of the dynamic properties are gone…

    Do you have any idea how this could be solved?

  8. Paolo Marani20 בנובמבר 2008 ב 9:25

    I did not understand how to properly add bindable runtime properties to the property grid, and their values correctly stored into XOML, could you provide a working example of a complete implementation of dynamic bindable attributes ?

  9. Brian Bull29 בינואר 2009 ב 21:08

    I have implemented something like this, but it doesn't achieve everything I need. I two interfaces: IParent and IChild. IChild inherits IParent. An Add(int x, int y) method is defined in the IParent interface. I am able to choose the IChild interface and select the Add method, but the parameters do not show up in the property grid. If I select the IParent interface, the parameters are there.

    Is there something that needs to be changed in the activity.Type.GetMethod call?

  10. Grigoriy Milman17 ביולי 2009 ב 19:41

    This is one of the best article about Windows Workflow Foundation!
    One question: how it is possible to validate such Dynamic Property?
    For example, if we need to initialized input arguments of the selected method, how we can check method arguments represented by Parameters Property in activity Validator? There are two possibilities here. The first one when argument is bind to something else. The second one when developer explicitly specified value of the argument during designed time. How we can verify that required argument fro the selected method is really set to something?

Comments are closed.