Jamie Kurtz

Promoting architectural simplicty

  Home  |   Contact  |   Syndication    |   Login
  27 Posts | 0 Stories | 54 Comments | 5 Trackbacks

News



Archives

In VS2010 Beta 2 you can associate your own activity designer with a custom activity in one of two ways:

  1. Using System.ComponentModel.DesignerAttribute on the custom activity
  2. Implementing System.Activities.Presentation.Metadata.IRegisterMetadata, using the Register() method to set up the association at run-time

It should be obvious that option (1) couples your custom activity directly to a specific designer. While not ideal, in many cases this is sufficient. But, there are some scenarios that require a looser coupling. For example, I may want to choose one of several designers at run-time – based on the user’s authorization. This post describes the steps needed to implement option (2) – because it’s a little tricky.

First, let’s assume we have a project in our solution called MyActivities. Let’s also assume that this project contains a custom activity called SendEmailActivity. This activity derives from CodeActivity and simply uses System.Net.Mail.SmtpClient to send an email within the Execute() method. Our SendEmailActivity activity has several InArguments – to allow the user to set the email address, subject, body, etc. The code for our SendEmailActivity might look like this:

   1: using System;
   2: using System.Activities;
   3: using System.Net.Mail;
   4:  
   5: namespace MyActivities
   6: {
   7:     public class SendEmailActivity : CodeActivity 
   8:     {
   9:         public InArgument<string> To { get; set; }
  10:         public InArgument<string> From { get; set; }
  11:         public InArgument<string> Subject { get; set; }
  12:         public InArgument<string> Body { get; set; }
  13:         public InArgument<string> Host { get; set; }
  14:  
  15:         protected override void Execute(CodeActivityContext context)
  16:         {
  17:             SmtpClient client = new SmtpClient(Host.Get(context));
  18:  
  19:             try
  20:             {
  21:                 client.Send(
  22:                     From.Get(context),
  23:                     To.Get(context),
  24:                     Subject.Get(context),
  25:                     Body.Get(context));
  26:             }
  27:             catch (Exception ex)
  28:             {
  29:                 string error = ex.Message;
  30:                 if (ex.InnerException != null)
  31:                 {
  32:                     error = ex.InnerException.Message;
  33:                 }
  34:                 Console.WriteLine("Failure sending email: " + error);
  35:             }
  36:         }
  37:     }
  38: }

 

So now, to create an activity designer and associate it to the SendEmailActivity:

  1. Create a Workflow Activity Designer Library project in the same solution with a name that appends ".VisualStudio.Design" to the name of the project where the custom activity resides. In this example, the name would be MyActivities.VisualStudio.Design
  2. Create your activity designer. We'll call the designer class SendEmailActivityDesigner. (I’ve pasted the XAML for this designer at the end of this post)
  3. From the new designer project (MyActivities.VisualStudio.Design), reference the following:
    1. MyActivities - as a project reference
    2. PresentationFramework
    3. System.Activities.Core.Presentation
    4. System.Activities.Presentation
  4. In the code behind for the activity designer's XAML (SendEmailActivityDesigner.xaml.cs), implement the IRegisterMetadata interface. This includes utilizing Metadatastore in the IRegisterMetadata.Register() method. The code should look like this:
   1: using System.Activities.Presentation.Metadata;
   2: using System.ComponentModel;
   3:  
   4: namespace MyActivities.VisualStudio.Design
   5: {
   6:     public partial class SendEmailActivityDesigner : IRegisterMetadata
   7:     {
   8:         public SendEmailActivityDesigner()
   9:         {
  10:             InitializeComponent();
  11:         }
  12:  
  13:         public void Register()
  14:         {
  15:             AttributeTableBuilder builder = new AttributeTableBuilder();
  16:             builder.AddCustomAttributes(
  17:                 typeof(SendEmailActivity), 
  18:                 new DesignerAttribute(typeof(SendEmailActivityDesigner)));
  19:             MetadataStore.AddAttributeTable(builder.CreateTable());
  20:         }
  21:     }
  22: }

 

Now, in order for the VS activity designer to "see" your designer for the corresponding activity you need to make sure the MyActivities.VisualStudio.Design.dll file ends up in the same folder as MyActivities.dll. I've done this so far with a simple post-build event on the MyActivities.VisualStudio.Design project.

Then, finally, in the project that contains the workflow on which you want to drag-and-drop the SendEmailActivity make sure you add project references to both MyActivities and MyActivities.VisualStudio.Design.

* Note that this works only for the Visual Studio hosted workflow designer. If you re-host the designer in your own application you need to remove the "VisualStudio" part of the project/DLL name from your activity designer project. In our example this would be: MyActivities.Design.

 

Here’s the XAML code for the SendEmailActivityDesigner designer. The code-behind content is above, under step 4. (I’m certainly no WPF expert so go easy on me!)

   1: <sap:ActivityDesigner x:Class="MyActivities.VisualStudio.Design.SendEmailActivityDesigner"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
   5:     xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation" 
   6:     xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation">
   7:     <sap:ActivityDesigner.Resources>
   8:         <sapc:ArgumentToExpressionConverter
   9:             x:Key="ArgumentToExpressionConverter"
  10:             x:Uid="swdv:ArgumentToExpressionConverter_1" />
  11:     </sap:ActivityDesigner.Resources>
  12:  
  13:     <Grid>
  14:         <Grid.RowDefinitions>
  15:             <RowDefinition></RowDefinition>
  16:             <RowDefinition></RowDefinition>
  17:             <RowDefinition></RowDefinition>
  18:             <RowDefinition></RowDefinition>
  19:             <RowDefinition></RowDefinition>
  20:         </Grid.RowDefinitions>
  21:         <Grid.ColumnDefinitions>
  22:             <ColumnDefinition Width=".4*"></ColumnDefinition>
  23:             <ColumnDefinition Width=".6*"></ColumnDefinition>
  24:         </Grid.ColumnDefinitions>
  25:  
  26:         <TextBlock 
  27:             Grid.Row="0"
  28:             Grid.Column="0"
  29:             Text="Host: " />
  30:  
  31:         <sapv:ExpressionTextBox
  32:             Name ="HostTextBox"
  33:             Grid.Row="0"
  34:             Grid.Column="1"
  35:             Expression="{Binding Path=ModelItem.Host, Mode=TwoWay,
  36:                 Converter={StaticResource ArgumentToExpressionConverter},
  37:                ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"
  38:                 MinLines="1" MaxLines="1" MinWidth="100"
  39:                 HintText="Host"/>
  40:  
  41:         <TextBlock 
  42:             Grid.Row="1"
  43:             Grid.Column="0"
  44:             Text="From: " />
  45:  
  46:         <sapv:ExpressionTextBox 
  47:             Grid.Row="1"
  48:             Grid.Column="1"
  49:             Expression="{Binding Path=ModelItem.From, Mode=TwoWay,
  50:                 Converter={StaticResource ArgumentToExpressionConverter},
  51:                ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"
  52:                 MinLines="1" MaxLines="1" MinWidth="100"
  53:                 HintText="From"/>
  54:  
  55:         <TextBlock 
  56:             Grid.Row="2"
  57:             Grid.Column="0"
  58:             Text="To: " />
  59:  
  60:         <sapv:ExpressionTextBox 
  61:             Grid.Row="2"
  62:             Grid.Column="1"
  63:             Expression="{Binding Path=ModelItem.To, Mode=TwoWay,
  64:                 Converter={StaticResource ArgumentToExpressionConverter},
  65:                ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"
  66:                 MinLines="1" MaxLines="1" MinWidth="100"
  67:                 HintText="To"/>
  68:  
  69:         <TextBlock 
  70:             Grid.Row="3"
  71:             Grid.Column="0"
  72:             Text="Subject: " />
  73:  
  74:         <sapv:ExpressionTextBox 
  75:             Grid.Row="3"
  76:             Grid.Column="1"
  77:             Expression="{Binding Path=ModelItem.Subject, Mode=TwoWay,
  78:                 Converter={StaticResource ArgumentToExpressionConverter},
  79:                ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"
  80:                 MinLines="1" MaxLines="1" MinWidth="100"
  81:                 HintText="Subject"/>
  82:  
  83:         <TextBlock 
  84:             Grid.Row="4"
  85:             Grid.Column="0"
  86:             Text="Body: " />
  87:  
  88:         <sapv:ExpressionTextBox 
  89:             Grid.Row="4"
  90:             Grid.Column="1"
  91:             Expression="{Binding Path=ModelItem.Body, Mode=TwoWay,
  92:                 Converter={StaticResource ArgumentToExpressionConverter},
  93:                ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"
  94:                 MinLines="10" MaxLines="15" MinWidth="700" MinHeight="100"
  95:                 HintText="Body"/>
  96:     </Grid>
  97: </sap:ActivityDesigner>
posted on Tuesday, January 26, 2010 9:27 AM

Feedback

# re: Associating a WF4 activity designer to a custom activity using MetadataStore 2/7/2011 9:41 AM Kavya
Hi, Excellent article.
Helped me a lot. I'm very new to workflow and started off with custom activities.
they work absolutely fine. My next step is to associate a designer to it.
Bit silly question, but the xaml was not generated but you had to write all the fields? :(
Apologies if i'm sounding bit silly

# re: Associating a WF4 activity designer to a custom activity using MetadataStore 7/27/2011 1:08 PM Sheshonq
Very interesting especially the IRegisterMetadata interface

# re: Associating a WF4 activity designer to a custom activity using MetadataStore 12/8/2011 7:42 PM peter cli
how would I add xaml ui ?
For example, a box to hold the Body, To, from Etc.


# re: Associating a WF4 activity designer to a custom activity using MetadataStore 12/9/2011 2:39 PM Jamie
@ peter cli

Adding the UI for the custom Activity Designer is what I did above - i.e. the XAML is used in the .xaml file for the custom designer when you are creating it in Visual Studio.

Does that answer your question?

# re: Associating a WF4 activity designer to a custom activity using MetadataStore 12/6/2012 2:00 PM Alex
please remove the line numbers, we are in 2012 :)

Post A Comment
Title:
Name:
Email:
Comment:
Verification: