Monday, February 8, 2010

Workflow Custom Activity – Part 2


Introduction

Windows Workflow foundation gives many activities to use in workflow like code, delay, IfElse, Listen, Parallel etc. But in some scenario, those activities will not work as per our requirement. So we need to implement custom activity which will work as per our requirement.

To create a custom activity, Please visit http://dotnet-codeguru.blogspot.com/2010/02/windows-workflow-foundation-custom.html

In some scenario, you have to create some dynamic property in custom activity. For an example you are creating custom activity for Order Search. In this activity, User can search order by order id or customer id. So you want to give filter where user can select either “Order ID” or “Customer ID”. If user will select Order ID then new property Order Number will display in property window. And if user will select Customer ID then new property Customer Number will display in property window.

It is similar to IfElse Branch condition property. You get two options “Code Condition” and “Declarative Rule Condition”. As per your selection, you will get sub properties.



Let’s implement same thing in custom activity:

To support sub property for Order Search type we need to implement two classes “OrderSearchByOrderID” and “OrderSearchByCustomerID” which are derived from “ActvityCondition” class.

In the above classes, we need to override “Evaluate” method. In this simple scenario, we always return true from the above classes.

In OrderSearchByOrderID class, we need to add one property for OrderID which will display when user select “OrderSearchByOrderID” option from the selection box. The code for the OrderSearchByOrderID class is look like below:


public class OrderSearchByOrderID : ActivityCondition
{
public override bool Evaluate(Activity activity, IServiceProvider provider)
{
return true;
}

private string orderID;

[Browsable(true)]
public string OrderID
{
get
{
return orderID;
}
set
{
orderID = value;
}
}

}


Code for OrderSearchByCustomerID is similar and looks like below:


public class OrderSearchByCustomerID : ActivityCondition
{
public override bool Evaluate(Activity activity, IServiceProvider provider)
{
return true;
}

private string orderID;

[Browsable(true)]
public string OrderID
{
get
{
return orderID;
}
set
{
orderID = value;
}
}
}


We need to develop “TypeConvertor” which helps to display the option in dropdown box in property window.


public class CustomOrderSearchTypeConverter : TypeConverter
{

private Hashtable _conditionDecls = new Hashtable();

public CustomOrderSearchTypeConverter()
{
try
{
AddTypeToHashTable(typeof(OrderSearchByCustomerID));
AddTypeToHashTable(typeof(OrderSearchByOrderID));
}
catch (Exception ex)
{
throw;
}
}

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)
{
try
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
catch (Exception ex)
{

throw;
}
}

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
try
{
string strVal = value as string;
if (strVal != null)
{
if (strVal.Length == 0 || strVal == "")
return null;
else
return Activator.CreateInstance(this._conditionDecls[value] as Type);
}
return base.ConvertFrom(context, culture, value);
}
catch (Exception ex)
{

throw;
}
}

public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
try
{
if (destinationType == typeof(string))
{
return true;
}
else
{
return base.CanConvertTo(context, destinationType);
}
}
catch (Exception ex)
{

throw;
}
}

public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
try
{
if (value == null)
return "";
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;
}
catch (Exception ex)
{

throw;
}
}

public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
try
{
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)));
}
catch (Exception ex)
{

throw;
}
}

public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
try
{
// Always return true.
return true;
}
catch (Exception ex)
{

throw;
}
}

public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
try
{
// Always return true.
return true;
}
catch (Exception ex)
{

throw;
}
}

public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
try
{
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;
}
catch (Exception ex)
{

throw;
}
}

public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
try
{
// Always return true.
return true;
}
catch (Exception ex)
{

throw;
}
}

}


Now we need to use these classes in our custom activity. Just create a new class “OrderSearchActivity” and derive it from “Activity” class.

To give support, we need to implement one dependency property which is ActivityCondition type. Here we declared “OrderSearchTypeProperty” dependency property.

And also implement property for the same “OrderSearchType”.


public static DependencyProperty OrderSearchTypeProperty = DependencyProperty.Register("OrderSearchType", typeof(ActivityCondition), typeof(OrderSearchActivity));

[Description("Order Search Type")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[TypeConverter(typeof(CustomOrderSearchTypeConverter))]
public ActivityCondition OrderSearchType
{
get
{
return ((ActivityCondition)(base.GetValue(OrderSearchTypeProperty)));
}
set
{
base.SetValue(OrderSearchTypeProperty, value);
}
}


Now when you will use this OrderSearchActivity in your workflow then you will see option for OrderSearchType.




If you select “OrderSearchByCustomerID” then you will see “+” sign in front of “OrderSearchType” property which indicate that you have some sub properties. When you click on “+” sign you will get “Customer ID” as a sub property.





Now let’s see how we can use the above property in your custom activity. For this I have override Execute method in OrderSearchActivity.


protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{

if (OrderSearchType.GetType() == typeof(OrderSearchByCustomerID))
{
OrderSearchByCustomerID orderSearchByCustomerID = (OrderSearchByCustomerID)OrderSearchType;
string CustomerID = orderSearchByCustomerID.CustomerID;
// User CustomerID variable to search orders
}
else if (OrderSearchType.GetType() == typeof(OrderSearchByOrderID))
{
OrderSearchByOrderID orderSearchByOrderID = (OrderSearchByOrderID)OrderSearchType;
string OrderID = orderSearchByOrderID.OrderID;
// User OrderID variable to search orders
}

return base.Execute(executionContext);
}


In Execute method, we first check OrderSearchType property value type, if it is OrderSearchByCustomerID then we convert it in that and find customer id which is assigned by user. If type is OrderSearchByOrderID then we convert it in that and find order id which is assigned by user.


1 comment:

  1. Hi,

    Thank you for the post. I have a query. In the TypeConveter's GetStandardValues function, I am getting null for the parameter context's properties like PropertyDescriptor, Instance and Container. I wanted to return values based on the propertydescriptor. Since it is null, I couldnt do that. Please let me know if you have any idea on this issue.

    ReplyDelete

DotNet Code Guru