Note: This article has been updated to WF4.5 version.
Contents
- Declaratively message mediation
- Based on the Contract Model
- Create, transform, and inspect untyped messages
- User control for re-hosted Workflow Designer
- Interactively Design & Run in the self hosted process
- XSLT technology
- .NET 4 Technologies
In my previous article Using WF4 WorkflowInvoker, I have mentioned a strategy of the "XAML stack" in the upcoming .NET 4 Technology and why it was necessary to change
the WF model from WF3/3.5. One of the most significant changes in the WF paradigm is an encapsulation of the workflow model from the communication model. These WCF and WF models are integrated via the XAML stack represented by
the Endpoint
element. In other words, the Endpoint
represents a logical connectivity layer between the service and its consumer. This logical connectivity can be described by
the Contract Model; view more details in my article Contract Model for Manageable Services.
The workflow service behind the Endpoint
is driven by a typed or untyped message. The workflow service doesn't need to know how the message will transported, etc. Its responsibility is processing a message based on the business workflow. The message flows through the service via activities where the message can be consumed, modified, or creates
another message. The process of the message massaging is called message mediation and the activities are mediation primitives. The message can be mediated based on the local resources or remotely via another service. The mediation primitives can change the format, content, or target of the message. The mediation flow can be simple with one mediation primitive or multiple primitives in sequential order.
For examples, the Message Logger represents a mediation primitive to capture a business process state in storage during a mediation flow. In this primitive, the flowed message is mediated to the correct format, adding more information and stored in the storage as a message log. Another good example
is a Message Router where the message is mediated based on the content (connectivity or business) to selectively route a message.
Basically, we can have three models of message mediation primitives, see the following pictures:
As the above picture shows, the first primitive will mediate an input message into the message Output and in the case of exception will create a new message Error. The second primitive will consume a message input and update a local mediation state, there is no output message except a message for exception.
The last primitive will create a new message Output based on the local mediation state and also a message Error.
The following picture shows a very simple example of the WF4 mediator primitive
Assign
:
As you can see, the output message needs a string element Name
instead of the input's string elements such as FirstN
and LastN
(FirstName/LastName). Therefore, the message must be mediated by primitive. This is a very simple mediation and can be accomplished by Assign
activity in the Expression toolbox. Thanks to the new WF model, where the expression text usage can help to simplify a message mediation and implementation of the primitives.
Notice, that the above example requires a typed message with CLR
types in the assembly, therefore we need a new version of recompiled assembly for any type changes. For example, if the new version of the above example will require the usage of a middle name, the schema of the input message needs to be updated for new type. On the other hand, the Assign primitive can modify an expression text straightforward, declaratively in the XAML stack.
OK, that's one way how to handle a message mediation. The other way is using a declaratively message mediation. This way is based on the untyped message and XSLT
Technology.
The following picture shows custom message mediation for creating a message output based on the XSLT technology:
In the above example, the custom CreateMessage
mediation primitive will create output message based on the XSLT template. During the design time, the designer will create a parameterized XSLT template (see the above Mediator text box) resource manually or automatically based on the contract model (WSDL, schema, etc.) and stored into the XAML stack. During the runtime, the XSLT compiler will transform this XSLT template into the message output with a real time value of the parameters. Any change in the contract model will only require modifying this XSLT template such as an embedded resource in the XAML stack.
The XLT template can be designed for any WCF supported message version including None. With this message primitive, we can send a message to any endpoint described by contract model, declaratively. Note, that the contract model can be discovered from Repository or from physical endpoint.
This article is focusing on the declaratively message mediation. I will show you how implementation and usage of the custom mediation primitives using the WF4 Technology. This library can be imported into the WF4 Toolbox. As a bonus, I am including a re-hosted Workflow Designer on the Windows Form for our test purposes.
I am assuming you have some working experience with current WCF and WF Technologies. OK, let's get started.
The concept and design of the declaratively message mediation is based on the massaging untyped message. Microsoft upcoming WF4 technology doesn't have built-in primitives for untyped message mediation. Untyped message can only be sent, received and queried. There is no tooling support to work with contract model directly. It requires adding some service references into the project, which will create all
CLR types in the project assembly.
Notice, that massaging untyped message requires knowing its metadata which can be obtained from the Repository, File System, or existing physical endpoint. The following picture shows a concept of the message mediation primitive:
As you can see in the above picture, the mediation primitive is divided into two parts such as Design-Time and Runtime. The Design-Time has responsibility to create XSLT template for specific message version based on the contract metadata. The contract metadata can be obtained from the URL resource and its successful receiving will be updated contract and operation combo boxes. By selecting a specific contract and operation, we can generate parameterized
XSLT message template and store it in the XML notepad panel for its customizing. Finally, the design process will embedded this XSLT resource into the XAML stack including a design information (for instance:
URL, version, etc.).
The second part of the primitive, such as Runtime is very straightforward; the XSLT processor will load this XSLT resource, collection of the runtime values and invoking a XSLT transformation to generate the output message.
As I mentioned earlier, the contract metadata can be loaded from the Repository, File System (for instance: project) or from existing physical endpoint. This is a great feature, allowing us to relay in the loosely coupled manner on the contract first model or from the metadata stored in the Repository. In the case of contract model changes, we need to go back to the design-time process and modified our XSLT message template. Note, that the XML template is accessible in the custom activity (primitive) via InArgument<XElement>
property.
Message mediation primitives
In this paragraph, I will describe a set of custom activities (primitives) for message mediation which can be loaded into the Workflow Designer and use them for the service orchestration. The following picture shows a screen snippet of the workflow designer toolbox:
The above picture shows an expanded node with 7 custom activities. Let's describe them one by along with their features:
1. CreateMessage
The feature of this primitive is to create a new message transforming a parameterized XSLT template to the output format.
The Mediator has read/write expression text box (etb)
for showing a XSLT template. The mediator etb
can be updated manually, by
a built-in editor or pasting content from the clipboard. The output can be formatted for the specific WCF MessageVersion including a None version (no
SOAP envelope). As I mentioned earlier, the XSLT resource can be parameterized. This mapping is done in the Parameters collection, where each parameter can have own Assign primitive via the expression text. Note, that this article only supports only mapping parameters manually between the XSLT template and workflow.
The CreateMessage
primitive is very powerful message mediator allowing to create dynamically proxy, generate untyped message from metadata or creating a dynamically type based on the schema.
2. XpathMessageInspector
The XPathMessageInspector
is a message mediation primitive to inspect its content.
The feature of this primitive is mapping selected content of the untyped message into the strong type parameter(s). Instead of selecting one single type, this primitive can select a complex type and mapping its members to the output parameters.
The following screen snippet shows example for mapping XPath result:
Note, that the _result
is a alias name of the XPath result. It should be XElement
type. The other names are members of the _result
complex type. The name of the outputs is defined as XName
value such as local name and namespace. The outputs are representing as a mediation state, which can be used directly in the expression text of other primitives, for instance, as a parameter for
CreateMessage
.
The XPath property is an etb
type and like the previous Mediator one, it can be created by editor, manually or pasted by clipboard. To create a correct
XPath value to know the message schema, therefore this primitive has build in editor to simplify this task.
Pressing the XPath button, the XPath Editor will launch its form allowing obtaining a message metadata, navigate in the content of the message or typing the
XPath formula to validate its position in the message, etc. Note, that the namespace collection can be created during this design time. Finally, the xpath formula, namespace collection and design time information are stored in the XAML stack for runtime processing.
3. TransformMessage
The TransformMessage
mediation primitive is a generic primitive for transformation an input message to the output message based of the XSLT mediator.
Its features allow parameterizing XSLT mediator and mapping XPath query to the output parameters. For performance issue, there is a checkbox option to create a buffered message copy or be a last consumer of the message.
The Mediator is an etb
type property, so we can create XSLT resource manually, from editor or paste it by clipboard. Note, that this primitive has only simple XML notepad editor, there is no support for tooling like in the previously primitives.
In this time, editor is under construction, it is real challenge having this feature, which will generate a parameterized XSLT resource based on the input/output metadata in the graphical designer.
The following screen snippet shows a first draft of the user control, where left/right messages have been created based on their metadata. Between them (white pane) is a workspace of the designer-transformer to place functions, parameters or direct mapping line. Every change in this workspace will regenerate XSLT resource in the bottom pane. Finally, the XSLT resource will be stored in the XAML stack, including additional information for design time to recreate a state in the editor.
As I mentioned, this feature is not included in this article, therefore I do recommend to use some 3rd party XML tool, for instance Altova to create a XSLT resource and cut&paste it to this primitive. Of course, for small changes or XSLT resource we can use a built-in XML editor.
4. ShowMessage
The ShowMessage
is a mediation primitive to transfer an input message to the display format on the selected target such as Trace, Console or MessageBox. There is an option to transfer full message or only header. The following screen snippet show this primitive as a custom activity:
5. SendMessage
The SendMessage
primitive is a complex primitive for message mediation using a remote resource. This sequence is in the following order of simple primitives:
CreateMessage
- creating untyped message ShowMessage
for diagnostics and logging purposes Send
untyped message using a generic contract via Endpoint (WF4 built-in activity) ReceiveReply
for receiving a reply message to the sent request (WF4 built-in activity) ShowMessage
for diagnostics and logging purposes XPathMessageInspector
for mapping outputs to the reply message
The SendMessage
primitive has the input/output arguments shown in the following picture in the property grid:
The feature of the above arguments have been described in the previous primitives in details. The
Endpoint
property is a WF4 complex type for declaration of the binding and address of the Send
activity.
Note, that the SendMessage
primitive represents a metadata driven proxy to any service driven by
a SOAP Message. Any changes in the contract model will only require changes in the XAML stack.
6. FireMessage
The FireMessage
primitive is a complex primitive for sending a mediated message out of the box without waiting for its response. This sequence is in the following order of simple primitives:
CreateMessage
- creating untyped message ShowMessage
for diagnostics and logging purposes Send
untyped message using a generic contract via Endpoint (WF4 built-in activity)
The FireMessage
primitive has the input arguments shown in the following picture in the property grid:
As you can see, there are no output arguments. Usage of this primitive is in the notifications, logging, etc. scenarios, where a message is fired and forgotten.
7. Assign2<T>
The WF4 model has built-in Assign<T>
activity for single assigning a value to the variable. The Assign2<T>
feature has a capability to assign multiple parameters for the same complex type. The following screen snippet is an example of assigning to the EventMessage
type:
Note, that Assign2<T>
primitive allows to assign parameters to the anonymous type. The following example shows this feature:
As I mentioned earlier, WF4 Beta2 doesn't support VB compiler option strict OFF, therefore there is no way to type expression like var.id
or <code>var
.name. It will be nice to have it in the release version.
Ok, that's all for describing the message mediation primitives. Now, let's continue with their usage in the real example, for example, get the city weather by zip code (public service http://ws.cdyne.com/WeatherWS/Weather.asmx)
In this article, I included a solution for re-hosting a Workflow Designer on the Windows Form. Thanks to WF4 Technology, this task has been accomplished much easier than previous model WF3 - see more details in my article Manageable Services . This time, I created a user control panel for hosting WorkflowDesigner, ToolBox, Properties, and XAML resource, which is an extracted and embedded XmlNotepad2007 user control. Note, the reason to host all these controls on the
Windows user control is straightforward hosting on the MMC3. Anyway, the following picture shows re-hosted Workflow Designer, which can be very useful test tool utility:
Before we start, I do recommend launching WindowsFormDesignerTester.exe
program and start working with it. As you can see, the left side of the Form is a horizontal splitter between the Workflow panel and XAML panel. This splitter is double clickable to minimize XAML panel. There are 3 buttons on the XAML panel.
The Refresh
and Load
button are for synchronizing the workflow vs. XAML text. The Run
button allows running our XAML stack on the self host console program.
One more thing, a great feature of the WF4 Technology - a panel for ToolBox on the right hand side. There are WF4 built-in activities, the library of custom message mediation activities (primitives) and special folder DynamicWorkflows
. By selecting a custom activity DynamicWorkflowSelector
, we can select any XAML resource in the file system for its embedded into the workflow placer.
Ok, now you know about our Tester and we can start building our example step by step, without using a Visual Studio 2010. Each time we can press a Run
button to see a runtime result. There are three major phases in this example, such as:
- Create request message
- Create generic proxy
- Analyze response
Step A1
Our example (get the city weather by zip code from the public service http://ws.cdyne.com/WeatherWS/Weather.asmx) we are starting to create variables message
and myZipCode
:
Step A2.
From the Toolbox drag and drop the following activities and populate an Output
property with a message
variable:
As you can see, in this step we are going to create and mediate message for the http://ws.cdyne.com/WeatherWS/Weather.asmx web service. To see the message content on the console, we added ShowMessage
activity in the workflow.
Step A3.
In this step, I will demonstrate a power of the tooling built-in the CreateMessage
primitive. Clicking on the Mediator
button, the following Editor will launch.
Type the http://ws.cdyne.com/WeatherWS/Weather.asmx in the
URL textbox and click the Get
button. This is the magic in action, where a service metadata is requested from this endpoint. After analyzing imported metadata, we can see all contracts in the combo boxes. For our example, the following selections are requested:
For this selection such as Soap11
, WeatherSoap
, GetCityWeatherByZip
and Request
we will see in the XML notepad result of the XSLT transformation, where a specific schema is translated into the parameterized soap message XSLT template. We need to make 3 changes to customize this XSLT template such as parameter zipcode
and action
.
After this change, switch the tab for Xml and it should have the same content like it is shown in the following screen snippet:
There are additional two buttons, Test and XSD. These buttons give you a preview of the runtime message and also information about the schema. The following picture is example of the preview message:
Once we are done with this step, we can press the button OK to accept this mediation.
This step shows you result of the message mediation in the workflow designer. As I mentioned earlier, the Meditor
is an etb
property and its contents can be edited manually, too.
Step A5.
In this step, we are going to "plug-in" a workflow variable(s) to the Mediator. Pressing the Parameters button the following Editor will show up:
Create argument zipcode
(note, that this name must match with the XSLT parameter) and map to the variable myZipCode
. The value of the argument is on the expression text, so we can use any VB expression for explicitly or implicitly computation.
Step A6.
This step is for reviewing a XAML stack only. Now, it is the time to see the body of the CreateMessage
mediation primitive. Note, that the SourceUri
represents a state of the mediation during the design time.
Step A7.
OK, that's all for the first phase of the example. In this step, we can press the Run
button to see a workflow processing:
The goal for this phase is to create a proxy with a generic contract (untyped message) and to send our request message to the physical endpoint. Therefore, from the toolbox drag&drop SendAndReceiveReplyFactory
and SendMessage
activities to our workflow, see the following screen snippet:
Rename a display name for Proxy
and type ProcessMessage
in the OperationName. After that, double click on the Send.Content
and setup its property for untyped message, like it is shown in the following screen snippet.
That's all for this step, now we have to add more variables and populate properties for Send activity.
Step B2.
In this step we are adding more variables in the workflow, such as a physical address of the web service and response - see the following screen snippet:
Once we have a variable for response message, we can go back to the workflow and double click on the ReceiveReplyForSend.Content
property to finalize its content like is shown in the following picture:
Step B3.
In this step we are going to finalize values for Send activity. It is very important to put '*' in the
Action
property, because our proxy has for generic contract such as Request/response untyped message (System.ServiceModel.Channels.Message)
. Note, that the Correlation handle
__handle1
is created automatically for this proxy.
That's all for this phase and we can go ahead to see what will be happen in the runtime, let's do it in the next step.
Step B4.
In this step, clicking on the Run button we will run our workflow on self hosted console. The result should be the same like it is shown in the following picture:
That's is very cool, our mediated message has been sent to the endpoint and we received a reply message back. You can see a response (weather) for my zip code = 92705.
OK, let's step in to the last phase. Its goal is to mediate a response message.
Step C1.
Like in the previous phases, we should start with adding more variables into the workflow space. Please, add the following variables:
Note, the result is a XElement
type to enable us for mapping its members to the strong types.
Step C2.
In this step, we will drag&drop the following activities from the toolbox:
The first custom activity is a message mediation primitive to map an output values and the others activities are for showing an expression text on the Console. Before the next step, type response
in the Message property.
Step C3.
This step is about generating XPath formula (blue line) using a magic XPath Editor. Clicking on the XPath button, we can start to play with this editor. Because we are going to mediate a response (reply) message from zip code web service, we need to have a response metadata for this message.
Type the URL address of this endpoint (http://ws.cdyne.com/WeatherWS/Weather.asmx), click the button Get and wait for couple seconds for this metadata. Then select Soap11, WeatherSoap, GetCityWeatherByZIP, and Response. You should have the same result like it is shown in the following picture:
The XPath select is shown in the in the read-only text box, every time you highlight some element. Double click on this text box to move this XPath formula to the output buffer blue colored textbox. The same time, the validation logic will show the status of this XPath in the message.
The other option is from the opposite side. You can type direct XPath formula in the blue colored textbox and see its validation and place in the message.
Once we have the correct XPath formula, then clicking on the OK button, we can persisted XPath, Namespaces and additional info about the design-time source into the XAML stack.
Step C4.
OK, we are very close to finishing this example. We need to map Outputs
parameters, such as city, temperature and result to the mediation message primitive:
Note, that Name is a XName
type, therefore the namespace is required for each member of the
_result
.
That's all, finally we are here to make a complete runtime test.
Step C5.
This is a final step to show our result such as "Santa Ana, 88
" and GetCityWeatherByZIPResponse
object. You can press Enter to repeat this test, or you can go back to the design and change mediation, etc. or you can pick-up another URL to create your example, etc. Have fun with untyped message mediators.
Summary
This example demonstrates a usage of untyped message in the declarative XAML workflow. I hope you have experienced the power of the untyped message mediation driven by metadata - contract model. The contract model can be centralized in the Repository and use it for service mediation.
OK, now if you would like to know some interesting implementation design using the WF4 Technology, the next paragraph is for you.
Implementation
Implementation of the Message mediation is divided into the following projects under the MessageMediationActivityLibrary
solution:
Basically, there are projects for runtime, design-time and testing. Take only first two assemblies from this solution to use the library in your solution. As you can see, the mediation library is divided into two parts, such as runtime and design-time. For building your custom activities, I do recommend having full isolated assemblies from runtime and design-time processes, therefore we can reduce the distributed size of the assembly. On the other hand, the designer assembly can have more sophisticated parts than the runtime one and it can be incrementally developed, etc.
Note, that the Tester's projects are for generic usage and there are no dependencies from the custom activities. Just insert your assemblies into the \bin
Tester's folder such as WindowsFormsDesignerTester
and WorkflowConsoleXamlTester
. Note, that the designer assemblies do not need to be in the console tester, because it is a runtime tester.
In the next code snippets, I am going to show you only few implementation parts. It is difficult to discuss every implementation part in this solution, therefore I only pick the interesting ones:
Assign2<T> activity
In this activity, the execute method has a responsibility to update property of the object To
based on the Parameters
collection. In the following code snippet shown this logic:
protected override void Execute(CodeActivityContext context)
{
var valueOfTo = context.GetValue<T>(this.To);
if (valueOfTo == null)
throw new ArgumentException("...");
this.Parameters.Keys.ToList().ForEach(delegate(string name)
{
Type type = this.Parameters[name].ArgumentType;
var val = this.Parameters[name].Get(context);
var pi = valueOfTo.GetType().GetProperties()
.FirstOrDefault(p => p.Name == name);
if(pi == null)
throw new ArgumentException("..."));
pi.SetValue(valueOfTo, val, null);
});
context.SetValue<T>(this.To, valueOfTo);
}
The first step in the above logic is getting the runtime value of the object To
, then populate this instance from the Parameters.
The last step is to set the value of the runtime To
object. Note, that the To
object must be InOutArgument<T>
.
The logic of collecting all runtime arguments, including parameters from the collection, is shown in the following code snippet. The override method CacheMetadata
is the right place for this task:
protected override void CacheMetadata(CodeActivityMetadata metadata)
{
Collection<RuntimeArgument> arguments = new Collection(<RuntimeArgument>);
RuntimeArgument argumentTo =
new RuntimeArgument("To", typeof(T), ArgumentDirection.InOut, true);
metadata.Bind(this.To, argumentTo);
arguments.Add(argumentTo);
this.Parameters.Keys.ToList().ForEach(delegate(string name)
{
Type type = this.Parameters[name].ArgumentType;
if (this.To.ArgumentType is object)
{
Trace.WriteLine("The type of the assign2 is object");
}
else
{
PropertyInfo pi = typeof(T).GetProperty(name);
if (pi == null)
throw new ArgumentException("...");
if (pi.PropertyType != type)
throw new ArgumentException("...");
}
RuntimeArgument argumentProperty =
new RuntimeArgument("ra_" + name, type, ArgumentDirection.In);
metadata.Bind(this.Parameters[name], argumentProperty);
arguments.Add(argumentProperty);
});
metadata.SetArgumentsCollection(arguments);
}
CreateMessageDesigner
The CreateMessage mediator shows ExpressionTextBox
on the scrollable multi-lines textbox, which can be edited directly without using any editor. Thanks to Beta2 version, the expand/collapse feature has been introduced to make more readable workflow layout. In our custom activities, this feature is implemented as well. The following screen snippet shows the designer of the CreateMessage custom activity. As you can see, the design shows a collapsed template.
The other template, such as Expanded
is a full template for design-time - see the Step A4. There is one button to launch its Editor. The interesting part for this designer code is getting the etb
instance from the dynamic resource template. The following code snippet shows the button handler:
private void buttonMediator_Click(object sender, RoutedEventArgs e)
{
try
{
ContentControl cc = e.Source as ContentControl;
var sp = VisualTreeUtils.GetNamedChild<StackPanel>(cc.Parent, "stackPanel", 5);
ExpressionTextBox expMediator =
VisualTreeUtils.GetNamedChild<ExpressionTextBox>(sp, "controlRoot", 0);
if (expMediator == null)
throw new Exception("...");
var sourceUri = ModelItem.Properties.FirstOrDefault(p=>p.Name=="SourceUri");
string xmltext = expMediator.Expression == null ?
string.Empty :
(string)expMediator.Expression.Properties["ExpressionText"].ComputedValue;
CreateMessageForm dialog = new CreateMessageForm(xmltext, sourceUri==null ?
string.Empty : (string)sourceUri.ComputedValue);
dialog.ShowDialog();
if (dialog.DialogResult == System.Windows.Forms.DialogResult.OK)
{
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
}
As you can see, we can get the button content control by pressing the button. This control has the same parent as our expMediator
control. Walking through the parent 's child, we can find our etb
control. Note, that the Beta2 has some bugs; therefore this step has been divided into two steps. The first one is finding the stackPanel
and then our etb
control. Once we have the etb
control instance, we can get or update its expression property.
OK, let's look at the part of the runtime process, where the CreateMessage
has a responsibility to generate output message.
Xslt transformation is very straightforward using the System.Xml.Xsl.XslCompiledTransform
class and all tasks are focusing on the correct passing of resources to this horse-power class.
The following code snippet shows the major steps for this logic in the CreteMessage.Execute
method:
#region Step 3: Load metadata
try
{
string xsltText = this.Mediator.Get(context).ToString());
using (MemoryStream msMetadata = new MemoryStream(UTF8Encoding.UTF8.GetBytes(xsltText)))
{
XmlDictionaryReader rdMetadata =
XmlDictionaryReader.CreateTextReader(msMetadata, XmlDictionaryReaderQuotas.Max);
xsltprocessor.Load(rdMetadata);
}
}
catch (Exception ex)
{
throw new Exception("...");
}
#endregion
#region Step 4: Transform empty Input and create MessageOutput
try
{
using (MemoryStream msMessageOutput = new MemoryStream())
using (XmlDictionaryWriter wrMessageOutput =
XmlDictionaryWriter.CreateTextWriter(msMessageOutput, Encoding.UTF8, true))
using (XmlDictionaryReader rdEmptyInput =
XmlDictionaryReader.CreateTextReader(UTF8Encoding.UTF8.GetBytes(
"<root />"), XmlDictionaryReaderQuotas.Max))
{
xsltprocessor.Transform(rdEmptyInput, xsltArgList, wrMessageOutput);
msMessageOutput.Position = 0;
XmlDictionaryReader rdMessageOutput = XmlDictionaryReader.CreateTextReader(
msMessageOutput.ToArray(), XmlDictionaryReaderQuotas.Max);
Message outputMessage = System.ServiceModel.Channels.Message.CreateMessage(
rdMessageOutput, int.MaxValue, LibHelper.MessageVersion(this.MessageVersion));
this.Message.Set(context, outputMessage);
}
}
catch (Exception ex)
{
throw new Exception("...");
}
#endregion
The above Step 3 shows implementation of the loading Mediator expression into the XSLT processor instance. The following Step 4 creates an output Message based on the loaded XSLT template. Note, that the Transform method requires an input resource, therefore the dummy (empty) input has been passed. That's fine, because we don't want to transform A -> B format, this mediation is to create a new message B based on the loaded XSLT template.
That's all for the custom activities, let's look at some interesting part from the re-hosted workflow designer. How about ...How will the designer add a custom activity into the Toolbox using a loosely decoupled manner? This mechanism will work for any custom activity assembly added into the bin
folder.
The following code snippet shows "before an application is executed" and "when the application is closing":
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string asmLocation = typeof(WorkflowDesignerUserControl).Assembly.Location;
string filepathname = Path.ChangeExtension(asmLocation, "xaml");
Form1 form = new Form1();
LoadToolboxItems(form.Designer);
LoadXamlFile(form.Designer, filepathname);
form.FormClosing += delegate(object sender, FormClosingEventArgs e)
{
SaveXamlFile(form.Designer, filepathname);
};
Application.Run(form);
}
As you can see, there is a LoadToolBoxItems
call in the start-up sequence of the re-hosted workflow designer. The following code snippet shows interesting fragments of the implementation, where each assembly in the bin folder is queried for types Activity
and IActivityTemplateFactory
. The designer is called to add item into the Toolbox for each collection of this type.
private static void LoadToolboxItems(WorkflowDesignerUserControl designer)
{
var types = from t in asm.GetExportedTypes()
where typeof(Activity).IsAssignableFrom(t)
select t.FullName;
if (types != null && types.Count() > 0)
{
string category = asm.FullName.Split(',')[0] + "_" + asm.FullName.Split(',')[1];
types.ToList<string>(().ForEach(typename => designer.AddToolboxItem(category, typename, asm.FullName));
}
var factories = from t in asm.GetExportedTypes()
where typeof(IActivityTemplateFactory).IsAssignableFrom(t)
select t.FullName;
if (factories != null && factories.Count() > 0)
{
string category = "DynamicWorkflows";
factories.ToList<string>(().ForEach(typename => designer.AddToolboxItem(category, typename, asm.FullName));
}
}
Note, that the IActivityTemplateFactory
is a great new feature of the Beta2 allowing importing pre-factored composite activity defined by XAML stack into the master workflow.
Implementation of the dynamic custom activity is very straightforward like it is shown in the following code snippet. This is a full implementation of the DynamicWorkflowSelector
custom activity using an
OpenFileDialog
to select a specific XAML resource for its importing into the master place.
public class DynamicWorkflowSelector : IActivityTemplateFactory
{
public Activity Create(DependencyObject target)
{
DynamicActivity activity = null;
try
{
OpenFileDialog dialog = new OpenFileDialog()
{
Title = "Select a XAML workflow",
InitialDirectory = AppDomain.CurrentDomain.BaseDirectory,
Multiselect = false,
AddExtension = true,
SupportMultiDottedExtensions = true,
Filter = "Xaml files(*.xaml)|*.xaml"
};
if (dialog.ShowDialog() == DialogResult.OK)
{
using (FileStream stream = File.OpenRead(dialog.FileName))
{
activity = (DynamicActivity)ActivityXamlServices.Load(stream);
}
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("...");
}
return activity == null ? null : activity.Implementation();
}
}
In the above example, the source of the XAML resource is a File System, but it can be anything else such as web service, Repository, etc.
That's all about the message mediation in the WF4 using the custom activities.
Conclusion
In conclusion, this article described custom activities for message mediation in the upcoming WF4 Technology. The untyped message is massaged using a message primitives based on the XSLT technology. Basically, the mediation primitive in the design-time uses a complex tooling support to generate a runtime XSLT resource.
This article introduced some concept design of the tooling support based on the WSDL metadata. I hope you enjoined it.