How To: Write a Custom Validator for Validation Application Block (VAB) using Application Block Software Factory
This post is a step by step guide on how to write a custom validator using Application Block Software Factory and integrate it in the configuration editor of Enterprise Library.
When you install Enterprise Library 3.1, you not only get library code and documentation, you also get the Application Block Software Factory. This factory allows you to create new application block that can sit above the Enterprise Library code services such as configuration and ObjectBuilder. This Software Factory can also help you to create typed and untyped providers for the library such as new TraceListener for the Logging Application Block, Call Handler for the Policy Injection Application Block, Custom Validator - Validation Application Block, etc.
Create a new Provider Library
1. In Visual Studio 2005, create a new project of type Guidance Packages -> Application Block Software Factory -> Provider Library.
2. This will start a wizard that will request some details from you:
Name: The name of the solution and the name pf the project that will contain the validator. I named it Bursteg.ProvidersLibrary, and we'll meet this name later in this post.
Description: A description that will be placed in the AssemblyInfo.cs of the providers library.
Author: Your Name
Namespace: Namespace of your choice.
3. Click Finish and Visual Studio will generate the solution structure for you. The solution contains the Bursteg.ProvidersLibrary project in which the validator will be created, and another project called Bursteg.ProvidersLibrary.Configuration.Design that will contain the visual elements needed in order to integrate this validator into the Enterprise Library Configuration Editor. If in step 1 you created a project with tests then the solution will also contain 2 unit tests projects.
Create the Custom Validator
Generate Initial Code for the Custom Validator
4. Right click the providers project (Bursteg.ProvidersLibrary) and from select Applciation Block Software Factory -> Validation Application Block -> New Validator (Typed).
5. This will start another wizard that will request the name of the validator. For this demo proposes I will create a Person ID Validator, that will validate an ID number according to a configured type. I named the validator: PersonIDValidator.
6. Click Finish and Visual Studio will add the necessary references and generate the initial source code for this validator. Notice that Bursteg.ProvidersLibrary has 3 new files for it.
PersonIDValidator.cs contains the code for the validator.
PersonIDValidatorAttribute.cs contains the code for the validator attribute in case you want to apply it by an attribute.
PersonIDValidatorData.cs in the Configuration folder contains the code for the configuration element of the validator - the class that holds its data that was configured in the validator.

Add support for properties
You should follow this step only if your validator receives any parameters. In this guide, the validator will receive a Country Code parameter and will validate the Person ID with the logic according to his country.
7. Open the PersonIDValidator.cs and add the country code property.
[ConfigurationElementType(typeof(PersonIDValidatorData))]
public class PersonIDValidator : ValueValidator
{
private int countryCode;
public int CountryCode
{
get { return countryCode; }
set { countryCode = value; }
}
...
}
8. Scroll down in this file and notice this commented code block:
// TODO: Decide whether a constructor with discrete arguments is necessary for the PersonIDValidator.
//public PersonIDValidator(object param1, object param2, string messageTemplate, bool negated)
// : base(messageTemplate, null, negated)
//{
// this.var1 = param1;
// this.var2 = param2;
//}
Uncomment it and create an appropriate constructor that receives the relevant parameters for your validator. In this guide I will receive the country code parameter. The constructor should look like that:
public PersonIDValidator(int coutry, string messageTemplate, bool negated)
: base(messageTemplate, null, negated)
{
this.countryCode = coutry;
}
This constructor will be useful when you can to initialize the validator by code, or by attribute.
If the country code is a mandatory parameter (It is in my case) then you should make sure it exist in all the overloads of the constructor. Finally, this is how the constructors should look like:
/// <summary>
/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>
/// </summary>
/// <param name="coutry">Country Code to validate by it</param>
public PersonIDValidator(int country)
: this(country, null, false)
{ }
/// <summary>
/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>
/// </summary>
/// <param name="configuration">The configuration instance for the validator.</param>
public PersonIDValidator(PersonIDValidatorData configuration)
: this(configuration.CountryCode, configuration.MessageTemplate, configuration.Negated)
{
}
/// <summary>
/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>
/// </summary>
/// <param name="coutry">Country Code to validate by it</param>
/// <param name="negated">True if the validator must negate the result of the validation.</param>
public PersonIDValidator(int country, bool negated)
: this(country, null, negated)
{ }
/// <summary>
/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>
/// </summary>
/// <param name="coutry">Country Code to validate by it</param>
/// <param name="messageTemplate">The message template to use when logging results.</param>
public PersonIDValidator(int country, string messageTemplate)
: this(country, messageTemplate, false)
{ }
// <summary>
/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>
/// </summary>
/// <param name="coutry">Country Code to validate by it</param>
/// <param name="messageTemplate">The message template to use when logging results.</param>
/// <param name="negated">True if the validator must negate the result of the validation</param>
public PersonIDValidator(int coutry, string messageTemplate, bool negated)
: base(messageTemplate, null, negated)
{
this.countryCode = coutry;
}
9. Open the PersonIDValidatorData.cs (under the Configuration folder) and locate the following commented line:
// TODO: Add the configuration properties for PersonIDValidatorData. The snippet for creating configuration properties would be useful.
Add the coutry code property to this class using the code snippet for creating configuration properties provided with the Application Block Software Factory. (To insert a snippet, click Ctrl + K + X and select the snippet you want. If you want to use this one directly, you can type its shortcut configproperty and click Tab twice).
Edit the snippet fields with the property's name, configuration name and type.
private const string CountryCodePropertyName = "CountryCode";
[ConfigurationProperty(CountryCodePropertyName)]
public int CountryCode
{
get { return (int)this[CountryCodePropertyName]; }
set { this[CountryCodePropertyName] = value; }
}
10. Go back to the PersonIDValidator.cs and modify the constructor that receives the PersonIDValidatorData as parameter. This constructor is being called when the validator was configured in the configuration file. Simply assign the values from the configuration properties to the validator properties.
/// <summary>
/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>
/// </summary>
/// <param name="configuration">The configuration instance for the validator.</param>
public PersonIDValidator(PersonIDValidatorData configuration)
: this(configuration.CountryCode, configuration.MessageTemplate, configuration.Negated)
{
}
Notice the first parameter that this constructor overload receives.
10. Open the PersonIDValidatorAttribute.cs and add a data member for the country code.
Locate the following commented code:
// TODO: Decide whether a constructor with discrete arguments is necessary for the PersonIDValidatorAttribute.
//public PersonIDValidatorAttribute(object param1, object param2)
//{
// this.var1 = param1;
// this.var2 = param2;
//}
Uncomment this code and create the constructor with the country code parameter:
public PersonIDValidatorAttribute(int country)
{
this.coutryCode = country;
}
If the country code is a mandatory parameter (It is in my case) then you should remove the default constructor.
11. In the same file edit the DoCreateValidator method. This method creates the instance of the validator based on the parameters of the attribute. Make sure to use the constructor overload that receives the country code.
protected override Validator DoCreateValidator(Type targetType)
{
return new PersonIDValidator(this.coutryCode, MessageTemplate, Negated);
}
Implement the Validation Logic
12. Go back the the PersonIDValidator.cs and locate the DoValidate method at the bottom of the class. This method will contain the validation logic, but first you must know what each of the parameters mean. From the parameters description above the method:
objectToValidate - The object to validate.
currentTarget - The object on the behalf of which the validation is performed.
key - The key that identifies the source of objectToValidate
validationResults - The validation results to which the outcome of the validation should be stored.
For example:
If this validator receives a string to validate (person id is string in this example):
PersonIDValidator validator = new PersonIDValidator(3);
ValidationResults results = validator.Validate("123");
Than the objectToValidate will be a string containing "123", as same as the currentTarget. The key will null.
If the validator receives an object and validates only a property of that object:
Person p = new Person();
p.PersonID = "123";
ValidationResults results = Validation.Validate<Person>(p);
Than the objectToValidate will be "123", the currentTarget will be the person instance and the key will be "PersonID".
Now, when you understand what each parameter does, we can go on and implement the validation logic. The validation logic in this case is very simple, since this is not the main purpose of this guide. The validator checks the input string and make sure it ends with the country code.
protected override void DoValidate(object objectToValidate, object currentTarget, string key, ValidationResults validationResults)
{
// Get the object to validate
string id = (string)objectToValidate;
// Check if it is valid
string end = countryCode.ToString().Trim();
bool isValid = id.EndsWith(end);
// If the negated property is false, and the id is not valid, log a validation error
if (isValid == Negated)
{
LogValidationResult(validationResults, this.MessageTemplate, currentTarget, key);
}
}
Change the Message Templates of the Custom Validator
13. When you log a validation error you also add a message that contains the error description. By default the validator uses 2 message templates - one for the default validation error and one for the negated error. To change the validation error messages, expand the provider library project (Bursteg.ProvidersLibrary) and double click the Resources.resx file under the properties folder.
This will open the Resource Editor and let you edit the messages. Notice that there 2 message templates - PersonIDValidatorNegatedDefaultMessage (for the negated template) and PersonIDValidatorNonNegatedDefaultMessage (for the non-negated template).
Add Design-Time support for the Custom Validator
When you develop Custom Validator for your project, you'll probably want to add design-time support that will enable developers configure the new validator from the Enterprise Library Configuration Tool just as simple as they would do for every other out-of-the-box validator.
Creating Design-Time providers node is a recipe that is similar to all Enterprise Library providers and not something specific for each provider type.
14. Add references to the Validation Application Block assemblies (Microsoft.Practices.EnterpriseLibrary.Validation and Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design).
Right click the design-time support project (Bursteg.ProvidersLibrary.Configuration.Design) and choose Application Block Software Factory -> Create Design-Time Provider Node.
15. This will start a new wizard that will request some detail about the provider:
Node Name is the name of the class that will be generated and will represent the node in the configuration tree for this validator. In this guide it is PersonIDValidatorNode.
Runtime Configuration Type is the type that holds the configuration data for this validator, and it was already created for us earlier. In this guide it is PersonIDValidatorData in the Bursteg.ProvidersLibrary assembly.
Base Design Mode is the class to inherit from when generating the configuration type. If you noticed when we edited the validator class it inherited from a class called ValueValidator, which has a Design-Time configuration type called ValueValidatorNode in the Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design assembly. You should pick this type as the Base Design Mode.
Parent UI Node is the parent node in the configuration tree. The validator will always have parent of the following nodes:
- SelfNode - When we want to validate an object (the whole object).
- PropertyNode - When we want to validate a property of a type.
- MethodNode - When we want to validate method parameters of a type
- FieldNode - When we want to validate a field of a type.
You would probably want to support all of the above options, but the wizard supports only a single selection. You can select SelfNode for now, and we will add the others by code.
Cardinality indicates whether this validator can be applied more than once. For example - if you want to have 2 PersonIDValidators validating a single property. For this guide it is Single.
16. Click Finish and Visual Studio will generate the code for the design-time configuration support. Notice that Bursteg.ProvidersLibrary.Configuration.Design has 4 new files for it:
PersonIDValidatorNode.cs contains the class that represents the node in the configuration.
CommandRegistrar.cs, ConfigurationDesignManager.cs and NodeMapRegistrar.cs contains the code that registers the new configuration node to the configuration tree in the location we have selected earlier in the wizard.
17. In step 14, we have selected to place the validator node under the SelfNode. If you want to add support for other node types, expand the CommandRegistrar.cs file node, and open the CommandRegistrar.PersonIDValidatorNode.cs file. This is a part of the file that registers the validator to the configuration.
Edit the AddPersonIDValidatorNodeCommand method: duplicate the code that invokes AddSingleChildNodeCommand method as many as you need to add the validator parent nodes. Usually you will want to allow all 4 parent nodes, so you should copy it 4 times. The only difference between all the 4 invocations is the last parameter which is the type of the parent node. In step 14 we selected the SelfNode, so the other 3 invocations should use PropertyNode, MethodNode and FieldNode.
sealed partial class CommandRegistrar
{
private void AddPersonIDValidatorNodeCommand()
{
AddSingleChildNodeCommand(
Resources.PersonIDValidatorNodeUICommandText,
Resources.PersonIDValidatorNodeUICommandLongText,
typeof(PersonIDValidatorNode),
typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.SelfNode));
AddSingleChildNodeCommand(
Resources.PersonIDValidatorNodeUICommandText,
Resources.PersonIDValidatorNodeUICommandLongText,
typeof(PersonIDValidatorNode),
typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.PropertyNode));
AddSingleChildNodeCommand(
Resources.PersonIDValidatorNodeUICommandText,
Resources.PersonIDValidatorNodeUICommandLongText,
typeof(PersonIDValidatorNode),
typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.MethodNode));
AddSingleChildNodeCommand(
Resources.PersonIDValidatorNodeUICommandText,
Resources.PersonIDValidatorNodeUICommandLongText,
typeof(PersonIDValidatorNode),
typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.FieldNode));
}
}
18. Change the captions of the Custom Validator as they will show in the configuration tool. Edit the resources file of the design-time support project (Bursteg.ProvidersLibrary.Configuration.Design).
Edit the following resources:
PersonIDValidatorNodeName - PersonID Validator
PersonIDValidatorNodeUICommandLongText - PersonID Validator Long Test
PersonIDValidatorNodeUICommandText - PersonID Validator Command Text
You'll soon see where each of these captions are displayed.
19. I've already said that adding a design-time support for all provider types is done using the same recipe, but with validators you have to change some code in order to complete this task.
Open PersonIDValidatorNode.cs and override the CreateValidatorData() method. As an implementation, paste the code from the ValueValidatorData property.
public override ValidatorData CreateValidatorData()
{
PersonIDValidatorData data = new PersonIDValidatorData(this.Name);
data.CountryCode = this.countryCode;
data.Negated = this.negated;
data.MessageTemplate = this.messageTemplate;
data.MessageTemplateResourceName = this.messageTemplateResourceName;
data.MessageTemplateResourceTypeName = this.messageTemplateResourceTypeName;
data.Tag = this.tag;
return data;
}
After you pasted the code, remove the ValueValidatorData property. You don't need it anymore.
20. Change the constructor that receives PersonIDValidatorData as a parameter. Have it call the base class constructor with the parameter value.
/// <summary>
/// Initialize a new instance of the <see cref="PersonIDValidatorNode"/> class with a <see cref="Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData"/> instance.
/// </summary>
/// <param name="data">A <see cref="Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData"/> instance</param>
public PersonIDValidatorNode(Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData data)
:base(data)
{
...
}
21. In order that the configuration tool can see your provider library and register the validator, you should copy the output assembly of the providers library and design type support (Bursteg.ProvidersLibrary.dll and Bursteg.ProvidersLibrary.Condiguration.Design.dll) to the Enterprise Library bin directory. By default it is C:\Program Files\Microsoft Enterprise Library 3.1 - May 2007\Bin.
Test the Custom Validator
Test the validator when used in code
22. Open a new instance of Visual Studio (otherwise the configuration changes will not be applied) and create a new Console Application in order to test the validator. After the project is created, add a new class of type Person:
public class Person
{
private string personID;
public string PersonID
{
get { return personID; }
set { personID = value; }
}
}
23. Add references to the relevant assemblies:
(Microsoft.Practices.EnterpriseLibrary.Common and Microsoft.Practices.EnterpriseLibrary.Validation) for the validation support, providers library(Bursteg.ProvidersLibrary) and System.Configuration.dll.
24. In the Main method, create a new instance of person and set its ID. Use the validator to check if the ID is valid.
// Initialize a new instance of Person
Person p = new Person();
p.PersonID = "123";
// Validate a valid person ID
PersonIDValidator validator = new PersonIDValidator(3);
ValidationResults results = validator.Validate(p.PersonID);
Debug.Assert(results.IsValid);
DisplayValidationResults(results);
// Validate an invalid person ID
validator = new PersonIDValidator(2);
results = validator.Validate(p.PersonID);
Debug.Assert(!results.IsValid);
DisplayValidationResults(results);
// Validate a valid Person ID, but with negated = true
validator = new PersonIDValidator(3, true);
results = validator.Validate(p.PersonID);
Debug.Assert(!results.IsValid);
DisplayValidationResults(results);
// Validate an invalid Person ID, but with negated = true
validator = new PersonIDValidator(2, true);
results = validator.Validate(p.PersonID);
Debug.Assert(results.IsValid);
DisplayValidationResults(results);
Where DisplayValidationResults method can be something like:
private static void DisplayValidationResults(ValidationResults results)
{
if (!results.IsValid)
{
foreach (ValidationResult result in results)
{
Console.WriteLine(result.Message);
}
}
}
Test the validator when configured using an attribute
25. Add the validator attribute to the ID Property of the Person class.
public class Person
{
private string personID;
[PersonIDValidator(3)]
public string PersonID
{
get { return personID; }
set { personID = value; }
}
}
26. In the Main method, validate the person instance to check if the ID is valid.
// Validate the person
results = Validation.ValidateFromAttributes<Person>(p);
DisplayValidationResults(results);
Test the validator when configured in the configuration file
27. Add a new app.config to your Console Application, and edit with the Enterprise Library Configuration Tool.
28. Add the Validation Application Block Configuration Section. Right click the configuration file name node and choose New -> Validation Application Block.
29. This will add the Validation Application Block configuration section to your configuration file. Add a new type to validate. Right click the Validation Application Block node and choose New -> Type.
30. This will open the Type Selector Dialog. Click Load an Assembly, and navigate to the output folder of the test project in order to find the assembly that contains the class Person. Finally, select the Person type.
31. When configuring validations for a type using the configuration tool you cannot add validations straight on the type, you must group a few validations into a Ruleset. Add a new Ruleset by clicking the Person type and selecting New -> Ruleset. After the new Ruleset is added, you should change its name to something meaningful.
32. The Custom Validator we created in this guide validates objects of type string that are ID's of people. In the Person type, we would like to validate the PersonID property with this validator. To validate this property we first have to add it to the Person node. Right Click the Person node and choose New -> Property. Name the property exactly as it is named in the class definition, case sensitive.
33. Add the PersonIDValidator to the PersonID node. Right click the PersonID node and select New-> PersonID Validator Command Text. (Now you see where the command text you edited in step 18 goes to.
34. After the validator is added, a new node will be shown called PersonID Validator (same as the PersonIDValidatorNodeName resource from step 18). Go to the properties pane and set its country code.
In the Main method, validate the person instance to check if the ID is valid, and don't forget to use the name of the ruleset.
// Validate the person
results = Validation.ValidateFromConfiguration<Person>(p, "DefaultValidation");
DisplayValidationResults(results);
Conclusion
Creating a Custom Validator is very easy to do, especially with the Application Block Software Factory. You create a provider library and generate the validator and you add design-type support using predefined recipes. Developers can use the new validator as simple as with any other out-of-the-box validator.
You can download a sample project: Custom Validator with Application Block Software Factory.
If you have any questions or feedback, I'd like to hear.
Enjoy!
Workflow External Data Exchange Service - ExternalDataExchange
In many scenarios, a workflow will need to communicate with the application in order to get some data, or notifications about state that has changed. In order to achieve that, the workflow needs a mechanism to exchange data with the application. Speaking in Windows Workflow Foundation terms, it is called External Data Exchange.
This post explains how to communicate between a workflow and the hosting environment using the External Data Exchange Service. After reading this series of posts, you will be able to understand each of the elements in the following Diagram:
Long running workflows contain activities that their execution depends on "user" actions after the workflow has already started. In those scenarios, the workflow is waiting for the application to raise an event before it continue to the next activity.
This is how you should implement such long running scenario using the External Data Exchange Service.
External Data Exchange Interface
The first thing you should do is to create an interface that will define the communication contract between the workflow and the application. This interface can contain methods and events and there are several rules for each of these elements. (These rules will be covered later in this post).
This is an example of a External Data Exchange Interface. This interface is decorated with the [ExternalDataExchangeAttribute], and in this case contains a single event.
[ExternalDataExchange]
public interface IExternalData
{
event EventHandler<ActionEventArgs> Done;
}
External Data Exchange Events and Arguments
Notice that the event declared in the above interface has a specific declarations type. The event uses the delegate EventHandler<T> that receives (object sender, T e) as arguments, where T is derived from EventsArgs class.
The event arguments class is used to communicate between the application and a workflow must derive from System.Workflow.Activities.ExternalDataEventArgs class. This class holds the woflow instance ID that the raised event is related to.
The ActionEventArgs that is used in the above interface is declared like this:
[Serializable]
public class ActionEventArgs : ExternalDataEventArgs
{
public ActionEventArgs(Guid instanceId, ...)
:base(instanceId)
{
...
}
...
}
There are two about that event arguments class you should notice:
- The constructor receives a Guid instance ID and calls the base class constructor with thie ID as parameter.
- This class is Serializable.
External Data Exchange Service
After creating the External Data Exchange Interface, you should create a class that implements it and attach it to the Workflow Runtime using the ExternalDataExchangeService.
The class you should create that implements the Extrenal Data Exchange Interface is the communication service. Use this code to add the External Data Exchange Service to the workflow runtime, and add your communication service as a service for exchanging data with workflows:
// Add the ExternalDataExchangeService to the WorkflowRuntime
ExternalDataExchangeService dataExchange = new ExternalDataExchangeService();
// Add the Communication Service as a service for exchaging data
workflowRuntime.AddService(dataExchange);
dataExchange.AddService(new ActionCommunicationService());
Handling External Event using HandleExternalEventActivity
Using the HandleExternalEventActivity blocks the workflow until the registered event is raised by the communication service.
In order to use the HandleExternalEventActivity and relate to a specific event of the communication service, simply follow the following steps:
- Select the HandleExternalEvent activity from the toolbox and drag and drop it onto the workflow design surface. Set the name property of the activity.
- Use the […] button of the InterfaceType property, and use the dialog to choose an External Data Exchange Interface that is declared in your project, or begin referenced by it.
- Choose an event to handle from that interface, by choosing an item from the drop-down list of the EventName property of the activity.
- The event sends a EventArgs instance as described in the previous post. In order to save It for later usage, declare a member:
// Workflow1.cs
public ActionEventArgs actionArguments = null;
- Set the e property of the activity. Use this property to inform the workflow in which member the received instance of EventArgs will be stored in.
To test this activity, I used the following code snippet. It registers the ExternalDataExchangeService to the runtime, and adds my communication service that implements the Extrenal Data Exchange Interface. Then, It starts a workflow instance. After waiting for somw time, It raises the Done event. After raising the event, the workflow will continue its execution.
// Add the ExternalDataExchangeService to the WorkflowRuntime
ExternalDataExchangeService dataExchange = new ExternalDataExchangeService();
workflowRuntime.AddService(dataExchange);
// Add the Communication Service as a service for exchaging data
ActionCommunicationService commSercice = new ActionCommunicationService();
dataExchange.AddService(commSercice);
WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Workflow1));
instance.Start();
Thread.Sleep(20000);
commSercice.DoYourWork(instance.InstanceId);
Some Important Tips
The above code snippet called the method DoYourWork of the communication service, that in some point raised the Done Event. Lets examine that code:
public void DoYourWork(Guid instanceID)
{
...
if (this.Done != null)
{
this.Done(null, new ActionEventArgs(instanceID, ""));
}
}
Keep in mind that there is a many-to-one relationship between the the communication service and the workflow instance. By that, I mean that a single communication service can communicate with many workflow instances. Therefore, you should always pass the workflow instance ID as parameter to methods, and also raise the event with the right instance ID. (Tip #1)
Notice that when calling the event handlers, you pass an object sender. (Tip #2) This objects must be serializable, or else, you will receive a System.Workflow.Activities.EventDeliveryFailedException with message like: "Event <YourEventName> on interface type <YourInterfaceName> for instance id <YourInsatnceID> cannot be delivered." With an inner exception message: "EventArgs not serializable". Although the problem is with the sender and not with the event arguments, this is the inner exception message. A bit confusing.
Using CallExternalMethodActivity
After creating a communication service and registering it to External Data Exchange Service, we could use the HandleExternalEvent activity in order to handle events of that service. In the opposite direction, we are able to call methods of the service, pass parameters and get return values.
If you are familiar with the HandleExternalEvent activity, using this activity is rather simple.
In order to use the HandleExternalEventActivity and relate to a specific event of the communication service, simply follow the following steps:
- Select the CallExternalMethod activity from the toolbox and drag and drop it onto the workflow design surface. Set the name property of the activity.
- Use the […] button of the InterfaceType property, and use the dialog to choose an External Data Exchange Interface that is declared in your project, or begin referenced by it.
- Choose the method to call from that interface, by choosing an item from the drop-down list of the MethodName property of the activity.
- After choosing the method name, the parameters of that method will appear in the property grid. You can specify constant values as parameters, or you can bind the parameters to a specific workflow field using ParameterBindings (explained later). If the method has a return value, set the field to store the return value in.
Parameter Binding
When passing parameters to methods, you can choose to set a constant value (MaxItems = 2), or you can bind the parameter value to some variable. For example, if the value is not known in design time and only in runtime. One good example for the usage in workflow scenarios is to handle a communication service event that sends some event arguments, and call a method with the ID of the entity that its event was handled.
To use Parameter Binding:
- Drag a CallExternalMethod activity or HandleExternalEvent activity to the design surface and choose the InterfaceType and EventName / MethodName.
- Click the […] button of a parameter / return value / event arguments.
- The Bind Property Dialog will open. You can use the first tab – "Bind to an existing member" to choose a public field or property to bind to the parameter, or you can use the other tab – "Bind to a new member" to create the member to create to store the value.
CallExternalMethodActivity.MethodInvoking and HandleExternalEventActivity.Invoked
What if you want to change the state of your workflow instance after handling a event? What if you want to prepare the parameter object before calling the external method?
In these cases, there couple of activity events that are very useful:
CallExternalMethod activity has a event called MethodInvoking that occurs just before the external method is called by the workflow. You can easily handle this event by double-clicking the activity or by typing a handler method name in the property grid of the activity and pressing Enter. This will create the following handler method:
private void CallExternalMethod1_MethodInvoking(object sender, EventArgs e)
{
}
HandleExternalEventActivity activity has a event called Invoked that occurs right after the external event is handled by the workflow. You can easily handle this event by double-clicking the activity or by typing a handler method name in the property grid of the activity and pressing Enter. This will create the following handler method:
private void HandleExternalEvent1_Invoked(object sender, ExternalDataEventArgs e)
{
}
Notice that the event arguments of this event is ExternalDataEventArgs , which is the base class of the Event arguments we used to create the events in the External Data Exchange Interface.
Summary
In this post I talked about the windows workflow foundation way of communicating between the workflows and the hosting environment - External Data Exchange.
I have shown how to declare an External Data Exchange Interface with External Data Exchange Events and how to register the External Data Exchange Service to the Workflow Runtime.
Then, I talked about how to use the HandleExternalEvent Activity and the CallExternalMethod activity, and how to bind parameters to members in your workflow class. I also explained how to use the CallExternalMethodActivity.MethodInvoking and HandleExternalEventActivity.Invoked events to extend the operation done by these activities.
You may want to read additional posts about Windows Workflow Foundation.
Enjoy!
Create Sharepoint Site By Template in Code - Workflow Custom Activity
This post documents the steps I took in order to build a custom activity that creates a Sharepoint site by template. This post also talks about the steps needed in order to add this custom activity into the Sharepoint designer.
In Visual Studio 2005, I created a new Activity Library Project for my new activity. I created a new activity that derives from Activity (and not from the default SequentialActivity).
namespace Bursteg.CustomActivities
{
public partial class CreateSiteFromTemplate : Activity
{
public CreateSiteFromTemplate()
{
InitializeComponent();
}
}
This custom activity creates the new site based on an input template name, and according to the value of a selected column in the item who triggered the workflow. To name this action I'd say: Creates a new site by template X, and calls it like the value of field Y.
In order for the activity to receive both values, 2 dependency properties should be created: SiteNameField and TemplateName, both of type string. (You can use the wdp code snippet in order to create them.)
public static DependencyProperty SiteNameFieldProperty = DependencyProperty.Register("SiteNameField", typeof(string), typeof(CreateSiteFromTemplate));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public string SiteNameField
{
get { return ((string)(base.GetValue(CreateSiteFromTemplate.SiteNameFieldProperty))); }
set { base.SetValue(CreateSiteFromTemplate.SiteNameFieldProperty, value); }
}
public static DependencyProperty TemplateNameProperty = DependencyProperty.Register("TemplateName", typeof(string), typeof(CreateSiteFromTemplate));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public string TemplateName
{
get { return ((string)(base.GetValue(CreateSiteFromTemplate.TemplateNameProperty))); }
set { base.SetValue(CreateSiteFromTemplate.TemplateNameProperty, value); }
}
Since the activity has to perform some actions on the list item that has triggered the workflow, get its values and etc., it must receive some additional properties that gives the context to the activity: __Context, __ListId and __ListItem:
public static DependencyProperty __ContextProperty = DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(CreateSiteFromTemplate));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public WorkflowContext __Context
{
get { return ((WorkflowContext)(base.GetValue(CreateSiteFromTemplate.__ContextProperty))); }
set { base.SetValue(CreateSiteFromTemplate.__ContextProperty, value); }
}
public static DependencyProperty __ListItemProperty = DependencyProperty.Register("__ListItem", typeof(int), typeof(CreateSiteFromTemplate));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public int __ListItem
{
get { return ((int)(base.GetValue(CreateSiteFromTemplate.__ListItemProperty))); }
set { base.SetValue(CreateSiteFromTemplate.__ListItemProperty, value); }
}
public static DependencyProperty __ListIdProperty = DependencyProperty.Register("__ListId", typeof(string), typeof(CreateSiteFromTemplate));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public string __ListId
{
get { return ((string)(base.GetValue(CreateSiteFromTemplate.__ListIdProperty))); }
set { base.SetValue(CreateSiteFromTemplate.__ListIdProperty, value); }
}
Notice that the __Context Property is of type WorkflowContext. This property is the most important for the implementation of this activity.
As for the implementation of the activity, we have to override the Execute method. In the following code I am using the WorkflowContext to get a reference to the list of custom templates in my site and creating the new site based on the input template. As for the name of the site, I am taking the value of the input field name for the current item. Notice the usage of all the input properties.
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
// Get the list of custom templates in this site
SPWebTemplateCollection templates = __Context.Site.GetCustomWebTemplates(1037);
// Find the input template.
SPWebTemplate template = templates[this.TemplateName];
// Get the list in which this workflow was started
SPList list = __Context.Web.Lists[new Guid(__ListId)];
// Get the item that has triggered the workflow
SPListItem item = list.Items[__ListItem - 1];
// Get the value of the selected field
string siteName = (string)item[this.SiteNameField];
// Create the new site
SPWeb newWeb = __Context.Web.Webs.Add(siteName, siteName, siteName, 1037, template, false, false);
// This activity has finished its job.
return ActivityExecutionStatus.Closed;
}
You can download a sample project with the custom activity I created.
2. Register the activity with Sharepoint
In order to use this custom activity in Sharepoint, the assembly must be signed and registered to the GAC. You should use a new or existing strong name key file and assign it the the custom activity project. (Simply go to the project properties, select the Signing pane and check the "Sign the assembly" checkbox.)
You can register the assembly to the GAC by dragging it from the bin\debug\ directory to the c:\windows\assembly directory or you can use the command line tool:
"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -i c:\....\bin\debug\MyAssembly.dll /f.
A better approach (which I took) is to use the command line tool in the post-build event of the project. Simply go to the project properties, select the Build Events pane, and use the following command:
"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -i $(TargetPath) /f.
In this way, after every build, the new assembly will be registered to the GAC.
Now that Sharepoint can use the assembly, you have to add its types as authorized types for Sharepiont. You can do that by editing the web.config of your site (For example: C:\Inetpub\wwwroot\wss\VirtualDirectories\8080\web.config). At the bottom of the file, locate the <System.Workflow.ComponentModel.WorkflowCompiler> tag and under the <authorizedTypes> add a new authorized type with the assembly details:
<System.Workflow.ComponentModel.WorkflowCompiler>
<authorizedTypes>
<authorizedType Assembly="Bursteg.CustomActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0a01dfd64655f7f6" Namespace="Bursteg.CustomActivities" TypeName="*" Authorized="True" />
</authorizedTypes>
</System.Workflow.ComponentModel.WorkflowCompiler>
I used .Net Reflector in order to get the full assembly name including the version, culture and public key token.
3. Register the custom activity to Sharepoint Designer
Sharepoint Designer has a simple and easy-to-use user interface for creating workflows. It allows selecting actions from a list of available actions and supplying parameters. You can add the new activity to that list and make it just like any other out-of-the-box activities.
To do this, open the wss.actions file located in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\Workflow. Under the <WorkflowInfo> tag, locate the <Actions> tag and add a new action like the following:
<Action Name="Create Site by Template"
ClassName="Bursteg.CustomActivities.CreateSiteFromTemplate"
Assembly="Bursteg.CustomActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0a01dfd64655f7f6"
AppliesTo="list"
Category="Custom"
UsesCurrentItem="true">
<RuleDesigner Sentence="Create a new site called %1 by template %2">
<FieldBind Field="SiteNameField" Text="Site Name Column" Id="1" DesignerType="writablefieldNames"/>
<FieldBind Field="TemplateName" Text="Template Name" Id="2" DesignerType="TextArea"/>
</RuleDesigner>
<Parameters>
<Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" />
<Parameter Name="__ListId" Type="System.String, mscorlib" Direction="In" />
<Parameter Name="__ListItem" Type="System.Int32, mscorlib" Direction="In" />
<Parameter Name="SiteNameField" Type="System.String, mscorlib" Direction="In" />
<Parameter Name="TemplateName" Type="System.String, mscorlib" Direction="In" />
</Parameters>
</Action>
There are few things you should notice about this action declaration. The action name will appear in the list of actions in the Sharepoint Designer. The AppliesTo = "list" and UsesCurrentItem="true" attributes will cause the workflow to pass the values of the list id and list item properties to the activity. The Sentence will appear in the design surface when you add the action, and the %1 and %2 will be replaced with the designers that match the FieldBinding tags by the Id attribute.
4. Testing the Custom Activity
In order to test this activity create a site and save it as a template. Create a new list with a column that will hold the name of the site to create. For example, you can have a list of courses in which the course code will be the site name.
In Sharepoint Designer create a new workflow, select the relevant list and select a start option (Start when item is created). In Step 2 of the designer, create a new step and add the Create Site by Template action from the Custom category. Choose the column from which to take the name of the site to create, and supply the name of the site template (should be something that ends with .stp).
Click Finish to apply the workflow to the site.
Now, go to the list and create a new item. The workflow should start and create the new site. You can find it the list of sites under your site collection.
I hope this tutorial helps, and I'd love to hear any feedback, comments and questions about it.
You can download a sample project with the custom activity I created.
Enjoy!