Tuesday, 23 April 2013

Extending ServiceStack

I’ve been using ServiceStack on a couple of projects recently and I have to say I’m well impressed, it’s a superbly written set of assemblies, really well thought out, extremely fast and extensible too.

One of the things I’ve been struggling with however is how to inject an implementation of an interface into the pipeline before everything else. The API I’m exposing requires a couple of Http headers to be present, and not only do these need to be verified as present, but I want to use them to initialise one of the services (we’ll call it IMerchantTokenService) that other parts of the application need.

The general process I’d like to go through is as follows…

  • When an HTTP Request comes in, check for the headers
    • If they exist, create an implementation of IMerchantTokenService that contains these headers and stuff it into the IOC container. If not, throw an exception.
    • As IMerchantTokenService is now registered in the IOC container, when I resolve any other services that have this as a requirement it will be injected into all of these other services too.

Like most things, until you “get” the API you’re left wondering how to implement a feature, and once you know the API it’s second nature. This was one of those days.

After some fruitless tinkering I took a step back and thought – my headers requirement sounds a lot like an HTTP session (even though it’s used for something different). So I went and had a look at how ServiceStack deals with sessions and, sure enough, that was also the place where I could plug in my stuff. It’s easy when you know how.

The feature I was searching for is called a “Plugin”. Simply put, a plugin allows you to attach some extra processing to the request pipeline in ServiceStack, and you can get in before your service is instantiated and therefore affect which service(s) are available to import into your dependencies.

Writing a plugin

Creating a plugin is trivially easy – create a class that derives from IPlugin, override the Register method and bingo, you’re in business…

Code Snippet
/// <summary>
/// This class provides a plug-in that's called prior to any services being
/// executed. It verifies that the required header values are in place,
/// and registers a service so that other objects can access these
/// values later in the cycle
/// </summary>
public class MerchantTokenFeature : IPlugin
{
    /// <summary>
    /// Add a callback so that we get called when a request comes in
    /// </summary>
    /// <param name="appHost">The hosting provider</param>
    public void Register(IAppHost appHost)
    {
        if (null == appHost)
            throw new ArgumentNullException("appHost");

        _appHost = appHost;
        appHost.PreRequestFilters.Add(this.PreFilter);
    }

    /// <summary>
    /// Process the request headers
    /// </summary>
    /// <param name="request">The Http request</param>
    /// <param name="response">The Http response</param>
    private void PreFilter(IHttpRequest request, IHttpResponse response)
    {
        string apiKey = request.Headers[Headers.APIKey];
        string merchantIdentifier = request.Headers[Headers.MerchantIdentifier];

        if ((null == apiKey) || (null == merchantIdentifier))
            throw HttpError.Unauthorized(string.Format("You must send the {0} and {1} headers", Headers.APIKey, Headers.MerchantIdentifier));

        _appHost.Register<IMerchantTokenService>(new MerchantTokenService(apiKey, merchantIdentifier));
    }

    private IAppHost _appHost;
}

This plug-in hooks an additional entry into the PreRequestFilters collection, and so when a request comes in my PreFilter method is called and I can check the Http headers and then manually construct the additional service.

Registering your PlugIn

The only other requirement is to register the plug-in. In the Configure method of your AppHostBase derived class just add the plugin…

Code Snippet
public override void Configure(Funq.Container container)
{
    SetConfig(new EndpointHostConfig { ServiceStackHandlerFactoryPath = "api" });
    Plugins.Add(new MerchantTokenFeature());
}

That’s it – up and working. I can now import IMerchantTokenService wherever I need, and know that this dependency will be available by the time my service gets instantiated and it starts looking for it’s own dependencies.

Monday, 10 September 2012

Selecting Hierarchical data with WCF Data Services

In this post I’ll describe how you can use hierarchical data with WCF Data Services as it’s not immediately obvious how to do this, and there’s also an issue (which I believe to be a bug) that needs to be addressed in order for it to work.

In this example I have a simple table shown below, with a hierarchical link between employees. The keys are integers, defined as IDENTITY columns in SQL server. All the code for this example is attached to this post if you should wish to look into it further.

image

I’ve also created a stored procedure that selects people in a hierarchical manner using a CTE. This is as follows…

-- Create an SP that selects a hierarchical set of data
CREATE PROCEDURE SelectPersonHierarchy(@personId int) AS
SET NOCOUNT ON
;
WITH PersonHierarchy AS
(
SELECT PersonId, Name, ManagerId
FROM Person
WHERE PersonId = @personId
UNION ALL
SELECT p.PersonId, p.Name, p.ManagerId
FROM Person p
INNER JOIN PersonHierarchy ph
ON p.ManagerId = ph.PersonId
)
SELECT PersonId, Name, ManagerId
FROM PersonHierarchy
GO

So far so good. Next I created an EF model that includes the table above and the stored procedure too. Then I created a simple WCF data service as shown below – the extra method is used to expose the hierarchical stored procedure, so that clients can call it directly.


public class PersonService : DataService<PersonEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);

// And set the protocol version used
config.DataServiceBehavior.MaxProtocolVersion =
DataServiceProtocolVersion
.V2;
}

[WebGet]
public IQueryable<Person> GetFullPerson(int personId)
{
return this.CurrentDataSource.
SelectPersonHierarchy(personId).AsQueryable();
}
}

Next I created the client for this service using Add Service Reference. Then I created a method that would call the GetFullPerson method on the server, the outline of which is shown below…


public Person GetFullPerson(int personId, bool makeItWork)
{
Person root = null;

this.CreateQuery<Person>("GetFullPerson")
.AddQueryOption("personId", personId).ToList().ForEach(
t =>
{
if (null == t.ManagerId)
root = t;
else
{
Person parent = this.FindManager(root, t.ManagerId.Value);
parent.Subordinates.Add(t);
}
});

return root;
}

This method converts the data returned from the WCF operation to a list, iterates through that list and builds the client Person hierarchy (using a small helper function FindManager which, given a tree of objects, looks for the manager of the current person).


So, with all that in place we can write some unit tests to test the service. In my tests I’ve just used Boss and Employee as my two people. First off we’ll insert a Boss and an Employee & link them together…


Uri geller = new Uri("http://localhost:11341/PersonService.svc");
ServerService.PersonEntities context =
new ServerService.PersonEntities(geller);

// Insert hierarchical data to test with
var boss = new ServerService.Person { Name = "Boss" };
context.AddToPeople(boss);

var employee = new ServerService.Person { Name = "Employee", Manager = boss };
context.AddToPeople(employee);

// Important to add this - otherwise EF won't record the parent/child
// link in the database
context.AddLink(boss, "Subordinates", employee);

// Now save this to the server
context.SaveChanges();

Apologies for the awful pun in the Uri variable, I just can’t help myself. Here I create a boss, add an employee and then save the changes to the database. The important thing to mention is that you must also call context.AddLink() to ensure that there is a link between the parent and child objects. Here “Subordinates” is the name of the EF navigation property that is mapped from the parent of the relationship to the child. After saving these changes in the database you’ll see the following in the DB (Id’s will differ)…


image


OK, so far so good, we can create and save data to the server.


The Problem


The next thing I wanted to do was to verify that I could reload a person (in a hierarchical manner). So, my unit test was basically as follows…



  • Create a Boss and an Employee

  • Save these to the database

  • Reload these and test that all data was correct, including the link between employee and boss. This is where it all started to go wrong.

The reload code wasn’t working properly, and I tracked it down to the ManagerId field (after a fair amount of time). First I used Fiddler to log what was coming down from the server. My request looked OK…


image


And the data coming back from the server also looked OK…


image


However in my code that was looping through the data that came from the server the ManagerId was always null, for each and every row returned from the server.


In another test however it was fine – what gives?


The Solution


My two unit tests were identical – well, nearly. One verified what it got inline, another called a method to do that verification and the crucial difference was that the one that worked used a new service context. So, if you create a hierarchy of Boss & Employee on the client, save changes and reload (using the same client proxy), the hierarchical link is broken. If however you create a new client proxy and call the server everything works as expected.


My solution to this was to include the following in the method that calls the server…


public Person GetFullPerson(int personId)
{
Person root = null;

var currentMergeOption = this.MergeOption;
this.MergeOption = MergeOption.OverwriteChanges;

this.CreateQuery<Person>("GetFullPerson")
.AddQueryOption("personId", personId).ToList().ForEach(
t =>
{
if (null == t.ManagerId)
root = t;
else
{
Person parent = this.FindManager(root, t.ManagerId.Value);
parent.Subordinates.Add(t);
}
});

this.MergeOption = currentMergeOption;

return root;
}

This ensures that the data read from the server overwrites the data on the client, and in this instance that’s enough for the ManagerId to be set correctly rather than being Null.


Example Code


The following is a link to a .zip file that contains the same code. You’ll find a database script in the EFHierarchyProblem project called SchemaAndLogic.sql. Run this against your database.


You should also update the database connection strings in each project to reference your database. There are 3 projects in the solution, a class library that contains the EF model and the service, a Web project that exposes that service, and a Test project that runs the unit tests. When you run the tests you should see the following in the test results window…


image


The Test_ServerService_WillFail should work but doesn’t, as the ManagerId is overwritten when reloading the data. The _WillWorkSameContext test uses the merge option to overwrite local data to ensure that the ManagerId isn’t overwritten, and the _WillWorkNewContext test inserts data but then requests it from a new client proxy which will always work.


The .zip can be downloaded from here.

Tuesday, 12 June 2012

Redesigning the Decision activity

In a couple of recent posts on Morphing Conditional Activities and Constraining Dropped Activities I’ve been steadily redesigning the Decision activity so that it is easier to use by non-programmers. There was one thing I left out which was re-styling the designer so that it looks and works a bit more like other activities. I’ll address that in this post.

Where I left off was an activity that looks like the following (I’ve added two red rectangles to indicate the parent and child activities here)…

image

That’s OK, however the title bar of the decision activity is looking a bit minimal. The issue here is that this name is, by default, not bound to the display name of the inner activity, and so it displays nothing. What would be preferable is to have the title bar look and work like the other standard activities, and for that there’s a fair bit of work to do.

There are two classes you can derive your activity designers from in .NET – most derive from ActivityDesigner which provides the standard look and feel as shown above, and if you wish to have more control over what is displayed (where more control == do it all yourself) then you can derive from WorkflowViewElement.

Having looked at the XAML for the inbuilt activity designer class (using Reflector) I felt it was not possible to use this, primarily because there was no way I could see to alter the binding on the caption bar. So, I created my own and along the way made some tweaks.

First off I started with a grid comprising 2 rows as follows…

image

The second row simply contained a WorkflowItemPresenter that will display the conditional activity dropped into place. The top row had to be further split into three columns, the left hand side for the decision icon, the middle for the display name of the child activity and the right for the validation error/warning icon.

Note: The inbuilt activity designer also includes UI for debugging, and also UI for expand/contract. I omitted the first part as the application I’m working on does not permit you to interactively debug a workflow, and I did not wish to expand or contract this activity so left this out of the designer.

So, my designer now looks something like this…

image

The icons for all of the standard activities are contained within the System.Activities.Presentation assembly as a resource, so I added in this and a couple other standard resource dictionaries to my activity XAML.

The validation icon needs to be displayed if the child activity has a validation warning or error. The activity we’re designing is the FlowDecision, and the Condition property is the one that holds the child activity, so we can use a binding syntax of ModelItem.Condition.ValidationState to check whether the child activity is (or is not) valid. Similarly, the ModelItem.Condition.ValidationMessage property path gets the error(s) associated with the child activity.

So, we have the Icons done, now for the name.

In the standard Activity designer, the name is actually formed using two controls, a textblock that displays the read-only name, and a textbox that is displayed when you click into the name, or when you tab into it using the keyboard. I wanted the same behaviour on my control so crufted up a couple of controls and hooked to a couple of events in order to get this working as expected. If you use the standard activity designer to rename an activity, you may notice that the textbox is not positioned exactly the same as the textblock, so when you enter edit mode the text will shift slightly. This annoys me something chronic, so I made sure that with my designer both items line up correctly, so there is no perceptible shift in the text when moving in and out of edit mode.

With that in place my activity is complete. Now I see the following on screen…

image

There’s some more going on in the XAML to ensure that the outline updates appropriately when selected, and this uses the standard colour resources so fits in with the colour scheme. Feel free to download the code and have a poke around.

Friday, 8 June 2012

ExpressionTextBox in .NET 4.5

The ExpressionTextBox allows you to databind to an argument on your activity and has, up until 4.5, had an optional OwnerActivity attribute which seemed to be optional. Whilst all activities in the framework used it, some custom activities might have omitted it as it worked in 4.0 without needing to be explicitly defined.

Now with .NET 4.5 this attribute is no longer optional – check out what I get if I forget to add it (the 3rd textbox is the one with the missing attribute, and I’m in a C# project)…

Capture

Not only do you get the horrific “Enter a VB expression” text, which sends a chill down my spine each time I see it, you’ll also find that the textbox is read-only if you omit the OwnerActivity attribute. It’s easy to fix though, the following highlighted XAML is all you need to change…

  <sapv:ExpressionTextBox 
Expression="{Binding ModelItem.Body, Converter={StaticResource conv}}"
OwnerActivity="{Binding ModelItem}" .../>

This change in behaviour might be a problem for anyone upgrading their solutions to 4.5, so hopefully someone will find this post useful.

Monday, 16 April 2012

Adding extensions to a hosted workflow

When hosting Workflows using WorkflowServiceHost or Windows Server AppFabric you may wish to add extensions to the runtime environment so that custom activities can access these extensions. Typically you’ll write custom activities to access some form of extension using a custom interface, and then provide an implementation of this interface at runtime.

When you are self hosting (using WorkflowInvoker or WorkflowApplication) it’s a simple matter of adding an object to the ‘host’, such as shown in the code below…

WorkflowInvoker invoker = new WorkflowInvoker(GetWorkflow());
invoker.Extensions.Add<IActivityLogger>(() => new ActivityLogger());

If you’re hosting using configuration using WorkflowServiceHost or AppFabric however it’s common to define everything in the web.config, however Workflow 4 doesn’t come with an out of the box mechanism to do this.


What I’d like to do is be able to configure up a bunch of extensions using the following in my web.config file…


<workflowExtensions>
<
extensions
>
<
add name="{something}"
extensionType="{Fully Qualifier Typename}"
{Optional Parameters}
/>
</
extensions
>
</
workflowExtensions
>

Now, if you’ve been using .NET for a while you might recognise this pattern – it’s very simple to the way you configure providers in ASP.NET, for things like membership, roles etc. I’ve done this deliberately – the provider model is a good pattern to use and fits this example well.


What I then need is something to read the set of extensions, and instantiate them for each hosted workflow. To do this you need a bit of WCF knowledge.


Behavior Extensions


A behavior extension allows us to inject some code into the WCF pipeline, and in this case I’m using one to instantiate objects defined in my <workflowExtensions> element. First up you need a class that derives from BehaviorExtensionElement as shown in the code below…


/// <summary>
///
Class which is used to define the configuration element for the
/// web.config, so we can add extensions to the workflow
/// </summary>
public class WorkflowExtensionsElement :
BehaviorExtensionElement
{
/// <summary>
///
Return the type of object that is created by this extension
/// </summary>
public override Type BehaviorType
{
get { return typeof(WorkflowExtensionsBehavior); }
}

/// <summary>
///
Construct the behavior
/// </summary>
/// <returns>
A behavior, created from the config in the web.config
</returns>
protected override object CreateBehavior()
{
List<WorkflowExtension> extensions = new List<WorkflowExtension>();

foreach (WorkflowExtensionConfigElement item in WorkflowExtensions)
extensions.Add(new WorkflowExtension(item.Name,
item.ExtensionType, item.Parameters));

return new WorkflowExtensionsBehavior(extensions);
}

///
Declare the extensions collection property.
[ConfigurationProperty("extensions", IsDefaultCollection = true)]
[ConfigurationCollection(typeof(WorkflowExtensionsCollection),
AddItemName = "add",
ClearItemsName = "clear",
RemoveItemName = "remove")]
public WorkflowExtensionsCollection WorkflowExtensions
{
get { return (WorkflowExtensionsCollection)base["extensions"]; }
}

}

This uses some other classes that are in the code download that read the appropriate information from the .config file. You then need to include some modifications to the .config file in order for this new behavior to be executed when reading the .config file…

  <system.serviceModel>

<
extensions
>
<behaviorExtensions
>
<
add name="workflowExtensions"
type="MSADC.Workflow.WorkflowExtensionsElement, MSADC.Workflow"
/>
</
behaviorExtensions
>
</
extensions>

This includes the extension such that we can then alter our behaviors element to include the list of extensions…

    <behaviors>
<
serviceBehaviors
>
<
behavior name=""
>
<
serviceMetadata httpGetEnabled="true"
/>
<
serviceDebug includeExceptionDetailInFaults="false"
/>
<
workflowExtensions
>
<
extensions
>
<
add name="logger"
extensionType="Demo.ActivityLogger, Demo"
/>
</
extensions
>
</
workflowExtensions
>

Extension Parameters


Now we have the ability to configure up an extension, how about additional parameters that the extension might need to use? Simple – the code that parses the <add> elements reads any additional attributes and pops these into a NameValueCollection, then during instantiation of the extension I check if the extension derives from the ProviderBase class. If so I simply call the Initialize() method, passing in the name of the provider and the NameValueCollection.


You could use this to pass in a database connection string name or other information – you just need to derive from ProviderBase and read the configuration parameters from the passed collection.


The Code


The download includes two projects, MSADC.Workflow and MSADC.Workflow.Common. I’ve refactored the code from my Hosting Dynamic Workflows example to move this into these libraries as there’s more to come and I need to keep this all in a sensible place. If you’re wondering about the name, MSADC is the shortened form of my company name (MS Application Development Consulting Ltd). I provide bespoke, deeply technical consulting on Windows Workflow 4 (and SIlverlight 4/5, WPF 4, WCF and lots of other parts of the .NET framework).


If you want some help from someone who knows what they’re talking about please get in touch!


The code is available as a NuGet package. Please click here to access the package. If you have any comments please contact me using the Contact Owners link from within NuGet.

Friday, 14 October 2011

Constraining Dropped Activities

In my last post I described making modifications to the standard FlowDecision activity to make it more usable, and mentioned there were a couple of modifications I needed to make to the code to make it more usable.

One is constraining the types of activity that can be dropped into the FlowDecision. Where I left off on that last post was something that looked as follows…

image

The problem here is at present I can drop anything into the FlowDecision – instead I need to constrain this to only activities that return a boolean result, i.e. something derived from Activity<Boolean>. I’m using the WorkflowItemPresenter in my designer and there’s a property on there named AllowedItemType which does just the job – however what I need to do is be able to pass a generic type into this type. A quick search around the web had something close, so here’s what I now have in the XAML…

    <sap:ActivityDesigner.Resources>
<
local:GenericType x:Key="mustReturnBool" BaseType="{x:Type sa:Activity`1}"
TypeParam="{x:Type sys:Boolean
}" />
</
sap:ActivityDesigner.Resources
>

<
sap:WorkflowItemPresenter Item="{Binding ModelItem.Condition}"
HintText="Drop a condition here" AllowedItemType="{StaticResource mustReturnBool
}"/>

So here I have a markup extension (local:GenericType) and have referenced this in the workflow item presenter. The markup extension defines the class we’re deriving from which is a generic type taking one argument so is defined as Activity`1 (if this took 3 arguments you would define this as Activity`3). I’ve then defined the type parameter as an instance of Boolean, and so at runtime this will create a type which is Activity<Boolean>.


Note: If you happen to cut & paste the line from the XAML that includes sa:Activity`1 this will (most annoyingly) be pasted back in as sa:Activity’1 – i.e. the grave character has been replaced with a single quote. So, beware!


The markup extension is shown below…

/// <summary>
///
A markup extension allowing generic types to be used in XAML
/// </summary>
public class GenericType :
MarkupExtension
{
/// <summary>
///
Defines the base type to derive from
/// </summary>
public Type BaseType { get; set; }

/// <summary>
///
Defines the collection of type parameters
/// </summary>
public Type[] TypeParams { get; set; }

/// <summary>
///
Defines the singular type parameter
/// </summary>
public Type TypeParam { get; set; }

/// <summary>
///
Default constructor
/// </summary>
public GenericType() { }

/// <summary>
///
Return the generic type constructed from the base Type and Type parameters
/// </summary>
/// <param name="serviceProvider"></param>
/// <returns></returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
Type retVal = null;

if (null != TypeParam)
retVal = BaseType.MakeGenericType(TypeParam);
else
retVal = BaseType.MakeGenericType(TypeParams);
return retVal;
}
}


With this in place I can try dragging any old activity onto the FlowDecision and now it won’t permit me to drop anything other than activities that derive from the correct type.


The Code


I’ve updated the code from the last article to include this new capability. Click here to download a .zip.

Thursday, 13 October 2011

Morphing Conditional Activities

I’m currently working on a workflow implementation which, like many, is going to be used by users who are not necessarily programmers. So the thought of giving them conditions to write in code is the kind of thing that keeps me up at night.

I’m using Flowchart as the root node as it provides the sort of UI that we need, however there are a few issues.

First off is the display of the FlowDecision node. The standard look and feel is as follows…

image

That’s all well and good, but I’m trying to keep this usable from non-programmers and so setting the Condition property (a) from the property grid and (b) in code is a step too far.

Re-Designing FlowDecision

So, the first step is to re-design this designer. It’s a simple job as with WF4 it’s a load easier to register a new designer. Here’s what I ended up with, the XAML and code is shown later…

image

That’s looking more user friendly already. The first step was to create a new designer, the XAML of which is shown below (I’ve taken some formatting liberties with the XAML to fit on the page)…

  <sap:ActivityDesigner x:Class="CustomActivities.CustomFlowDecisionDesigner"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap
="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv
="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">

<
sap:WorkflowItemPresenter Item="{Binding ModelItem.Condition}"
HintText
="Drop a condition here"/>

</
sap:ActivityDesigner
>

All I’ve done here is derive from ActivityDesigner and then create a WorkflowItemPresenter (there’s a bit more work to do on this but I’ll save that for another blog post).


So, now I want to have a ‘condition’ activity (that’s a naming convention I’ve come up with myself). A condition activity returns a boolean, so will fit nicely into the slot provided by FlowDecision. My first one is the “IsAPastyMuncher” activity (I’m from Cornwall in the UK, pasties are part of my heritage – for some great ones you can do a lot worse than order over the phone from Malcolm Barnecutts).


Anyhow, this activity returns True or False, and the default designer looks like this…


image


Now that’s OK when it’s on its own, but as this activity is designed to be dropped within the FlowDecision it doesn’t look as good in that instance…


image


Removing the ‘Chrome’


There’s a bit too much ‘chrome’ going on there for my liking – the reason being that a standard designer derives from ActivityDesigner which adds the adornments such as the border, name and so on. What I’d really like is ‘chrome-less’ experience in the designer, and to do that all you need to do is change the base class of your designer from ActivityDesigner to WorkflowViewElement. This is as simple as editing the XAML directly and altering the root element to the following…

<sap:WorkflowViewElement x:Class="CustomActivities.IsAPastyMuncherDesigner"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap
="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv
="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">

<
CheckBox Content="Munches Pasties" IsChecked="{Binding ModelItem
.MunchesPasties}"/>

</
sap:WorkflowViewElement
>

Then you’ll have a much better experience within the designer…


image


That’s looking a load better – maybe still some room for improvement mind you (again another post I must get around to) but it’s getting there. But now to the main event…


Morphing Activities


There is however one major problem with the IsAPastyMuncher activity as now, when added to the toolbox & dropped outside of a decision I see just the following…


image


Now that’s a bit too minimal even for me! What I would ideally like to do is check if this activity has been dropped inside a FlowDecision, and if not automatically surround it with one. Your first thought might be to dive in with an activity that implements IActivityTemplateFactory, create a FlowDecision and within it create the IsAPastyMuncher activity. There’s one problem with that – FlowDecision doesn’t derive from Activity and cannot therefore be returned from the custom factory.


Never fear however, where there’s a will there’s a way, and instead I’ve overridden a method within the IsAPastyMuncher activity that is called when the activity is created (i.e. by the drag & drop operation) and in here I can do the switch. The code is shown below…

  protected override void OnModelItemChanged(object newItem)
{
ConditionHelper.MorphIntoDecision(newItem);
}

I’ve created the ConditionHelper class which actually performs the swap…

internal class ConditionHelper
{
/// <summary>
///
Method that morphs a dropped activity inside a flow step into the same
///
activity but within a flow decision
/// </summary>
/// <param name="newItem"></param>
public static void MorphIntoDecision(object newItem)
{
if (newItem is ModelItem)
{
ModelItem item = newItem as ModelItem;
ModelItem parent = item.Parent;

if (parent.ItemType == typeof(FlowStep))
{
// Morph the parent to a flow decision
EditingContext context = item.GetEditingContext();
ModelItem newModelItem = ModelFactory.CreateItem(context, new FlowDecision());

using (ModelEditingScope scope = newModelItem.BeginEdit("Convert to Decision"))
{
MorphHelper.MorphObject(parent, newModelItem);
MorphHelper.MorphProperties(parent, newModelItem);

// Now I need to set newModelItem.Condition = parent.Action,
// so the MorphProperties helper is pretty useless in this instance!
ModelProperty conditionProp = newModelItem.Properties["Condition"];
ModelProperty actionProp = parent.Properties["Action"];
conditionProp.SetValue(actionProp.Value);
actionProp.SetValue(null);

scope.Complete();
}
}
}
}
}

When an activity other than FlowDecision is added to the flow chart activity it is surrounded with a FlowStep activity. I check if this is the case and if so construct a FlowDecision activity as the parent instead and morph the existing parent into the new activity (well, MorphHelper.MorphObject does this for me). Any properties on the original parent activity which match with property names on the new parent activity are copied across using the MorphHelper.MorphProperties call, but then I need to move the actual activity (i.e. the IsAPastyMuncher) from the FlowStep to the FlowDecision. On FlowStep the property holding the activity is Action, whereas on FlowDecision this is Condition, so I use the ModelProperty class to make this change.


Now I can drop on an IsAPastyMuncher activity and have it automatically surrounded with a FlowDecision, but also allow the user to drop one inside a FlowDecision that’s already on the design surface which is the best of both worlds.


The Code


If you would like to download a sample please click here. This is built on Visual Studio 2010.


In the example there are 2 custom activities on the toolbox – the IsAPastyMuncher which morphs inside a FlowDecision when dropped, and the LovesClottedCream activity which does not.