Jon Flanders' Blog

WorkflowDesigner hosting and Rules

Friday, June 02, 2006 7:13:15 AM (GMT Daylight Time, UTC+01:00)

So Matt posted a link to a new updated example from the Workflow team - you can get it here

This sample unfortunately has the same flaw that many of the samples using the Workflow Designer in a re-hosted (e.g. not VS.NET) situation have - is that the Rules editing dialogs are busted.

Here is what happens if you use the above sample.  It creates a new SequentialWorkflowActivity.  Go to the DynamicUpdateCondition property.

Select to have this condition be a "Declarative Rule Condition" (which will be a dynamically invoked RuleCondition - which is what is generally what you want in most Activities/Workflows).

Here is what happens in this designer when you click on the editor button in the property value field next to "Condition Name":

 

Hmm - I wonder what that means "Value cannot be null.  Parameter name: activity".  Is  the Activity is null?  Clearly not - no - the Activity isnt' null - but the Rule Condition infrastructure (which is invoked when you click on the editor button in the Condition Name property) certainly can't resolve what Activity this property is part of.  Why?

Well - here is why - its because of an interface known as ISite.  The PropertyGrid, which is the control that is being used to display the properties of the Activity and hence is the container for the classes that help put up the Choose Rule Condition Dialog - aren't setup correctly. So what has to happen to this sample to make this work?   The PropertyGrid.Site property must be set before the PropertyGrid is used.  ISite is an interface that is used to bind a Component to its Container.  In this case the PropertyGrid relies on ISite to get its services (unlike most other designer components which rely entirely on IServiceProvider).  What is happening is that the internal classes that dispaly the Rule Condition dialogs is trying to find a service and can't find it because the Site property of the PropertyGrid is null.  So to fix this error, you need to set the PropertyGrid's Site property.  Any implementation will do - in this case - since the Control that is displaying the worklfow is also the Parent of the PropertyGrid - we can use that Control to provide the implementation of ISite - which is fairly simple.

Here is my simple implementation of this interface for this control :

public partial class WorkflowDesignerControl : UserControl, IDisposable, IServiceProvider,ISite
{
#region ISite Members

IComponent ISite.Component
{
get { return this; }
}

IContainer ISite.Container
{
get { return this.Container; }
}

bool ISite.DesignMode
{
get { return true; }
}

string ISite.Name
{
get
{
return this.Name;
}
set
{
this.Name = value;
}
}

#endregion

#region IServiceProvider Members

object IServiceProvider.GetService(Type serviceType)
{
return this.GetService(serviceType);
}

 

Now - we need to actually set the PropertyGrid's Site property.  Here is that code from inside of the control's constructor:


InitializeComponent();
this.propertyGrid.Site = this;

 

After adding that code - the "Select Condition" dialog comes up fine.  Unfortunately if you try to add a new condition via the "New Condition" button - another exception comes up.

Ok - so "Object reference not set to an instance of an object".  What does that mean?

What is happening here - is that the RuleConditionDialog class is looking for a Service (if you start to play with Designer re-hosting you will quickly become intimate with an interface named IServiceProvider - and the way that different designer component are de-coupled and only communicate via services).

The service it is looking for is ITypeProvider.  ITypeProvider is an interface that is exposed from the workflow assemblies itself.  It is a type that other worklow types (the Rules infrastructure for one - WorkflowMarkupSerializer for another) use to get dynamic type information.  The cool thing about this system is that is become very easy for you as the designer hoster to dynamically give those components types that might not be able to be resovled via the normal .NET assembly and type resolution system.  This is cool, so we need to provide this service to the design environment so that we can get to the next dialog and actually edit a RuleCondtion!

Here again is a simple implemenation to get things moving.

internal class CustomTypeProvider : ITypeProvider
{
public CustomTypeProvider(IServiceProvider sp)
{
_contained = new TypeProvider(sp);
_provider = sp;
Assembly[] assemblyArray1 = AppDomain.CurrentDomain.GetAssemblies();
for (int num1 = 0; num1 < assemblyArray1.Length; num1++)
{
Assembly assembly1 = assemblyArray1[num1];
_contained.AddAssembly(assembly1);
}
}
IServiceProvider _provider;
TypeProvider _contained;
#region ITypeProvider Members

Type ITypeProvider.GetType(string name, bool throwOnError)
{

return _contained.GetType(name, throwOnError);

}

Type ITypeProvider.GetType(string name)
{

Type t = _contained.GetType(name);

return t;
}

Type[] ITypeProvider.GetTypes()
{
Type[] types = _contained.GetTypes();
return types;
}

System.Reflection.Assembly ITypeProvider.LocalAssembly
{
get
{
Assembly a = _contained.LocalAssembly;
return a;
}
}

ICollection<System.Reflection.Assembly> ITypeProvider.ReferencedAssemblies
{
get
{
ICollection<Assembly> asms = _contained.ReferencedAssemblies;
return asms;
}
}

IDictionary<object, Exception> ITypeProvider.TypeLoadErrors
{
get
{
IDictionary<object, Exception> errors = _contained.TypeLoadErrors;
return errors; ;
}
}

event EventHandler ITypeProvider.TypeLoadErrorsChanged
{
add { _contained.TypeLoadErrorsChanged += value; }
remove { _contained.TypeLoadErrorsChanged -= value; }
}

event EventHandler ITypeProvider.TypesChanged
{
add { _contained.TypesChanged += value; }
remove { _contained.TypesChanged += value; }
}

#endregion
}

What does this implementation do?  It just scans the AppDomain for any loaded assemblies and adds those assemblies to the TypeProvider type implemented by the Workflow framework itself, and just delegates calls to the contained TypeProvider.  Once added to the environment (this is typically done inside of the WorkflowDesignerLoader.Initialize method - since that is generally when types need to be found initially).

//inside WorkflowDesignerLoader.Initialize override
host.AddService(typeof(ITypeProvider), new CustomTypeProvider(LoaderHost), true);

Ok - so now - we can actually bring up the RuleConditionDialog which allows us to create a new RuleCondtion.

Whew - that's alot for tonight - especially this late - I am having to blog lately after my normal work hours on the WF contracting project I am working on.  :)  Part Two of this post (yes I realize I now have two Part Two posts to complete) - will go into how you can actually load and store rules if you are hosting the designer outside of VS.NET.

 

WF   #    Comments [0]