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.

Supporting optional mobile theming in [BE]

I posted a feature request on the [BE] CodePlex site a few weeks ago because I wanted the ability for mobile users to opt in and out of using the mobile theme. The primary reason for this was that I wanted to be able to access the admin features of my own blog from my phone from time to time. This was not easily possible using the standard mobile theme.

I figured it wouldn’t be too hard to implement so I bashed it out yesterday and pushed up a fork. The change has been accepted by rutr who added it to the [BE] codebase.

I modified the mobile theme to include a “Regular Site” link at the top of the page as highlighted below.

Each theme provided in the [BE] installation has also been updated to display a “Mobile Site” link in the most appropriate place for the theme. I’m using the fantastic Stardust theme which I have also modified in a similar manner as highlighted below.

image

I changed the header links in the stardust site.master page with the following:

<ul >
    <li <%=MenuClass("default.aspx")%>><a href="<%=Utils.AbsoluteWebRoot %>" rel="home"><%=Resources.labels.home %></a></li>
    <li <%=MenuClass("archive.aspx")%>><a href="<%=Utils.AbsoluteWebRoot %>archive.aspx"><%=Resources.labels.archive %></a></li>
    <li <%=MenuClass("contact.aspx")%>><a href="<%=Utils.AbsoluteWebRoot %>contact.aspx"><%=Resources.labels.contact %></a></li>
      <% if (Utils.IsMobile)
 { %>
      <li><blog:MobileThemeSwitch ID="MobileThemeSwitch1" runat="server" /></li>
      <%
 }
      %>
    <li <%=MenuClass("account/login.aspx")%>><a runat="server" id="aLogin" /></li>
</ul>

This change is similar to the changes made to the standard [BE] themes. The theme adds a new custom control to the page if the device is a mobile device. This custom control displays a link button that actions the theme switch.

You can check out this functionality by hitting this site with a mobile device.

WorkflowApplication throws ObjectDisposedException on ActivityContext with LINQ queries

I have written a simple WF4 activity that validates a WIF IClaimsIdentity has certain claims attached to it for a specific claim value. I wrote several unit tests for this activity to validate the implementation. The basic logic is to select a subset of claims that have a specified claim value using Where(). I then fall into a flow chart that validates whether there are any claims (Any() == false) and then whether there is only one claim (First() with a Count()) of a specified claim type that will not be accepted.

image[2]

Almost all of the tests failed with the following error:

Test method Neovolve.Jabiru.Server.Business.UnitTests.Activities.DemandAnyDataChangeClaimTests.DemandAnyDataChangeClaimThrowsExceptionWithManageOnlyClaimTest threw exception System.ObjectDisposedException, but exception System.Security.SecurityException was expected. Exception message: System.ObjectDisposedException: An ActivityContext can only be accessed within the scope of the function it was passed into.
Object name: 'System.Activities.ActivityContext'.

This seemed a little strange as the activity being tested is very simple. The only complexity is that the activity does use a few LINQ queries. Strangely one of the tests did pass and it was one that tested an identity without any claims. I then made a change to one of the other failed tests to remove it’s claim from the test identity and the test then passed. The most likely cause of the exception is the LINQ queries against the claims of the identity.

So this clearly seemed like a bug somewhere in WF. The first net search result turned up with this post on MSDN forums which does indicate a bug with WF and LINQ.

Andrew notes in the forum response that this is a bug that will not get fixed by RTM so it is probably the same issue I am having here. The forum makes some suggestions about how to get around this issue but I was hoping for a simple workaround.

I guessed that perhaps the issue might be due to LINQ queries against a particular IEnumerable<T> type. I then tried putting a ToList() onto the original Where query. Now all the tests pass as expected. This simple workaround is just changing the data type to IList for the other LINQ operations (Any(), First() and Count()) to operate on rather than the IEnumerable returned from Where().

Stardust theme update instructions for BlogEngine.Net 2.0

I had a couple of issues with the excellent Stardust theme I use on this site after upgrading to [BE] 2.0. The following are the changes I made to make the theme compatible.

  1. Post.IsCommentEnabled has been changed to Post.HasCommentEnabled. This appears in themes/stardust/PostView.ascx.
  2. Login.aspx has been moved into the Account folder. Modify the reference to this in themes/stardust/site.master in the code MenuClass("account/login.aspx")
  3. Similarly to #2, modify the reference to login.aspx in themes/stardust/site.master.cs in the two references to aLogin.HRef

All these three references need to be changed from login.aspx to account/login.aspx.

Neovolve.BlogEngine.Web 2.0 released

Along with the updates to my [BE] extensions, Neovolve.BlogEngine.Web 2.0 is now released.

Neovolve.BlogEngine.Web 2.0 contains an HTTP module that supports CS style urls (as of several years ago at least). There is no functional change in this release as it is simply a refresh against the latest [BE] 2.0 binaries.

You can download this release from here.