(WF4) Why can’t I change the TypeArgument of Switch<> or FlowSwitch<> using the property grid?

I don’t remember anyone ever actually asking me this, but I found myself wondering the question while rustling up a quick rehosting app.

Foreach<> activity lets you change the TypeArgument dynamically via the property grid. But FlowSwitch<> doesn’t. Why?

image

As usual, although it take a while, I like investigating issues like this in a practical way. Is it possible for us to ‘Add’ the TypeArgument property in the property grid for FlowSwitch<> in the same way that ForEach<> does? Starting off with disassembly diving, we can figure out that there is a bunch of internal classes like FeatureAttribute, UpdatableGenericArgumentsFeature and GenericArgumentUpdaterService which are involved in the addition of the TypeArgument property to the ForEach<> activity’s property grid. We can also figure out that none of that really matters. Because GenericArgumentUpdaterService is really just a helper class for using a regular public API which is AttachedPropertiesService.

What is AttachedPropertiesService, and what is it being used for here?

For a quick and abstract intro on AttachedPropertiesService, see Matt’s old post. In short, what it allows you to do is add ‘extra properties’ on any type which you want to display in WorfklowDesigner. This is pretty awesome in its flexibility and power. Want to add a new, fake property on a built-in framework type? You can!

So, how would we go about adding the updatable TypeArgument property to FlowSwitch<>?

 

Step 1: Register the Attached Property

public static void MakeGenericArgumentUpdatable(EditingContext context)

{

    AttachedPropertiesService aps;

    AttachedProperty property;

           

    aps = context.Services.GetService<AttachedPropertiesService>();

    property = new AttachedProperty<Type>()

    {

        IsBrowsable = true,

        Name = "TypeArgument",

        Getter = GetTypeArgument,

        Setter = SetTypeArgument,

        OwnerType = typeof(FlowSwitch<>),

    };

    aps.AddProperty(property);

}

We passed in an EditingContext from the WorkflowDesigner where we want this attached property to be registered in. The generic type of our attached property is AttachedProperty<Type>, because the type of the virtual property we are creating should be System.Type.

Step 2: Implement the Getter

private static Type GetTypeArgument(ModelItem mi)

{

    object value = mi.GetCurrentValue();

    Type ret = value.GetType().GetGenericArguments()[0]; // Should be first generic argument of FlowSwitch<T>

    return ret;

}

Just pull apart the ModelItem assuming it is a FlowSwitch<X>, and figure out what X is.

 

Step 3: Implement the Setter

 

private static void SetTypeArgument(ModelItem oldModelItem, Type value)

{

           

    Type newItemType = typeof(FlowSwitch<>).MakeGenericType(value);

    object newItem = Activator.CreateInstance(newItemType);

    ModelItem newModelItem = WrapAsModelItem(oldModelItem.GetEditingContext(), newItem);

           

    // use the somewhat magical MorphHelper

    MorphHelper.MorphObject(oldModelItem, newModelItem);

    return;

}

 

private static ModelItem WrapAsModelItem(EditingContext context, object obj)

{

    return context.Services.GetService<ModelTreeManager>().CreateModelItem(null, obj);

}

This bit is the weirdest. MorphHelper.MorphObject is being used to replace one ModelItem in the Model Tree with another, and simultaneously updates all references to the old model item. This is a generally useful trick for ‘swapping in’ one object to replace another in the model tree.

There’s some remaining wrinkles… none of the properties of the old FlowSwitch<> got copied over the new one, ack! Also, the viewstate which tracks the FlowSwitch<>’s position in the designer is lost. Ideally we would also implement that copying behavior in our SetTypeArgument function, so that we don’t unexpected lose flowchart connections whenever someone changes their TypeArgument in the property grid…

You can use MorphHelper.MorphProperties as a best-effort implementation of automatic property cloning. But in this case it’s kind of hard not to lose data, because the type data on the FlowSwitch links is dependent on the type of FlowSwitch<>.

Which is probably the real reason that FlowSwitch<> doesn’t (currently) show a TypeArgument property.