Jamie Kurtz

Promoting architectural simplicty

  Home  |   Contact  |   Syndication    |   Login
  25 Posts | 0 Stories | 48 Comments | 5 Trackbacks

News



Archives

Tuesday, January 26, 2010 #

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>
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

I’m working on a project where we are using the Composite Application Library from Microsoft’s patterns & practices team. You can read the official documentation on that site and on MSDN for all the details, but basically the CAL allows you to build applications using totally decoupled modular components – or “modules” in CAL vernacular. These modules are discovered at runtime and are registered in the CAL container, which then handles each modules’ loading, showing, unloading, etc. (I’m greatly simplifying here).

To enable runtime module discovery, you can pick between one of four different “cataloging” methods:

  • Populate from code
  • Populate from XAML
  • Populate from a configuration file
  • Populate from a directory

The fourth one, populating from a directory, is what we wanted to use. This method of cataloging allows you to drop modules into a directory and have them picked up by the CAL. Essentially, it examines all assemblies in the directory and looks for types decorated with the ModuleAttribute attribute.

The CAL’s implementation of this directory cataloging only allows for a single directory. It does this through the DirectoryModuleCatalog catalog. As taken from the CAL’s documentation, the following example will configure your application to search in a Modules subdirectory for all modules:

   1: protected override IModuleCatalog GetModuleCatalog()
   2: {
   3:     return new DirectoryModuleCatalog() {ModulePath = @".\Modules"};
   4: }

Very cool!! But… we need to search through multiple directories – not just a single directory.

Long story short, I was able to subclass the DirectoryModuleCatalog to create a new directory-based catalog that can search as many directories as you want to give it. Now, you might laugh at my new catalog, but it does work!! Here it is:

   1: /// <summary>
   2: /// Allows our shell to probe multiple directories for module assemblies
   3: /// </summary>
   4: public class MultipleDirectoryModuleCatalog : DirectoryModuleCatalog
   5: {
   6:     private readonly IList<string> _pathsToProbe;
   7:      
   8:     /// <summary>
   9:     /// Initializes a new instance of the MultipleDirectoryModuleCatalog class.
  10:     /// </summary>
  11:     /// <param name="pathsToProbe">An IList of paths to probe for modules.</param>
  12:     public MultipleDirectoryModuleCatalog(IList<string> pathsToProbe)
  13:     {
  14:         _pathsToProbe = pathsToProbe;     
  15:     }
  16:  
  17:     /// <summary>
  18:     /// Provides multiple-path loading of modules over the default <see cref="DirectoryModuleCatalog.InnerLoad"/> method.
  19:     /// </summary>
  20:     protected override void InnerLoad()
  21:     {
  22:         foreach (string path in _pathsToProbe)
  23:         {
  24:             ModulePath = path;
  25:             base.InnerLoad();
  26:         }
  27:     }
  28: }

All you need to do is provide an IList<string> of paths – that’s it! So, to update the CAL’s sample:

   1: protected override IModuleCatalog GetModuleCatalog()
   2: {
   3:     IList<string> pathsToProbe = GetPathsToProbe();
   4:     return new MultipleDirectoryModuleCatalog(pathsToProbe);
   5: }
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

For quite a while now I’ve been wondering if our culture of text messages, emails, messenger/chat conversations, short YouTube clips, rapid-fire news channels, PodCasts, blog posts, Twitter, and Facebook updates is somehow hurting my ability to focus on and develop deeper concepts. By that I mean really dig in, learn, and add value to some larger-then-me idea. As I write these few sentences, emails are streaming in, the phone is ringing, and no doubt my friends and family are Tweeting and writing on my Facebook wall. And sometimes the urge to check on these things is too much. Actually, most of the time it IS too much – and I give in. In computer terms I am performing a context switch, thus flushing out my cache and starting over with the next task. When I finally get back to writing this blog post, my brain struggles to re-engage and become effective again.

It has been know for a while that even answering emails – while working on other mentally tasking activites – can really set you back. It interrupts flow. So you may spend 1 minute reading an email, but then your brain spends another 20 minutes trying to get back to the point at which you switched from the task at hand to read that email. The term "flow” is often used to describe the state in which the average person is really effective. Their brain is chugging along and they are adding real value to whatever they are working on. (well, I suppose they could be adding dis-value to whatever they’re working on, but that’s another story!).

One of the best books I’ve ever read on productivity is Getting Things Done, by David Allen. He provides a very simple and pragmatic approach to organizing and prioritizing all the “things” we have to do in life – whether small work tasks, or larger life goals. One of Mr. Allen’s tips that really stuck with me – among many – was reserving only certain times of the day for checking email. For example, maybe you check your email at 9am, 12pm, and 4pm. Then in between those times you are working (or playing or painting or walking, etc.). I learned long ago that I work much better by simply turning off the little Outlook “new mail” notifications (both visually and the little bell sound). The goal is to get into a “flow” – a state described very well by David Chaplin in his article on Maximizing Development Productivity:

Being in flow is when you are fully immersed in a task. You are so focused on it that you are almost in a trance like state. Hours can go by without you noticing. Work gets done very fast. When you are in flow you are at running at your highest velocity. It takes approximately 20 minutes to get into flow. However, if you get disturbed and knocked out of flow, it will take another 20 minutes immersion time before you are back in full flow. It is important to stay immersed in flow for long periods at a time to get anything considerable done.

Take a look at the productivity graphs in that article, and compare the “Ideal Productivity” graph to the “Disruptions” graph. Most developers, and probably most people in general, would agree with the idea of “flow”. Further, most people would probably agree that it takes time to re-engage in “flow” – following an interruption.

But I was wondering, what is the relationship between this constant interruption of flow and my ability to actually stay in flow (i.e. ignore the interruptions and stay engaged)? In other words, flip the whole theory upside down. As a people, are we learning to better deal with the constant multi-tasking demands and context switching? Or is a pervasive interruption of flow actually damaging to our ability to really focus on and develop an idea?

I can see in myself, that yes, this constant context switching is altering my modus operandi. My mind seems to crave a context switch!! Am I learning to become bored with a mere 10 minutes of “flow”? As a result of years of multi-tasking, is it now much harder to get myself to focus on anything that is not ultra exiting? Is my brain compelled – even addicted – to catching a quick news article, or read a short blog post, or fire off a Twitter update?

This morning I stumbled on this recent article, http://www.crn.com/it-channel/219401343;jsessionid=DZRE4FY5OPW5JQE1GHPCKHWATMY32JVN, titled “Multitaskers Not Very Good At Multitasking”. This study is early in the exploration of multi-tasking and its relationship with our current-age media, but its results resonate with my thinking:

The subjects were asked to perform a simple cognitive filtering test, to focus on the characteristics of a group of red triangles while ignoring a group of blue triangles. The end result: So-called multitaskers performed worse than people who were not regular media multitaskers, according to Reuters.

Similar findings occurred when the study participants took tests to measure organizational ability and task switching. Multitaskers were slower to shift their attention from one task to another.

While this doesn’t directly speak to my feelings and observations around the interruption of “flow”, it certainly debunks the idea that we are getting better at managing all of our inputs. Such practices as meditation, reading actual books (you know, with pages and chapters), taking naps, and enjoying long walks may need become more than infrequent pleasures. We may find ourselves in a state where these activities are required just to avoid the kind of mental degeneration we are starting to see all around us. While watching 30-second news clips and reading 5-word Facebook updates is certainly enjoyable and keeps us in touch with our friends and family and the rest of the world, I don’t think society can really move forward without deep intellectual engagement in ideas and uninterrupted “flow”.

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati