BusinessFailureScope activity with deep nested support

I wrote a series of posts late last year about a custom WF activity that collates a set of business failures that child evaluator activities identify into a single exception. At the time, the only way I could get the child evaluator activities to communicate failures to the parent scope activity was by using a custom WF extension to store the failures and manage the exception throwing logic.

The relationship between the parent scope activity and child evaluator activity works like this. The parent scope activity registers all the child evaluator activities with the extension in order to create a link between them. The extension then holds on to the failures from the child activities so that it can throw an exception for them as a set when the parent scope completes. If there was no link between the parent scope and a child evaluator then the extension would throw the exception directly for the singular failure.

One of the limitations of this design was that I could not create a link between a parent scope and child evaluator activity when the child activity was not a direct descendent. This was quite limiting because you could not branch off into validation checks and run evaluators within those sub-branches. You can see from the post that describes the extension that the implementation is also very messy.

If only I had known about Workflow Execution Properties back then. You can see a good description of execution properties in Tim’s post.

A great feature of execution properties in a workflow is that they are scoped to a specific sub-tree of the workflow structure. This is perfect for managing sets of failures between the parent scope and child evaluator activities. This method also allows for child evaluators to communicate failures to the parent scope from any depth of the workflow sub-tree.

image

namespace Neovolve.Toolkit.Workflow.Activities
{
    using System;
    using System.Collections.ObjectModel;
    using System.Runtime.Serialization;

    [DataContract]
    internal class BusinessFailureInjector<T> where T : struct
    {
        public BusinessFailureInjector()
        {
            Failures = new Collection<BusinessFailure<T>>();
        }

        public static String Name
        {
            get
            {
                return "BusinessFailureInjector" + typeof(T).FullName;
            }
        }

        [DataMember]
        public Collection<BusinessFailure<T>> Failures
        {
            get;
            set;
        }
    }
}

An instance of the BusinessFailureInjector<T> class is the value that is added to the parent scope’s execution context as an execution property. This class simply holds the failures that child evaluator activities find. One thing to notice about BusinessFailureInjector is the usage of DataContract and DataMember attributes. WF does all the heavy lifting for us with regard to persistence. The data held in the execution property automatically gets persisted and then restored for us. This was done manually in the old extension version as well as manually tracking the links between scopes and evaluators.

There are some minor changes to the code in the BusinessFailureScope and BusinessFailureEvaluator activities to work with the execution property rather than the extension.

The parent scope adds the execution property to its context when it is executed.

BusinessFailureInjector<T> injector = new BusinessFailureInjector<T>();

context.Properties.Add(BusinessFailureInjector<T>.Name, injector);

If the child activity can’t find the execution property then it throws the failure exception straight away for the single failure. If the execution property is found then it adds its failure to the collection of failures.

BusinessFailureInjector<T> injector = context.Properties.Find(BusinessFailureInjector<T>.Name) as BusinessFailureInjector<T>;

if (injector == null)
{
    throw new BusinessFailureException<T>(failure);
}

injector.Failures.Add(failure);

The parent scope then checks with the execution property to determine if there are any failures to throw in an exception.

private static void CompleteScope(NativeActivityContext context)
{
    BusinessFailureInjector<T> injector = context.Properties.Find(BusinessFailureInjector<T>.Name) as BusinessFailureInjector<T>;

    if (injector == null)
    {
        return;
    }

    if (injector.Failures.Count == 0)
    {
        return;
    }

    throw new BusinessFailureException<T>(injector.Failures);
}

Overall, the complexity of the code has been significantly reduced by this this method of inter-activity communication. In addition, child evaluators can now exist anywhere under a parent scope activity and still have their failures managed by the parent scope.

You can download the updated version of BusinessFailureScope in the latest beta of my Neovolve.Toolkit project out on CodePlex.

WF content correlation and security

I have posted previously about using content correlation in WF services to implement a service session. One issue that must be highlighted regarding content correlation is about the security of the session in relation to hijack attacks.

I am writing a workflow service that is a combination of IIS, WCF, WF, WIF and AppFabric. WIF is used to secure the WCF service to ensure that only authenticated users can hit the endpoint. WIF then handles claim demands raised depending on the actions taken within the service by the authenticated user. A session hijack can occur with content correlation where authenticated UserA starts the service and then authenticated UserB takes the content used for correlation and makes their own call against the service. In this case UserB is authenticated and passes through the initial WIF authentication. UserB could then potentially take actions or obtain data from the service related to UserA.

The way to protect the service against this session hijack attack is to hold on to the identity of the user that started the session. Each service call within the session should then validate the identity of the caller against the original identity. The service execution can continue if the identities match, otherwise a SecurityException should be thrown.

In my application, the StartSession service operation does this first part.

image

The StartSession service operation is the first for the session and (among other things) configures the service for content correlation. It uses my ReceiveIdentityInspector activity to obtain the identity of the user that is invoking the service. It then stores this identity in a workflow variable that is scoped in such a way that it is available to the entire lifecycle of the workflow.

Each other service operation then uses the same ReceiveIdentityInspector to get the identity of the user invoking those operations.

image

All these other service operations can then compare the two identities to protect the service against a hijack attack. The following condition is set against the Invalid Identity activity above:

ReadSegmentIdentity Is Nothing OrElse ReadSegmentIdentity.IsAuthenticated = False OrElse ReadSegmentIdentity.Name <> SessionIdentity.Name

A SecurityException is thrown if this condition is evaluated as True. An authenticated user is now unable to hijack the service of another user even if they can obtain the value used for content correlation.

Another security measure to protect the content correlation value (and all the data of the service) to use ensure that SSL is used to encrypt the traffic for the service. This should not however remove the requirement for the above security check. Additionally, you should also write integration tests that verify that this potential security hole is successfully managed by your service application.

Calling a workflow service operation multiple times

Ron Jacobs has just answered an interesting question over on his blog. The question is about whether a workflow service operation can be invoked multiple times. Ron does not provide the details of the question but the example he provides implies that the implementation of the two invocations of the same service operation may be different as the same operation name is implemented twice in the workflow. This seems like a design issue as far as the service goes but the question itself is still interesting.

If we assume that the implementation of the service operation is the same (as it should be), how do we keep the service alive so that we can invoke the same method multiple times?

The answer is by using some kind of service session using WF correlation. Content correlation is my preferred option because it is independent of infrastructure concerns and does not restrict the WCF bindings available to you. I have previously posted about how to get a workflow service to create a session using content correlation.

With respect to the question put to Ron, you would not be able to achieve this result with just one service operation on the service. Correlation requires the client to provide the correlation value to the service operation. The correlation value must then map to an existing WF instance. This means that the first service operation cannot be the service operation invoked multiple times. You will need a service operation that creates the service session by returning a session identifier that can then be used for content correlation on subsequent service operations. This first operation has the CanCreateInstance set to true and will be the entry point into the service. A DoWhile activity can then allow a service operation to be invoked multiple times within that session. The WF instance will remain alive (or persisted) until the workflow exists. The DoWhile activity prevents this from happening until some kind of exit condition is met.

I have implemented this design in a DataExchange service in my Jabiru project on CodePlex. The StartSession operation generates a Guid and returns it with some other service context information. The DoWhile then has a check for whether the session is completed. The session will be completed by one of the following conditions:

  • a timeout
  • CancelSession is called
  • FinishSession is called

This service design can be seen in the following screenshot (full image is linked).

image

The timeout case is handled in the first pick branch by using a Delay activity and then a check of the current time against when the service was last hit.

image

Each other pick branch has a service operation in it. The first action taken by each of these service operations is to set LastActivity = DateTime.Now() in order to prevent the timeout case. Each of these service operations (such as the ReadSegment operation) within the DoWhile can be invoked multiple times using the same session identifier while the SessionCompleted flag is False.

image

The CancelSession operation simply assigns SessionCompleted flag as True. This will then allow the DoWhile activity to exit and the service session will be finished as far as the client is concerned.

image

Similarly, the FinishSession operation sets the SessionCompleted flag as True and then does some other work relating to actioning the session outcome.

image

Note the Delay activity directly after the Reply activity. This allows the workflow to push the response back to the client and then process more work asynchronously. The same technique is used after the DoWhile so that session related resources on the server can be cleaned up asynchronously after the client has finished with the service.

We have seen here that a service operation can be invoked multiple times by using WF correlation and a DoWhile activity.

Custom DisposalScope activity

The previous post outlined the issues with working with unmanaged and IDisposable resources in WF. To recap, the issues with these resources in WF are:

  • Persistence
  • Restoration after persistence
  • Error handling
    • Messy WF experience to handle this correctly

A custom activity can handle these issues in a much more elegant way than the partially successful implementation in the previous post.

The design goals of this activity are:

  • take in a resource of a generic type (limited to IDisposable)
  • enforce a No Persist Zone to avoid the persistence issue
  • dispose the resource on successful execution
  • dispose the resource on a faulted child activity
  • as always, provide adequate designer support

The code implementation of the custom DisposalScope<T> activity handles these goals.

namespace Neovolve.Toolkit.Workflow.Activities
{
    using System;
    using System.Activities;
    using System.Activities.Presentation;
    using System.Activities.Statements;
    using System.Activities.Validation;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Drawing;
    using System.Windows;
    using System.Windows.Markup;
    using Neovolve.Toolkit.Workflow.Properties;

    [ToolboxBitmap(typeof(ExecuteBookmark), "bin_closed.png")]
    [DefaultTypeArgument(typeof(IDisposable))]
    [ContentProperty("Body")]
    public sealed class DisposalScope<T> : NativeActivity<T>, IActivityTemplateFactory where T : class, IDisposable
    {
        public DisposalScope()
        {
            NoPersistHandle = new Variable<NoPersistHandle>();
        }

        public Activity Create(DependencyObject target)
        {
            ActivityAction<T> body = new ActivityAction<T>
                                     {
                                         Handler = new Sequence(), 
                                         Argument = new DelegateInArgument<T>("instance")
                                     };

            return new DisposalScope<T>
                   {
                       Body = body
                   };
        }

        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            metadata.AddDelegate(Body);
            metadata.AddImplementationVariable(NoPersistHandle);

            RuntimeArgument instanceArgument = new RuntimeArgument("Instance", typeof(T), ArgumentDirection.In, true);

            metadata.Bind(Instance, instanceArgument);

            Collection<RuntimeArgument> arguments = new Collection<RuntimeArgument>
                                                    {
                                                        instanceArgument
                                                    };

            metadata.SetArgumentsCollection(arguments);

            if (Body == null || Body.Handler == null)
            {
                ValidationError validationError = new ValidationError(Resources.Activity_NoChildActivitiesDefined, true, "Body");

                metadata.AddValidationError(validationError);
            }
        }

        protected override void Execute(NativeActivityContext context)
        {
            NoPersistHandle noPersistHandle = NoPersistHandle.Get(context);

            noPersistHandle.Enter(context);

            T instance = Instance.Get(context);

            context.ScheduleAction(Body, instance, OnCompletion, OnFaulted);
        }

        private void DestroyInstance(NativeActivityContext context)
        {
            T instance = Instance.Get(context);

            if (instance == null)
            {
                return;
            }

            try
            {
                instance.Dispose();

                Instance.Set(context, null);
            }
            catch (ObjectDisposedException)
            {
                // Ignore this exception
            }
        }

        private void OnCompletion(NativeActivityContext context, ActivityInstance completedinstance)
        {
            DestroyInstance(context);

            NoPersistHandle noPersistHandle = NoPersistHandle.Get(context);

            noPersistHandle.Exit(context);
        }

        private void OnFaulted(NativeActivityFaultContext faultcontext, Exception propagatedexception, ActivityInstance propagatedfrom)
        {
            DestroyInstance(faultcontext);

            NoPersistHandle noPersistHandle = NoPersistHandle.Get(faultcontext);

            noPersistHandle.Exit(faultcontext);
        }

        [Browsable(false)]
        public ActivityAction<T> Body
        {
            get;
            set;
        }

        [DefaultValue((String)null)]
        [RequiredArgument]
        public InArgument<T> Instance
        {
            get;
            set;
        }

        private Variable<NoPersistHandle> NoPersistHandle
        {
            get;
            set;
        }
    }
}

The DisposalScope<T> activity enforces a no persist zone. Attempts at persistence by a child activity will result in throwing an exception. The resource is released on either a successful or fault outcome. There is some validation in the activity that ensures that a Body activity has been defined. The activity also uses the IActivityTemplateFactory to create the activity with a Sequence activity for its Body property when it is created on the WF designer.

The designer of the activity handles most of the design time experience.

<sap:ActivityDesigner x:Class="Neovolve.Toolkit.Workflow.Design.Presentation.DisposalScopeDesigner"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                      xmlns:s="clr-namespace:System;assembly=mscorlib"
                      xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
                      xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
                      xmlns:conv="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
                      xmlns:sadm="clr-namespace:System.Activities.Presentation.Model;assembly=System.Activities.Presentation"
                      xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
                      xmlns:ntw="clr-namespace:Neovolve.Toolkit.Workflow;assembly=Neovolve.Toolkit.Workflow"
                      xmlns:ntwd="clr-namespace:Neovolve.Toolkit.Workflow.Design">
    <sap:ActivityDesigner.Icon>
        <DrawingBrush>
            <DrawingBrush.Drawing>
                <ImageDrawing>
                    <ImageDrawing.Rect>
                        <Rect Location="0,0"
                              Size="16,16">
                        </Rect>
                    </ImageDrawing.Rect>
                    <ImageDrawing.ImageSource>
                        <BitmapImage UriSource="bin_closed.png"></BitmapImage>
                    </ImageDrawing.ImageSource>
                </ImageDrawing>
            </DrawingBrush.Drawing>
        </DrawingBrush>
    </sap:ActivityDesigner.Icon>
    <sap:ActivityDesigner.Resources>
        <conv:ModelToObjectValueConverter x:Key="modelItemConverter"
                                          x:Uid="sadm:ModelToObjectValueConverter_1" />
        <conv:ArgumentToExpressionConverter x:Key="expressionConverter" />
        <DataTemplate x:Key="Collapsed">
            <TextBlock HorizontalAlignment="Center"
                       FontStyle="Italic"
                       Foreground="Gray">
                Double-click to view
            </TextBlock>
        </DataTemplate>
        <DataTemplate x:Key="Expanded">
            <StackPanel Orientation="Vertical">
                
                    <StackPanel Orientation="Horizontal">
                        <sapv:TypePresenter Width="120"
                                        Margin="5"
                                        AllowNull="false"
                                        BrowseTypeDirectly="false"
                                        Filter="DisposalTypeFilter"
                                        Label="Target type"
                                        Type="{Binding Path=ModelItem.ArgumentType, Mode=TwoWay, Converter={StaticResource modelItemConverter}}"
                                        Context="{Binding Context}" />
                        <TextBox Text="{Binding ModelItem.Body.Argument.Name}"
                             MinWidth="80" />
                        <sapv:ExpressionTextBox Expression="{Binding Path=ModelItem.Instance, Mode=TwoWay, Converter={StaticResource expressionConverter}, ConverterParameter=In}"
                                            OwnerActivity="{Binding ModelItem, Mode=OneTime}"
                                            Margin="2" />
                    </StackPanel>

                <sap:WorkflowItemPresenter Item="{Binding ModelItem.Body.Handler}"
                                           HintText="Drop activity"
                                           Margin="6" />
            </StackPanel>
        </DataTemplate>
        <Style x:Key="ExpandOrCollapsedStyle"
               TargetType="{x:Type ContentPresenter}">
            <Setter Property="ContentTemplate"
                    Value="{DynamicResource Collapsed}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=ShowExpanded}"
                             Value="true">
                    <Setter Property="ContentTemplate"
                            Value="{DynamicResource Expanded}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </sap:ActivityDesigner.Resources>
    <Grid>
        <ContentPresenter Style="{DynamicResource ExpandOrCollapsedStyle}"
                          Content="{Binding}" />
    </Grid>
</sap:ActivityDesigner>

The designer uses a TypePresenter to allow modification of the generic type of the activity. The configuration of the TypePresenter uses the Filter property to restrict the types available to those that implement IDisposable. The designer users an ExpressionTextBox to provide the disposable resource to the activity. The expression can either instantiate the resource directly or provide it by referencing a variable in the parent workflow. Finally, the designer provides a WorkflowItemPresenter that allows designer interaction with the Body activity that gets executed by the activity.

namespace Neovolve.Toolkit.Workflow.Design.Presentation
{
    using System;
    using System.Diagnostics;
    using Neovolve.Toolkit.Workflow.Activities;

    public partial class DisposalScopeDesigner
    {
        [DebuggerNonUserCode]
        public DisposalScopeDesigner()
        {
            InitializeComponent();
        }

        public Boolean DisposalTypeFilter(Type typeToValidate)
        {
            if (typeToValidate == null)
            {
                return false;
            }

            if (typeof(IDisposable).IsAssignableFrom(typeToValidate))
            {
                return true;
            }

            return false;
        }

        protected override void OnModelItemChanged(Object newItem)
        {
            base.OnModelItemChanged(newItem);

            GenericArgumentTypeUpdater.Attach(ModelItem);
        }
    }
}

The code behind the designer provides the filter of the TypePresenter (see this post for details) and the designer support for modifying the generic type of the activity (see this post for details).

The example in the previous post can now be refactored to the following.

image

A workflow variable against held against a parent activity is no longer required to define the resource as this is now exposed directly by the DisposalScope activity. The variable that the activity provides is type safe via the generic definition as can be seen above with the ReadByte method for the FileStream type.

This is now a much cleaner solution as far as a design time experience goes and satisfies all the above design goals. 

You can download this activity in my latest Neovolve.Toolkit 1.1 Beta on the CodePlex site.

Working with unmanaged and IDisposable resources in WF

Generally speaking you will want to steer clear of unmanaged or IDisposable resources when authoring WF workflows. The primary reasons for this are:

  • Persistence
  • Restoration after persistence
  • Error handling

A workflow may be persisted at any time (with the exception of a No Persist Zone). There are two problems for unmanaged or IDisposable resources with regard to persistence. Firstly, the resource may not be released when the workflow is persisted. Secondly the state of the resource when the workflow is restored is unlikely to be the same as when the workflow was persisted. There is no native support in WF for dealing with these two issues.

A workflow may encounter an exception at any time. The workflow needs to handle this scenario at the appropriate scope to ensure that the resource is released correctly. There is an additional concern here in that disposing an IDisposable instance may throw an ObjectDisposedException. This should be caught and ignored as there is nothing that can be done in such a scenario and the desired outcome has been achieved anyway. While not a runtime concern, addressing the exception handling issue in the WF designer results in a very messy implementation.

Consider the following example.

image

This workflow creates a disposable resource and holds it in a workflow variable called Reader. The workflow makes this resource available to the child activities and then disposes it when the activities are finished. This workflow definition cannot handle the persistence and restoration issues outlined above. It can however make a best effort at error handling. The above activity creates the Stream and then uses a TryCatch around the usage of the resource.

image

The first TryCatch surrounds the resource usage and attempts to dispose the resource in the Finally section. In order to handle an ObjectDisposedException, a second TryCatch is required in the Finally of the first TryCatch.

image

The Catch section of the second TryCatch catches ObjectDisposedException and simply ignores it.

Overall this is a messy and incomplete solution. The next post will address this using a new custom WF activity.

Custom IfThen activity

The two most common WF activities to use when implementing decision branching are the If activity and the Flowchart activity. Using a Flowchart is overkill for a simple/singular decision branch so the If is often chosen. Unfortunately the If activity that comes with WF4 forces an Else branch to be visible on the designer even if you are not using it. This can make workflows unnecessarily cluttered.

image

The solution is to author a custom activity that only executes an If-Then branch rather than an If-Then-Else branch and provide appropriate designer support for this. A simplistic example of such an activity was provided in the January MSDNMag by Leon Welicki in his Authoring Control Flow Activities in WF 4 article.

I have created an IfThen activity that addresses this designer concern. The IfThen activity below executes its Body activity if the Condition value is true. It also includes some validation logic and a default activity structure provided via IActivityTemplateFactory.

namespace Neovolve.Toolkit.Workflow.Activities
{
    using System;
    using System.Activities;
    using System.Activities.Presentation;
    using System.Activities.Statements;
    using System.Activities.Validation;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.Windows;
    using System.Windows.Markup;
    using Neovolve.Toolkit.Workflow.Properties;

    [ToolboxBitmap(typeof(Retry), "arrow_branch.png")]
    [ContentProperty("Body")]
    public sealed class IfThen : NativeActivity, IActivityTemplateFactory
    {
        public IfThen()
        {
            Body = new ActivityAction();
        }

        [DebuggerNonUserCode]
        public Activity Create(DependencyObject target)
        {
            return new IfThen
                   {
                       Body =
                           {
                               Handler = new Sequence()
                           }
                   };
        }

        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            metadata.AddDelegate(Body);

            RuntimeArgument conditionArgument = new RuntimeArgument("Condition", typeof(Boolean), ArgumentDirection.In, true);

            metadata.Bind(Condition, conditionArgument);

            Collection<RuntimeArgument> arguments = new Collection<RuntimeArgument>
                                                    {
                                                        conditionArgument
                                                    };

            metadata.SetArgumentsCollection(arguments);

            if (Body == null
                || Body.Handler == null)
            {
                ValidationError validationError = new ValidationError(Resources.Activity_NoChildActivitiesDefined, true, "Body");

                metadata.AddValidationError(validationError);
            }
        }

        protected override void Execute(NativeActivityContext context)
        {
            Boolean condition = context.GetValue(Condition);

            if (condition == false)
            {
                return;
            }

            if (Body == null)
            {
                return;
            }

            context.ScheduleAction(Body);
        }

        [Browsable(false)]
        public ActivityAction Body
        {
            get;
            set;
        }

        public InArgument<Boolean> Condition
        {
            get;
            set;
        }
    }
}

The designer support provides the ability to define the Condition expression and the child activity to execute.

<sap:ActivityDesigner x:Class="Neovolve.Toolkit.Workflow.Design.Presentation.IfThenDesigner"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                      xmlns:s="clr-namespace:System;assembly=mscorlib"
                      xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
                      xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
                      xmlns:conv="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
                      xmlns:sadm="clr-namespace:System.Activities.Presentation.Model;assembly=System.Activities.Presentation"
                      xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
                      xmlns:ntw="clr-namespace:Neovolve.Toolkit.Workflow;assembly=Neovolve.Toolkit.Workflow"
                      xmlns:ntwd="clr-namespace:Neovolve.Toolkit.Workflow.Design">
    <sap:ActivityDesigner.Icon>
        <DrawingBrush>
            <DrawingBrush.Drawing>
                <ImageDrawing>
                    <ImageDrawing.Rect>
                        <Rect Location="0,0"
                              Size="16,16">
                        </Rect>
                    </ImageDrawing.Rect>
                    <ImageDrawing.ImageSource>
                        <BitmapImage UriSource="arrow_branch.png"></BitmapImage>
                    </ImageDrawing.ImageSource>
                </ImageDrawing>
            </DrawingBrush.Drawing>
        </DrawingBrush>
    </sap:ActivityDesigner.Icon>
    <sap:ActivityDesigner.Resources>
        <conv:ArgumentToExpressionConverter x:Key="expressionConverter" />
        <DataTemplate x:Key="Collapsed">
            <TextBlock HorizontalAlignment="Center"
                       FontStyle="Italic"
                       Foreground="Gray">
                Double-click to view
            </TextBlock>
        </DataTemplate>
        <DataTemplate x:Key="Expanded">
            <StackPanel Orientation="Vertical">
                <TextBlock HorizontalAlignment="Left"
                               VerticalAlignment="Center" Margin="3">
                        Condition
                </TextBlock>
                <sapv:ExpressionTextBox Expression="{Binding Path=ModelItem.Condition, Converter={StaticResource expressionConverter}}"
                                            ExpressionType="s:Boolean"
                                            OwnerActivity="{Binding ModelItem}" Margin="3" />
                <TextBlock HorizontalAlignment="Left"
                               VerticalAlignment="Center" Margin="3">
                        Then
                </TextBlock>
                <sap:WorkflowItemPresenter Item="{Binding ModelItem.Body.Handler}"
                                           HintText="Drop activity" Margin="3" />
            </StackPanel>
        </DataTemplate>
        <Style x:Key="ExpandOrCollapsedStyle"
               TargetType="{x:Type ContentPresenter}">
            <Setter Property="ContentTemplate"
                    Value="{DynamicResource Collapsed}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=ShowExpanded}"
                             Value="true">
                    <Setter Property="ContentTemplate"
                            Value="{DynamicResource Expanded}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </sap:ActivityDesigner.Resources>
    <Grid>
        <ContentPresenter Style="{DynamicResource ExpandOrCollapsedStyle}"
                          Content="{Binding}" />
    </Grid>
</sap:ActivityDesigner>

Using this new activity, the above example can now be redefined as the following.

image

As you can see here, the IfThen activity is able to streamline your workflow designer for those scenarios where you do not need an Else branch in your logic.

You can grab this activity from my Neovolve.Toolkit project out on CodePlex with the latest Neovolve.Toolkit 1.1 Beta release.

WF Retry activity

One of the tools missing out of the WF toolbox is the ability to run some retry logic. Applications often have known scenarios where something can go wrong such that a retry of the last action could get a successful outcome. One such example is a connection timeout to a database. You may want to try a couple of times before throwing the exception in order to get more success over time.

The specific scenario I am addressing is a little different. I have created some custom MSF providers that will allow me to run a MSF sync session behind a WCF service. The main issue with this design is that it is possible that multiple clients may want to sync the same data set at the same time. MSF stores the metadata (Replica) that describes the items to sync in a SQLCE file. It will throw an exception if the replica is already open and in use. This doesn’t work well in a services environment. The way I will attempt to manage this is to limit the amount of time the replica is in use and then implement some retry logic around opening the replica in order to run a sync.

The Retry activity shown below will execute its child activity and watch for a match on a nominated exception type or derivative. It will then determine whether it can make another attempt at the child activity by comparing the current number of attempts against a MaxAttempts property. If another attempt will be made then it will determine whether the child activity will be invoked immediately or whether a delay should occur first.

namespace Neovolve.Toolkit.Workflow.Activities
{
    using System;
    using System.Activities;
    using System.Activities.Presentation;
    using System.Activities.Statements;
    using System.Activities.Validation;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.Windows;
    using System.Windows.Markup;
    using Neovolve.Toolkit.Workflow.Properties;

    [ToolboxBitmap(typeof(Retry), "arrow_retry.png")]
    [ContentProperty("Body")]
    public sealed class Retry : NativeActivity, IActivityTemplateFactory
    {
        private static readonly TimeSpan _defaultRetryInterval = new TimeSpan(0, 0, 0, 1);

        private readonly Variable<Int32> _attemptCount = new Variable<Int32>();

        private readonly Variable<TimeSpan> _delayDuration = new Variable<TimeSpan>();

        private readonly Delay _internalDelay;

        public Retry()
        {
            _internalDelay = new Delay
                             {
                                 Duration = new InArgument<TimeSpan>(_delayDuration)
                             };
            Body = new ActivityAction();
            MaxAttempts = 5;
            ExceptionType = typeof(TimeoutException);
            RetryInterval = _defaultRetryInterval;
        }

        [DebuggerNonUserCode]
        public Activity Create(DependencyObject target)
        {
            return new Retry
                   {
                       Body =
                           {
                               Handler = new Sequence()
                           }
                   };
        }

        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            metadata.AddDelegate(Body);
            metadata.AddImplementationChild(_internalDelay);
            metadata.AddImplementationVariable(_attemptCount);
            metadata.AddImplementationVariable(_delayDuration);

            RuntimeArgument maxAttemptsArgument = new RuntimeArgument("MaxAttempts", typeof(Int32), ArgumentDirection.In, true);
            RuntimeArgument retryIntervalArgument = new RuntimeArgument("RetryInterval", typeof(TimeSpan), ArgumentDirection.In, true);

            metadata.Bind(MaxAttempts, maxAttemptsArgument);
            metadata.Bind(RetryInterval, retryIntervalArgument);

            Collection<RuntimeArgument> arguments = new Collection<RuntimeArgument>
                                                    {
                                                        maxAttemptsArgument, 
                                                        retryIntervalArgument
                                                    };

            metadata.SetArgumentsCollection(arguments);

            if (Body == null)
            {
                ValidationError validationError = new ValidationError(Resources.Retry_NoChildActivitiesDefined, true, "Body");

                metadata.AddValidationError(validationError);
            }

            if (typeof(Exception).IsAssignableFrom(ExceptionType) == false)
            {
                ValidationError validationError = new ValidationError(Resources.Retry_InvalidExceptionType, false, "ExceptionType");

                metadata.AddValidationError(validationError);
            }
        }

        protected override void Execute(NativeActivityContext context)
        {
            ExecuteAttempt(context);
        }

        private static Boolean ShouldRetryAction(Type exceptionType, Exception thrownException)
        {
            if (exceptionType == null)
            {
                return false;
            }

            return exceptionType.IsAssignableFrom(thrownException.GetType());
        }

        private void ActionFailed(NativeActivityFaultContext faultcontext, Exception propagatedexception, ActivityInstance propagatedfrom)
        {
            Int32 currentAttemptCount = _attemptCount.Get(faultcontext);

            currentAttemptCount++;

            _attemptCount.Set(faultcontext, currentAttemptCount);

            Int32 maxAttempts = MaxAttempts.Get(faultcontext);

            if (currentAttemptCount >= maxAttempts)
            {
                // There are no further attempts to make
                return;
            }

            if (ShouldRetryAction(ExceptionType, propagatedexception) == false)
            {
                return;
            }

            faultcontext.CancelChild(propagatedfrom);
            faultcontext.HandleFault();

            TimeSpan retryInterval = RetryInterval.Get(faultcontext);

            if (retryInterval == TimeSpan.Zero)
            {
                ExecuteAttempt(faultcontext);
            }
            else
            {
                // We are going to wait before trying again
                _delayDuration.Set(faultcontext, retryInterval);

                faultcontext.ScheduleActivity(_internalDelay, DelayCompleted);
            }
        }

        private void DelayCompleted(NativeActivityContext context, ActivityInstance completedinstance)
        {
            ExecuteAttempt(context);
        }

        private void ExecuteAttempt(NativeActivityContext context)
        {
            if (Body == null)
            {
                return;
            }

            context.ScheduleAction(Body, null, ActionFailed);
        }

        [Browsable(false)]
        public ActivityAction Body
        {
            get;
            set;
        }

        [DefaultValue(typeof(TimeoutException))]
        public Type ExceptionType
        {
            get;
            set;
        }

        public InArgument<Int32> MaxAttempts
        {
            get;
            set;
        }

        public InArgument<TimeSpan> RetryInterval
        {
            get;
            set;
        }
    }
}

The CacheMetadata method has some logic in it to validate the state of the activity. This validation will display a warning if no child activity has been defined. It will also raise an error if the ExceptionType property has not been assigned a type that derives from System.Exception. The activity implements IActivityTemplateFactory and uses the Create method to include a child Sequence activity when Retry is added to an activity on the designer.

The designer xaml of the Retry activity looks like the following.

<sap:ActivityDesigner x:Class="Neovolve.Toolkit.Workflow.Design.Presentation.RetryDesigner"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                      xmlns:s="clr-namespace:System;assembly=mscorlib"
                      xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
                      xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
                      xmlns:conv="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
                      xmlns:sadm="clr-namespace:System.Activities.Presentation.Model;assembly=System.Activities.Presentation"
                      xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
                      xmlns:ntw="clr-namespace:Neovolve.Toolkit.Workflow;assembly=Neovolve.Toolkit.Workflow"
                      xmlns:ntwd="clr-namespace:Neovolve.Toolkit.Workflow.Design">
    <sap:ActivityDesigner.Icon>
        <DrawingBrush>
            <DrawingBrush.Drawing>
                <ImageDrawing>
                    <ImageDrawing.Rect>
                        <Rect Location="0,0"
                              Size="16,16">
                        </Rect>
                    </ImageDrawing.Rect>
                    <ImageDrawing.ImageSource>
                        <BitmapImage UriSource="arrow_retry.png"></BitmapImage>
                    </ImageDrawing.ImageSource>
                </ImageDrawing>
            </DrawingBrush.Drawing>
        </DrawingBrush>
    </sap:ActivityDesigner.Icon>
    <sap:ActivityDesigner.Resources>
        <conv:ModelToObjectValueConverter x:Key="modelItemConverter"
                                          x:Uid="sadm:ModelToObjectValueConverter_1" />
        <DataTemplate x:Key="Collapsed">
            <TextBlock HorizontalAlignment="Center"
                       FontStyle="Italic"
                       Foreground="Gray">
                Double-click to view
            </TextBlock>
        </DataTemplate>
        <DataTemplate x:Key="Expanded">
            <StackPanel Orientation="Vertical">
                <Grid Name="contentGrid">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                    </Grid.RowDefinitions>

                    <TextBlock Grid.Row="0"
                               Grid.Column="0"
                               HorizontalAlignment="Left"
                               VerticalAlignment="Center">
                        Exception Type:
                    </TextBlock>
                    <sapv:TypePresenter HorizontalAlignment="Left"
                                        VerticalAlignment="Center"
                                        Margin="6"
                                        Grid.Row="0"
                                        Grid.Column="1"
                                        Filter="ExceptionTypeFilter"
                                        AllowNull="false"
                                        BrowseTypeDirectly="false"
                                        Label="Exception type"
                                        Type="{Binding Path=ModelItem.ExceptionType, Mode=TwoWay, Converter={StaticResource modelItemConverter}}"
                                        Context="{Binding Context}" />
                </Grid>
                <sap:WorkflowItemPresenter Item="{Binding ModelItem.Body.Handler}"
                                           HintText="Drop activity"
                                           Margin="6" />
            </StackPanel>
        </DataTemplate>
        <Style x:Key="ExpandOrCollapsedStyle"
               TargetType="{x:Type ContentPresenter}">
            <Setter Property="ContentTemplate"
                    Value="{DynamicResource Collapsed}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=ShowExpanded}"
                             Value="true">
                    <Setter Property="ContentTemplate"
                            Value="{DynamicResource Expanded}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </sap:ActivityDesigner.Resources>
    <Grid>
        <ContentPresenter Style="{DynamicResource ExpandOrCollapsedStyle}"
                          Content="{Binding}" />
    </Grid>
</sap:ActivityDesigner>

The designer supports the common expand/collapse style behaviour and allows for a single child activity via the WorkflowItemPresenter control. It also allows for modification of the activities ExceptionType property via the designer using the TypePresenter control. A recent post discusses the use of the Filter property on the TypePresenter to restrict the available types to be those derived from System.Exception.

image

The Retry activity will make as many attempts as it can to successfully execute the child activity. One thing to note about this however is that there is no concept of transactional support within the activity. Any workflow variables that have changed or any other side affects from the child activity will remain when it fails and is retried. For example, if the child activity increments a workflow variable or makes a change to the file system then these changes will remain in that state when the child activity is executed again. This must be taken into consideration when using this activity. The number of tasks actioned within the Retry should be as few as possible and should not alter any in-memory or persisted state.

Extract WCF identity into a WorkflowServiceHost activity

I came across an issue with the combination of WCF, WF, WIF and AppFabric in December that had me a little worried. The issue was how to get the identity of the user calling a WCF service inside a WorkflowServiceHost workflow when using WIF to manage security and AppFabric for WF persistence.

The WIF documentation says the following:

As a note, when WIF is enabled inside WCF, the WCF ServiceSecurityContext does not work for obtaining the caller’s identity and claims; the application code must use the IClaimsPrincipal to access the caller’s information. You can obtain the IClaimsPrincipal instance usingThread.CurrentPrincipal. This is available for every authentication type as soon as WIF is enabled for the WCF application.

I had been developing my service application using Thread.CurrentPrincipal based on this information. Everything was fine until I started using WF persistence. Unfortunately enabling persistence (via AppFabric) had the affect of wiping out Thread.CurrentPrincipal.

The requirement for persistence is so that the service can throw a fault back to the client and allow the workflow instance to remain alive (persisted) so that it can be called again. An example of this scenario is where a validation fault is thrown in the middle of an existing service session. The service needs to be persisted so that it can be called again by the client otherwise the session will be lost. So the question is how do we get the identity of the user when we have both WIF and WF persistence running a WCF service?

I put the question out on the MSDN forum at emailed a few of the prominent WF guru’s for help. Both Zulfiqar and Maurice have the answer to this riddle. It turns out that the WIF documentation is not entirely correct as the security information about the client is still available via ServiceSecurityContext.

I have created a custom WF activity that will make the calling identity available to the workflow. I wanted my activity to be more specific than the OperationContextScope activity available in the WF security pack on Codeplex in order for it to be intuitive and easy to use. This activity does however take the same implementation as the OperationContextScope activity.

The ReceiveIdentityInspector activity below hooks into the WCF pipeline using a private inspector class that implements IReceiveMessageCallback. This interface allows the inspector class to look at the client identity of the WCF service that has executed the Receive activity in the workflow service. The inspector instance is added as a property on the WF context so that it gets executed. The activity can then get the identity from the inspector and make it available to the parent workflow. The Body property of the activity is of type Receive as it only supports a Receive activity as its single child activity. This helps to make a nice drag\drop experience on the designer and prevent the user dragging inappropriate activities on to it.

namespace Neovolve.Toolkit.Workflow.Activities
{
    using System;
    using System.Activities;
    using System.Activities.Presentation;
    using System.Activities.Validation;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.Globalization;
    using System.Runtime.Serialization;
    using System.Security;
    using System.Security.Principal;
    using System.ServiceModel;
    using System.ServiceModel.Activities;
    using System.Windows.Markup;
    using Neovolve.Toolkit.Workflow.Properties;

    [ToolboxBitmap(typeof(ReceiveIdentityInspector<>), "user.png")]
    [ContentProperty("Body")]
    [DefaultTypeArgument(typeof(IIdentity))]
    public sealed class ReceiveIdentityInspector<T> : NativeActivity<T> where T : class, IIdentity
    {
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            if (Body == null)
            {
                ValidationError nullBodyFailure = new ValidationError(Resources.OperationIdentityInspector_NullBodyFailure, false, "Body");

                metadata.AddValidationError(nullBodyFailure);
            }

            metadata.AddChild(Body);
        }

        protected override void Execute(NativeActivityContext context)
        {
            if (Body == null)
            {
                return;
            }

            OperationIdentityInspector inspector = new OperationIdentityInspector();

            context.Properties.Add(inspector.GetType().FullName, inspector);

            context.ScheduleActivity(Body, OnBodyCompleted);
        }

        private void OnBodyCompleted(NativeActivityContext context, ActivityInstance instance)
        {
            OperationIdentityInspector inspector = context.Properties.Find(typeof(OperationIdentityInspector).FullName) as OperationIdentityInspector;

            Debug.Assert(inspector != null, "No inspector was found in the context properties");

            IIdentity operationIdentity = inspector.Identity;

            if (operationIdentity == null)
            {
                return;
            }

            T specificIdentity = operationIdentity as T;

            if (specificIdentity == null)
            {
                String message = String.Format(
                    CultureInfo.CurrentCulture, 
                    Resources.OperationIdentityInspector_UnexpectedIdentityType, 
                    typeof(T).FullName, 
                    operationIdentity.GetType().FullName);

                throw new InvalidCastException(message);
            }

            Result.Set(context, specificIdentity);
        }

        [DefaultValue((String)null)]
        [Browsable(false)]
        public Receive Body
        {
            get;
            set;
        }

        [DataContract]
        private class OperationIdentityInspector : IReceiveMessageCallback
        {
            public void OnReceiveMessage(OperationContext operationContext, ExecutionProperties activityExecutionProperties)
            {
                if (operationContext == null)
                {
                    return;
                }

                if (operationContext.ServiceSecurityContext == null)
                {
                    return;
                }

                Identity = operationContext.ServiceSecurityContext.PrimaryIdentity;
            }

            [DataMember]
            public IIdentity Identity
            {
                get;
                set;
            }
        }
    }
}

The designer of the activity allows for a child activity to be dragged and dropped onto it by using a WorkflowItemPresenter. It also supports the normal collapse/expand style behaviour.

<sap:ActivityDesigner x:Class="Neovolve.Toolkit.Workflow.Design.Presentation.ReceiveIdentityInspectorDesigner"
                      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:Activities="clr-namespace:System.ServiceModel.Activities;assembly=System.ServiceModel.Activities">
  <sap:ActivityDesigner.Icon>
    <DrawingBrush>
      <DrawingBrush.Drawing>
        <ImageDrawing>
          <ImageDrawing.Rect>
            <Rect Location="0,0" Size="16,16" ></Rect>
          </ImageDrawing.Rect>
          <ImageDrawing.ImageSource>
            <BitmapImage UriSource="user.png" ></BitmapImage>
          </ImageDrawing.ImageSource>
        </ImageDrawing>
      </DrawingBrush.Drawing>
    </DrawingBrush>
  </sap:ActivityDesigner.Icon>
    <sap:ActivityDesigner.Resources>
        <DataTemplate x:Key="Collapsed">
            <TextBlock HorizontalAlignment="Center"
                       FontStyle="Italic"
                       Foreground="Gray">
                Double-click to view
            </TextBlock>
        </DataTemplate>
        <DataTemplate x:Key="Expanded">
      <sap:WorkflowItemPresenter AllowedItemType="{x:Type Activities:Receive}" Item="{Binding ModelItem.Body}"
                                       HintText="Drop activity"
                                       Margin="6" />
        </DataTemplate>
        <Style x:Key="ExpandOrCollapsedStyle"
               TargetType="{x:Type ContentPresenter}">
            <Setter Property="ContentTemplate"
                    Value="{DynamicResource Collapsed}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=ShowExpanded}"
                             Value="true">
                    <Setter Property="ContentTemplate"
                            Value="{DynamicResource Expanded}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </sap:ActivityDesigner.Resources>
    <Grid>
        <ContentPresenter Style="{DynamicResource ExpandOrCollapsedStyle}"
                          Content="{Binding}" />
    </Grid>
</sap:ActivityDesigner>

The code behind the designer adds support for the generic IIdentity type of the activity to be modified using a previously posted helper class.

namespace Neovolve.Toolkit.Workflow.Design.Presentation
{
    using System;
    using System.Diagnostics;
    using Neovolve.Toolkit.Workflow.Activities;

    public partial class ReceiveIdentityInspectorDesigner
    {
        [DebuggerNonUserCode]
        public ReceiveIdentityInspectorDesigner()
        {
            InitializeComponent();
        }
        
        protected override void OnModelItemChanged(Object newItem)
        {
            base.OnModelItemChanged(newItem);

            GenericArgumentTypeUpdater.Attach(ModelItem);
        }
    }
}

The designer looks like the following. The ReceiveIdentityInspector<IIdentity> seen below contains a Receive activity as its only child activity.

image

The activity will expose the identity of the user calling the GetData service operation and make it available in its Result property.

image

In this example the user identity is stored in a workflow variable called CallingIdentity. The workflow can then refer to this variable when it needs to take any identity related action.

image

One side effect of this implementation is that the user identity will now automatically participate in workflow persistence because it is stored in a workflow variable. This can be useful for security validation of a workflow that simulates a service session using content correlation.

In the following example, a service has a StartSession operation that returns a System.Guid and must be the first operation invoked on the service. A service session is simulated as all other methods on the service accept this Guid value. Content correlation then ensures that correct workflow instance is associated with the subsequent service calls matching on the Guid.

The security scenario to cover is if another authenticated user hijacks the session by providing the correct Guid for the session of another user.

Persisting the identity that called the StartSession service operation in a high enough scope will make it available to the entire service session lifetime. In this case the ReceiveIdentityInspector surrounding StartSession will store the identity in a workflow variable called StartSessionIdentity. The workflow is then persisted after the StartSession service operation has completed and waits for the client to invoke the next service operation using the correct Guid value.

image

Another service operation in the workflow (GetItemMetadata) will also have a ReceiveIdentityInspector surrounding its Receive activity. The identity inspector will store the identity of this service operation in a workflow variable called GetItemMetadataIdentity. One of the first things this service operation must do is ensure that the user invoking the service operation is the same user who “owns” the service session.

image

The Invalid Identity activity seen above will run this security check by evaluating the following condition.

image

A SecurityException will be thrown if this condition is not met.

We have seen here how a custom activity can make it very easy to get the identity of a client calling a workflow service and then use that identity for further validation across the lifetime of the workflow. This activity can be found my Neovolve.Toolkit 1.1 project (in beta at time of posting) on CodePlex.

Restricting the types available in TypePresenter in WF designers

The TypePresenter control is the UI that the WF designer displays for selecting a type. It is often seen on generics based activities and the InvokeMethod activity.

image

This drop down list provides some common types and includes some of the types already found in the current activity context. Selecting “Browse for Types …” allows for all referenced types to be searched in a dialog.

image

Sometimes you don’t want the TypePresenter to provide every available type. The TypePresenter has a great feature that allows you to restrict the types it displays in this list and the associated “Browse for Types …” dialog. This is done by providing a Func<Type, Boolean> reference on the TypePresenter’s Filter property.

In my scenario, I want to restrict the types available to those that derive from System.Exception. The first step to achieve this is to make a reference to the filter method in the xaml of the activity designer.

<sapv:TypePresenter HorizontalAlignment="Left"
    VerticalAlignment="Center"
    Margin="6"
    Grid.Row="0"
    Grid.Column="1"
    Filter="ExceptionTypeFilter"
    AllowNull="false"
    BrowseTypeDirectly="false"
    Label="Exception type"
    Type="{Binding Path=ModelItem.ExceptionType, Mode=TwoWay, Converter={StaticResource modelItemConverter}}"
    Context="{Binding Context}" />

The code behind class of the designer must contain the method defined in the Filter property (ExceptionTypeFilter in this case). This method must take a Type parameter and return a Boolean in order to satisfy the Func<Type, Boolean> signature. The filter method related to the xaml above is the following.

public Boolean ExceptionTypeFilter(Type typeToValidate)
{
    if (typeToValidate == null)
    {
        return false;
    }

    if (typeof(Exception).IsAssignableFrom(typeToValidate))
    {
        return true;
    }

    return false;
}

The designer for this activity will now only display exception types in the TypePresenter.

image

The associated “Browse for Types …” dialog will also use this filter value.

image

Unfortunately the property grid will still use the default TypePresenter implementation.

image

I haven’t figured out a way to change this behaviour and I suspect that it is not possible.

The final piece of the puzzle is to address what happens when the developer selects an inappropriate type using the property grid. This is where activity validation using CacheMetadata comes into play.

protected override void CacheMetadata(NativeActivityMetadata metadata)
{
    metadata.AddDelegate(Body);
    metadata.AddImplementationChild(_internalDelay);
    metadata.AddImplementationVariable(_attemptCount);
    metadata.AddImplementationVariable(_delayDuration);

    RuntimeArgument maxAttemptsArgument = new RuntimeArgument("MaxAttempts", typeof(Int32), ArgumentDirection.In, true);
    RuntimeArgument retryIntervalArgument = new RuntimeArgument("RetryInterval", typeof(TimeSpan), ArgumentDirection.In, true);

    metadata.Bind(MaxAttempts, maxAttemptsArgument);
    metadata.Bind(RetryInterval, retryIntervalArgument);

    Collection<RuntimeArgument> arguments = new Collection<RuntimeArgument>
                                            {
                                                maxAttemptsArgument, 
                                                retryIntervalArgument
                                            };

    metadata.SetArgumentsCollection(arguments);

    if (Body == null)
    {
        ValidationError validationError = new ValidationError(Resources.Retry_NoChildActivitiesDefined, true, "Body");

        metadata.AddValidationError(validationError);
    }

    if (typeof(Exception).IsAssignableFrom(ExceptionType) == false)
    {
        ValidationError validationError = new ValidationError(Resources.Retry_InvalidExceptionType, false, "ExceptionType");

        metadata.AddValidationError(validationError);
    }
}

The validation at the end of this method checks for an inappropriate type. It is marked as an error so that the activity is not able to be executed in this state. For example, it the property grid is used to assign the type of System.String, the designer will display the following.

image

The workflow runtime will throw an InvalidWorkflowException if the activity is executed in this state.

We have seen here that we can restrict the types presented by TypePresenter and back this up with some activity validation.

LinkFixer BlogEngine.Net extension released

For a while now I have been battling issues with fully qualified urls in my blog posts and pages that link internally back to the blog. There are several scenarios that cause me grief in this regard:

  1. Adding an additional domain name

    Several years ago I registered a new shorter domain name and had both pointed at this blog. One issue with this is links in existing posts served on the new domain having a fully qualified reference using the old domain.

    I want all these links to be served as relative addresses.
  2. Using WLW to upload images and link to existing posts

    WLW is without a doubt a fantastic blog post editor. One issue I have with it however is that it uses fully qualified addresses (based on the blog account address) when it uploads an image for a post or links to an existing post within the blog.

    I want these fully qualified references to be saved as relative addresses.
  3. Using WLW when MetaWeblog API over SSL is required

    [BE] has this wonderful setting that forces MetaWeblog API calls to be over SSL.

    image

    This is great because the authentication mechanism for MetaWeblog is like email (POP) in that it uses username/password authentication in clear text over the wire. I don’t like the idea of someone grabbing my credentials when I save a post. In addition to WLW using fully qualified address (as mentioned in #2), enabling this setting causes WLW uses the https scheme for these addresses. The result of this is that the post is saved into [BE] using the fully qualified domain and the https scheme.

    I want these references to be (optionally) downgraded to http by making them relative to the current host and scheme.

    On a side note, check out www.cacert.org if you want a free SSL server certificate that supports wildcard names.
  4. Fully qualified http references to an image or link served from an https page

    This scenario usually happens because of occurrences of #2 above. Browsers tend to throw warnings if a page is served over https but has references to insecure resources. A reference to an image  using a fully qualified http address would be such a case.

    I want these internal references to be (optionally) upgraded to https references by making them relative to the current host and scheme.

So far I have manually updated each link back to an existing post as I write an entry in WLW in order to make it relative. This caters for most of the above issues but it is laborious and easily overlooked. Nothing can be done about images urls because those url values are injected into the post content when WLW saves the post. You would have to then edit the post manually online after posting and this is far from ideal. This also does not take care of fully qualified address already stored in the blog.

The birth of LinkFixer

This is where the LinkFixer extension comes into play. LinkFixer can run its logic whenever an entry is either saved to the blog or served to a client. It updates the src and href attributes of tags that link back to the same blog by making them relative addresses.

The first issue above is taken care of by configuring the extension with a set of alias domains that the blog uses. By default the extension will add the current host name and localhost as a domain aliases. You can specify a specific domain name with or without a host header (www.neovolve.com, something.neovolve.com or just neovolve.com for example) or you can specify a wildcard domain to take care of any host header (such as *.neovolve.com).

Note: This extension does not take custom ports into account and assumes the standard port 80 for http and port 443 for https.

The domain alias configuration of this site looks like the following.

image

The extension will update any url found in a post, page or comment to make them relative for these configured addresses according to the settings discussed below.

image

Enabling the Convert on saving setting will cause this extension to change blog entry contents when the entry is saved.

Enabling the Convert on serving setting will cause this extension to change blog entry contents when the entry is served to a client. This entry can be disabled for better site performance for new blogs or blogs where all existing post content has been checked for fully qualified addresses and removed any that are found.

Enabling the Convert to HTTPS setting will upgrade http url references in a blog entry when the current request scheme is https. This is enabled by default due to potential security risks in leaving a resource as unsecured.

Enabling the Convert to HTTP setting will downgrade https url references in a blog entry when the current request scheme is http. This is disabled by default due to potential security risks that this may have on a blogs content. I have enabled this setting on my installation as there is no url or image reference in any blog entry that explicitly requires https.

Installation

You can get this extension from here or download the latest set of [BE] extensions from here.