Updated CodeCampOz presentation abstract

The preparations for my CodeCampOz presentation have come a long way as has my understanding of the subject matter. I have tweaked my presentation abstract as a result. The new abstract is the following:

Not a WIF of federation

The Windows Identify Foundation (WIF) provides the latest Microsoft implementation for working in the claims-based identity space. WIF has particular strengths in providing federated security for systems that target users across multiple security domains, multiple credential types and multiple credential stores.

Developers of small systems may find it difficult to understand how WIF fits into their system design. Small systems tend to have their own security store, do not cross security domains and may not even run within an Active Directory managed domain. How do developers leverage claims-based security when they do not require federated security?

This session will provide a brief introduction to claims-based security and then look at how to implement WIF in ASP.Net and WCF applications without federation dependencies. It will then extend this to include a federation capable architecture.

Unit testing a workflow that relies on Thread.CurrentPrincipal.Identity.Name

This is a bit of a curly one. I have a workflow that is mostly abstracted from security concerns in that authentication and authorization logic has already been processed. The workflow does however need to get the name of the current user via Thread.CurrentPrincipal.Identity.Name. Unfortunately this returns an empty string when executing the workflow directly in a unit test.

The reason for the workflow not having access to the current principal is that workflows are executed on a new thread. The principal associated with the workflow thread is determined according to the PrincipalPolicy assigned to the AppDomain. By default the AppDomain will return an unauthenticated GenericPrincipal. See my Thread identity propagation post from a few years ago for the background information.

Fortunately there are two easy workarounds for this problem. Both workarounds make changes to the configuration of the AppDomain and how it manages the current principal. This will work because the workflow is executed within the same AppDomain as the unit test, just on a different thread.

These workarounds leverage the logic that Reflector shows in AppDomain.GetThreadPrincipal. This method gets called by Thread.CurrentPrincipal when the current principal is null. Fortunately this isn't a problem for executing a workflow as the workflow engine does not assign a principal for the new workflow thread.

internal IPrincipal GetThreadPrincipal()
{
    IPrincipal principal = null;
    IPrincipal principal2;
    lock (this)
    {
        if (this._DefaultPrincipal == null)
        {
            switch (this._PrincipalPolicy)
            {
                case PrincipalPolicy.UnauthenticatedPrincipal:
                    principal = new GenericPrincipal(new GenericIdentity("", ""), new string[] { "" });
                    goto Label_0073;

                case PrincipalPolicy.NoPrincipal:
                    principal = null;
                    goto Label_0073;

                case PrincipalPolicy.WindowsPrincipal:
                    principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
                    goto Label_0073;
            }
            principal = null;
        }
        else
        {
            principal = this._DefaultPrincipal;
        }
    Label_0073:
        principal2 = principal;
    }
    return principal2;
}

1. Change the AppDomain PrincipalPolicy

// Configure the app domain to put the current windows credential into the thread when Thread.CurrentPrincipal is invoked
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

Changing the PrincipalPolicy on the AppDomain will result in the current WindowsIdentity being returned if the thread did not already have a principal assigned.

There are a few caveats for this workaround to be aware of:

  • Thread.CurrentPrincipal will end up with the current users WindowsPrincipal.
  • No flexibility for other principal types
  • No flexibility for custom principal values

2. Assign a default principal against the AppDomain

Changing the AppDomain PrincipalPolicy will work however the outcome locks you into testing against the current WindowsIdentity which is not ideal. The AppDomain class also has the ability to define a default principal. This overrides the PrincipalPolicy as seen in the above GetThreadPrincipal method. Assigning the principal in this way provides a lot more control over the principal that is used in the workflow thread.

AppDomain.CurrentDomain.SetThreadPrincipal(newPrincipal);

There is a caveat for this workaround as well. The default principal for the app domain can only be set once. You will need to have all your tests running against the same principal to avoid any issues.

Clean up and test method usage

It is important to clean up any changes made when the test either completes or fails. This is where a handle context/scope style class comes into play.

namespace Neovolve.Jabiru.Server.TestSupport
{
    using System;
    using System.Security.Policy;
    using System.Security.Principal;
    using System.Threading;

    public class TestUserContext : IDisposable
    {
        private readonly IPrincipal _originalPrincipal;

        public TestUserContext(IPrincipal newPrincipal)
        {
            _originalPrincipal = Thread.CurrentPrincipal;

            AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.NoPrincipal);

            Thread.CurrentPrincipal = null;

            IPrincipal defaultAppDomainPrincipal = Thread.CurrentPrincipal;

            AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.UnauthenticatedPrincipal);

            if (defaultAppDomainPrincipal != null)
            {
                // Check if the app domain default principal has been set to another principal
                const String AppDomainHasAlreadyBeenAssignedADifferentPrincipal = "App domain has already been assigned a different principal";

                if (newPrincipal.GetType().Equals(defaultAppDomainPrincipal.GetType()) == false)
                {
                    throw new PolicyException(AppDomainHasAlreadyBeenAssignedADifferentPrincipal);
                }

                if (newPrincipal.Identity.GetType().Equals(defaultAppDomainPrincipal.Identity.GetType()) == false)
                {
                    throw new PolicyException(AppDomainHasAlreadyBeenAssignedADifferentPrincipal);
                }

                if (newPrincipal.Identity.Name != defaultAppDomainPrincipal.Identity.Name)
                {
                    throw new PolicyException(AppDomainHasAlreadyBeenAssignedADifferentPrincipal);
                }
            }
            else
            {
                AppDomain.CurrentDomain.SetThreadPrincipal(newPrincipal);
            }

            Thread.CurrentPrincipal = newPrincipal;
        }

        public void Dispose()
        {
            Thread.CurrentPrincipal = _originalPrincipal;
        }
    }
}

This class will configure the AppDomain to use a provided principal. It will also attempt to detect if the app domain is already configured with a different default principal. The Dispose method will then restore the original principal back onto the thread.

using (TestUsers.DefaultUser.CreateContext())
{
    IDictionary<String, Object> outputParameters = ActivityInvoker.Invoke(target, inputParameters);

    itemSetId = (Guid)outputParameters["ItemSetId"];
}

The unit test method can then use this context in a using statement with the help of some nice syntactic sugar provided by extension methods. The using statement here will ensure that the current thread is restored by TestUserContext.Dispose regardless of whether an exception is thrown.

Updated: Found issue with AppDomain default principal only being allowed to be set once. Updated TestUserContext to attempt to cater for this.

Extension methods that add fluent elegance

Last night I was updating some integration tests for one of my projects. I have many test methods that configure a directory path from several sources of information and found that path concatenation often results in ugly unreadable code.

Consider the scenario where you have a base path to which you want to add several other directory names.

namespace ConsoleApplication1
{
    using System;
    using System.IO;

    class Program
    {
        static void Main(String[] args)
        {
            String basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            const String ProductName = "SomeProductName";
            String instanceName = Guid.NewGuid().ToString();
            const String dataName = "DataStore";

            String hardToReadEvaluation = Path.Combine(Path.Combine(Path.Combine(basePath, ProductName), instanceName), dataName);

            Console.WriteLine(hardToReadEvaluation);
            Console.ReadKey();
        }
    }
}

The statement with multiple Path.Combine evaluations is very difficult to read. A simple extension method can turn this into a fluent API design to achieve the same result and allow the intention of the code to be crystal clear.

namespace ConsoleApplication1
{
    using System;
    using System.IO;

    public static class Extensions
    {
        public static String AppendPath(this String basePath, String appendValue)
        {
            return Path.Combine(basePath, appendValue);
        }
    }

    class Program
    {
        static void Main(String[] args)
        {
            String basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            const String ProductName = "SomeProductName";
            String instanceName = Guid.NewGuid().ToString();
            const String dataName = "DataStore";

            String fluentEvaluation = basePath.AppendPath(ProductName).AppendPath(instanceName).AppendPath(dataName);

            Console.WriteLine(fluentEvaluation);
            Console.ReadKey();
        }
    }
}

The code is now much more readable. You no longer need to backtrack along the line of code to figure out which variables related to which operation in the evaluation as the code now reads fluently from left to right.

Configuration support for custom IErrorHandler in WCF

My post about implementing IErrorHandler for WCF a few years ago is my second top post on this site. My Toolkit project on Codeplex has had support for hooking up an IErrorHandler using an attribute on the service implementation class which is an extension of the original post.

My preference has always been to hook up IErrorHandler using an attribute to avoid any potential security holes. This would be a scenario where the configuration for IErrorHandler is removed and exception shielding is no longer available to prevent potentially sensitive information from being displayed to clients. I am now playing with workflow services and am not able to use an attribute for this purpose. I no longer have a choice and must use a configuration based IErrorHandler implementation.

I have added configuration support for IErrorHandler to my Toolkit project based on the original posts above to assist with this process.

namespace Neovolve.Toolkit.Communication
{
    using System;
    using System.Configuration;
    using System.ServiceModel.Configuration;

    public class ErrorHandlerElement : BehaviorExtensionElement
    {
        public const String ErrorHandlerTypeAttributeName = "type";

        private ConfigurationPropertyCollection _properties;

        protected override Object CreateBehavior()
        {
            return new ErrorHandlerAttribute(ErrorHandlerType);
        }

        public override Type BehaviorType
        {
            get
            {
                return typeof(ErrorHandlerAttribute);
            }
        }

        [ConfigurationProperty(ErrorHandlerTypeAttributeName)]
        public String ErrorHandlerType
        {
            get
            {
                return (String)base[ErrorHandlerTypeAttributeName];
            }

            set
            {
                base[ErrorHandlerTypeAttributeName] = value;
            }
        }

        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                if (_properties == null)
                {
                    ConfigurationPropertyCollection properties = new ConfigurationPropertyCollection();

                    properties.Add(
                        new ConfigurationProperty(
                            ErrorHandlerTypeAttributeName, typeof(String), String.Empty, null, null, ConfigurationPropertyOptions.IsRequired));

                    _properties = properties;
                }

                return _properties;
            }
        }
    }
}

The ErrorHandlerElement class allows for WCF configuration to configure an IErrorHandler for a service. This class provides the configuration support to define the type of error handler to use. The CreateBehavior method simply forwards the configured IErrorHandler type to the ErrorHandlerAttribute class that is already in the toolkit.

<?xml version="1.0" ?>
<configuration>
    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="ErrorHandlerBehavior">
                    <errorHandler type="Neovolve.Toolkit.IntegrationTests.Communication.KnownErrorHandler, Neovolve.Toolkit.IntegrationTests"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <extensions>
            <behaviorExtensions>
                <add name="errorHandler"
                     type="Neovolve.Toolkit.Communication.ErrorHandlerElement, Neovolve.Toolkit"/>
            </behaviorExtensions>
        </extensions>
        <services>
            <service behaviorConfiguration="ErrorHandlerBehavior"
                     name="Neovolve.Toolkit.IntegrationTests.Communication.TestService">
                <endpoint address=""
                          binding="basicHttpBinding"
                          bindingConfiguration=""
                          contract="Neovolve.Toolkit.IntegrationTests.Communication.ITestService"/>
            </service>
        </services>
    </system.serviceModel>
</configuration> 

The WCF configuration defines the ErrorHandlerElement class as a behavior extension. This service behavior can then use this extension and identify the type of IErrorHandler to hook into the service.

This class has been added into the next beta of my Toolkit. You can get it from here.

Custom IssuerNameRegistry to reduce WIF team development pain

I have been implementing WIF into my hosted synchronization project over recent months. One of the issues that I keep hitting with WIF is managing STS certificates between multiple machines. In my case I have a desktop and a laptop that I use for development. The same issue outlined here applies to working in a development team.

The WIF SDK makes it easy to get up and running with an STS. The wizard application creates an STS project and development certificates that are then integrated into your [VS] solution. The certificates are created on the local machine and are specific to that machine. One is the signing certificate with the default name of STSTestCert and the other is the encrypting certificate with the default name of DefaultApplicationCertificate.

The WIF configuration usually refers to these certificates using the subject distinguished name of the certificate. This will work where multiple development machines use certificates with the same subject where those certificates where created on each machine. Unfortunately the configuration for the Relying Party application identifies the trusted issuer certificate using a thumbprint. This thumbprint will be different across each machine.

<service name="Neovolve.Jabiru.Server.Service.ExchangeSession">
    <audienceUris>
        <add value="https://localhost/Jabiru/DataExchange.svc" />
    </audienceUris>
    <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <trustedIssuers>
            <!-- This is the thumbprint for the certificate used sign the certificate - STSTestCert -->
            <add thumbprint="3E8D41EA2AF035D352D07FE46AACE352AD2F32B0"
                 name="https://localhost/JabiruSts/Service.svc" />
        </trustedIssuers>
    </issuerNameRegistry>
</service>

The above configuration is for one of the services in my project. The configuration identifies the trusted issuer using a specific certificate thumbprint.

One solution to this issue is to copy the certificates around each development machine. I’m not a fan of this solution as trying to use local certificates on other machines is problematic. My preference is to have each development machine generate their own certificates using the same subject names. If required this functionality could be easily wrapped up in a batch file that is part of the solution. This method of working with local certificates is the same as a development team getting IIS on each workstation to create self-signed certificates with the same name (localhost for example).

There is an alternative solution however as WIF provides an extensibility point that allows for a different implementation. The type attribute of the issuerNameRegistry node in the above configuration above identifies the type that provides the IssuerNameRegistry class for the service. There is only one implementation provided with WIF which is ConfigurationBasedIssuerNameRegistry. This class is hard-coded to only deal with certificate thumbprints. Creating a type that can handle more certificate matching options using X509FindType will be the answer to this restriction.

namespace Neovolve.Jabiru.Server.Security
{
    using System;
    using System.Security.Cryptography.X509Certificates;

    public struct IssuerCertificateMapping
    {
        public X509FindType FindType;

        public String FindValue;

        public String IssuerName;
    }
}

The IssuerCertificateMapping struct will define the relationship between an issuer name and the certificate matching criteria. The ConfiguredCertificateIssuerNameRegistry class will read the issuer configuration and provide the logic for matching against the security token certificate when the GetIssuerName is invoked.

namespace Neovolve.Jabiru.Server.Security
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Globalization;
    using System.IdentityModel.Tokens;
    using System.Security.Cryptography.X509Certificates;
    using System.Xml;
    using Microsoft.IdentityModel.Tokens;
    using Neovolve.Jabiru.Server.Security.Properties;

    public class ConfiguredCertificateIssuerNameRegistry : IssuerNameRegistry
    {
        private readonly List<IssuerCertificateMapping> _trustedIssuers = new List<IssuerCertificateMapping>();

        public ConfiguredCertificateIssuerNameRegistry()
        {
        }

        public ConfiguredCertificateIssuerNameRegistry(XmlNodeList customConfiguration)
        {
            if (customConfiguration == null)
            {
                throw new ArgumentNullException("customConfiguration");
            }

            for (Int32 index = 0; index < customConfiguration.Count; index++)
            {
                XmlNode node = customConfiguration[index];

                if (node.Name != "trustedIssuers")
                {
                    continue;
                }

                LoadIssueConfiguration(node);
            }
        }

        public override String GetIssuerName(SecurityToken securityToken)
        {
            if (securityToken == null)
            {
                throw new ArgumentNullException("securityToken");
            }

            X509SecurityToken token = securityToken as X509SecurityToken;

            if (token == null)
            {
                return null;
            }

            X509Certificate2 tokenCertificate = token.Certificate;

            if (tokenCertificate == null)
            {
                return null;
            }

            for (Int32 index = 0; index < TrustedIssuers.Count; index++)
            {
                IssuerCertificateMapping mapping = TrustedIssuers[index];

                if (CertificateMatchesConfigurationItem(tokenCertificate, mapping))
                {
                    return mapping.IssuerName;
                }
            }

            return null;
        }

        private static Boolean CertificateMatchesConfigurationItem(X509Certificate2 tokenCertificate, IssuerCertificateMapping mapping)
        {
            String findValue = mapping.FindValue;

            switch (mapping.FindType)
            {
                case X509FindType.FindByThumbprint:

                    return tokenCertificate.Thumbprint == findValue;

                case X509FindType.FindBySubjectName:

                    return StripToCommonName(tokenCertificate.SubjectName) == findValue;

                case X509FindType.FindBySubjectDistinguishedName:

                    return tokenCertificate.Subject == findValue;

                case X509FindType.FindByIssuerName:

                    return StripToCommonName(tokenCertificate.IssuerName) == findValue;

                case X509FindType.FindByIssuerDistinguishedName:

                    return tokenCertificate.IssuerName.Name == findValue;

                case X509FindType.FindBySerialNumber:

                    return tokenCertificate.SerialNumber == findValue;

                    // case X509FindType.FindByTimeValid:
                    // break;
                    // case X509FindType.FindByTimeNotYetValid:
                    // break;
                    // case X509FindType.FindByTimeExpired:
                    // break;
                    // case X509FindType.FindByTemplateName:
                    // break;
                    // case X509FindType.FindByApplicationPolicy:
                    // break;
                    // case X509FindType.FindByCertificatePolicy:
                    // break;
                    // case X509FindType.FindByExtension:
                    // break;
                    // case X509FindType.FindByKeyUsage:
                    // break;
                    // case X509FindType.FindBySubjectKeyIdentifier:
                    // break;
                default:
                    throw new NotSupportedException();
            }
        }

        private static Nullable<IssuerCertificateMapping> ConvertNodeToCertificateConfiguration(XmlNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }

            if (node.Attributes == null)
            {
                return null;
            }

            XmlNode findTypeNode = node.Attributes.GetNamedItem("findType");

            if (findTypeNode == null)
            {
                throw new ConfigurationErrorsException(Resources.ConfiguredCertificateIssuerNameRegistry_FindTypeNotConfigured);
            }

            String configuredFindType = findTypeNode.Value;
            X509FindType findType;

            if (Enum.TryParse(configuredFindType, out findType) == false)
            {
                String message = String.Format(
                    CultureInfo.CurrentCulture, Resources.ConfiguredCertificateIssuerNameRegistry_InvalidFindTypeConfigured, configuredFindType);

                throw new ConfigurationErrorsException(message);
            }

            XmlNode findValueNode = node.Attributes.GetNamedItem("findValue");

            if (findValueNode == null)
            {
                throw new ConfigurationErrorsException(Resources.ConfiguredCertificateIssuerNameRegistry_FindValueNotConfigured);
            }

            String findValue = findValueNode.Value;

            if (String.IsNullOrWhiteSpace(findValue))
            {
                throw new ConfigurationErrorsException(Resources.ConfiguredCertificateIssuerNameRegistry_FindValueConfigurationIsEmpty);
            }

            XmlNode nameNode = node.Attributes.GetNamedItem("name");

            if (nameNode == null)
            {
                throw new ConfigurationErrorsException(Resources.ConfiguredCertificateIssuerNameRegistry_NameNotConfigured);
            }

            String name = nameNode.Value;

            if (String.IsNullOrWhiteSpace(name))
            {
                throw new ConfigurationErrorsException(Resources.ConfiguredCertificateIssuerNameRegistry_NameConfigurationIsEmpty);
            }

            return new IssuerCertificateMapping
                   {
                       FindType = findType, 
                       FindValue = findValue, 
                       IssuerName = name
                   };
        }

        private static String StripToCommonName(X500DistinguishedName distinguishedName)
        {
            String name = distinguishedName.Name;

            if (String.IsNullOrWhiteSpace(name))
            {
                return null;
            }

            Int32 commonNameIndex = name.LastIndexOf("CN=");

            if (commonNameIndex < 0)
            {
                return null;
            }

            commonNameIndex += 3;

            if (commonNameIndex > name.Length)
            {
                return null;
            }

            return name.Substring(commonNameIndex);
        }

        private void LoadIssueConfiguration(XmlNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }

            foreach (XmlNode childNode in node.ChildNodes)
            {
                if (childNode.Name == "clear")
                {
                    TrustedIssuers.Clear();
                }
                else
                {
                    Nullable<IssuerCertificateMapping> issuerCertificateConfiguration = ConvertNodeToCertificateConfiguration(childNode);

                    if (issuerCertificateConfiguration == null)
                    {
                        continue;
                    }

                    if (childNode.Name == "add")
                    {
                        TrustedIssuers.Add(issuerCertificateConfiguration.Value);
                    }
                    else if (childNode.Name == "remove")
                    {
                        TrustedIssuers.Remove(issuerCertificateConfiguration.Value);
                    }
                }
            }
        }

        public List<IssuerCertificateMapping> TrustedIssuers
        {
            get
            {
                return _trustedIssuers;
            }
        }
    }
}

The ConfiguredCertificateIssuerNameRegistry parses the RP configuration to search for add, remove and clear elements. The add and remove elements then need to define the findType (X509FindType), findValue and name attributes. The set of trusted issues becomes the reference point for mapping security token certificates to issuer names.

This class allows for a more flexible mapping between issuer names and certificates. The example configuration above can then be modified to use the subject name instead of thumbprint.

<service name="Neovolve.Jabiru.Server.Service.ExchangeSession">
    <audienceUris>
        <add value="https://localhost/Jabiru/DataExchange.svc" />
    </audienceUris>
    <issuerNameRegistry type="Neovolve.Jabiru.Server.Security.ConfiguredCertificateIssuerNameRegistry, Neovolve.Jabiru.Server.Security">
        <trustedIssuers>
            <add findType="FindBySubjectDistinguishedName"
                findValue="CN=STSTestCert"
                name="https://localhost/JabiruSts/Service.svc" />
        </trustedIssuers>
    </issuerNameRegistry>
</service>

The RP can then correctly identify issuer certificates created with the same subject on different machines.

Custom Workflow activity for business failure evaluation–Wrap up

My latest series on custom WF activities has provided a solution for managing business failures.

The BusinessFailureEvaluator<T> activity evaluates a single business failure and may result in an exception being thrown.

The BusinessFailureScope<T> activity manages multiple business failures and may result in an exception being thrown for a set of failures.

The following is a list of all the posts in this series.

This workflow activity and several others can be found in my Neovolve.Toolkit project (in 1.1 Beta with latest source code here) on CodePlex.

Custom Workflow activity for business failure evaluation–Part 6

The previous post in this series provided the custom activity that manages multiple business failures in WF. Providing adequate designer support was one of the design goals of this series. This post will outline the designer support for the BusinessFailureEvaluator<T> and BusinessFailureScope<T> activities.

BusinessFailureEvaluator<T>

The BusinessFailureEvaluator<T> evaluates a single business failure. There is no support for child activities which makes the designer very simple.

<sap:ActivityDesigner x:Class="Neovolve.Toolkit.Workflow.Design.Presentation.BusinessFailureEvaluatorDesigner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
  <sap:ActivityDesigner.Icon>
    <DrawingBrush>
      <DrawingBrush.Drawing>
        <ImageDrawing>
          <ImageDrawing.Rect>
            <Rect Location="0,0" Size="16,16" ></Rect>
          </ImageDrawing.Rect>
          <ImageDrawing.ImageSource>
            <BitmapImage UriSource="shield.png" ></BitmapImage>
          </ImageDrawing.ImageSource>
        </ImageDrawing>
      </DrawingBrush.Drawing>
    </DrawingBrush>
  </sap:ActivityDesigner.Icon>
</sap:ActivityDesigner>
The XAML for the designer simply identifies the image to use for the activity on the design surface.

The activity has a generic type argument that defaults to Int32 when the activity is dropped onto the designer. This type is not always suitable for the purposes of the application so the generic type argument needs to be updatable.

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

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

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

            GenericArgumentTypeUpdater.Attach(ModelItem);
        }
    }
}

The code behind the designer supports this by attaching an ArgumentType property to the ModelItem when it is assigned to the designer.

This attached property allows the generic type of the activity to be changed to another type.

BusinessFailureScope<T>

The BusinessFailureScope<T> allows for multiple business failures to be stored against the scope so that they can be thrown together rather than one at a time.

<sap:ActivityDesigner x:Class="Neovolve.Toolkit.Workflow.Design.Presentation.BusinessFailureScopeDesigner"
    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:sacdt="clr-namespace:System.Activities.Core.Presentation.Themes;assembly=System.Activities.Core.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
  <sap:ActivityDesigner.Icon>
    <DrawingBrush>
      <DrawingBrush.Drawing>
        <ImageDrawing>
          <ImageDrawing.Rect>
            <Rect Location="0,0" Size="16,16" ></Rect>
          </ImageDrawing.Rect>
          <ImageDrawing.ImageSource>
            <BitmapImage UriSource="shield_go.png" ></BitmapImage>
          </ImageDrawing.ImageSource>
        </ImageDrawing>
      </DrawingBrush.Drawing>
    </DrawingBrush>
  </sap:ActivityDesigner.Icon>
  <ContentPresenter x:Uid="ContentPresenter_1" Style="{x:Static sacdt:DesignerStylesDictionary.SequenceStyle}" Content="{Binding}" />
</sap:ActivityDesigner>

The XAML for the designer does two things. Firstly it identifies the icon the activity uses on the designer. Secondly, it identifies that the style of the content presenter is the same one that the SequenceDesigner uses for the Sequence activity. This style provides the support for displaying arrows, drag/drop behaviour and animation on the designer for working with child activities. This style is available from the DesignerStylesDictionary.SequenceStyle property. The System.Activities.Core.Presentation assembly exposes this type and is a reference of the designer project.

Like the BusinessFailureEvalator<T> activity, the BusinessFailureScope has a generic type argument that defaults to Int32 when the activity is dropped onto the designer. The code behind the designer makes this type updatable in the same way.

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

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

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

            GenericArgumentTypeUpdater.Attach(ModelItem);
        }
    }
}

The property attached by GenericArgumentTypeUpdater allows generic type to be changed using the ArgumenType property.

This post has demonstrated the designer support for the BusinessFailureEvaluator<T> and BusinessFailureScope<T> activities. Workflows can now use these two activities to manage business failures.

Custom Workflow activity for business failure evaluation–Part 5

The previous post in this series provided the custom activity that evaluates a single business failure in WF. One of the design goals of this series is to support the evaluation and notification of multiple failures. This post will provide a custom activity that supports this design goal.

At the very least, this activity needs to be able to contain multiple BusinessFailureEvaluator<T> activities. The design of this control will be like the Sequence activity where it can define and execute a collection of child activities.

There is no reason to restrict the child activities to the BusinessFailureEvaluator activity type so it will allow any child activity type. The screenshot above demonstrates this by adding an ExecuteBookmark activity in the middle of the business evaluators within the scope.

image

The activity has the same behaviour as BusinessFailureEvaluator for managing the generic type on the activity. It defaults to using Int32 and exposes an ArgumentType property to change the type after the activity is on the designer.

namespace Neovolve.Toolkit.Workflow.Activities
{ 
    using System;
    using System.Activities;
    using System.Activities.Presentation;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Drawing;
    using System.Windows.Markup;
    using Neovolve.Toolkit.Workflow.Extensions;

    [ContentProperty("Activities")]
    [ToolboxBitmap(typeof(ExecuteBookmark), "shield_go.png")]
    [DefaultTypeArgument(typeof(Int32))]
    public sealed class BusinessFailureScope<T> : NativeActivity where T : struct
    {
        private readonly Variable<Int32> _activityExecutionIndex = new Variable<Int32>();

        public BusinessFailureScope()
        {
            Activities = new Collection<Activity>();
            Variables = new Collection<Variable>();
        }

        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            metadata.RequireExtension<BusinessFailureExtension<T>>();
            metadata.AddDefaultExtensionProvider(() => new BusinessFailureExtension<T>());
            metadata.SetChildrenCollection(Activities);
            metadata.SetVariablesCollection(Variables);
            metadata.AddImplementationVariable(_activityExecutionIndex);
        }

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

            if (Activities.Count <= 0)
            {
                return;
            }

            Activity activity = Activities[0];

            ExecuteChildActivity(context, activity);
        }

        private void CompleteScope(NativeActivityContext context)
        {
            BusinessFailureExtension<T> extension = context.GetExtension<BusinessFailureExtension<T>>();

            IEnumerable<BusinessFailure<T>> businessFailures = extension.GetFailuresForScope(this);

            if (businessFailures == null)
            {
                return;
            }

            throw new BusinessFailureException<T>(businessFailures);
        }

        private void ExecuteChildActivity(NativeActivityContext context, Activity activity)
        {
            BusinessFailureExtension<T> extension = context.GetExtension<BusinessFailureExtension<T>>();

            extension.LinkActivityToScope(this, activity);

            context.ScheduleActivity(activity, OnActivityCompleted);
        }

        private void OnActivityCompleted(NativeActivityContext context, ActivityInstance completedInstance)
        {
            Int32 currentIndex = _activityExecutionIndex.Get(context);

            if ((currentIndex >= Activities.Count) || (Activities[currentIndex] != completedInstance.Activity))
            {
                currentIndex = Activities.IndexOf(completedInstance.Activity);
            }

            Int32 nextActivityIndex = currentIndex + 1;

            if (nextActivityIndex != Activities.Count)
            {
                Activity activity = Activities[nextActivityIndex];

                ExecuteChildActivity(context, activity);

                _activityExecutionIndex.Set(context, nextActivityIndex);
            }
            else
            {
                CompleteScope(context);
            }
        }

        [Browsable(false)]
        public Collection<Activity> Activities
        {
            get;
            set;
        }

        [Browsable(false)]
        public Collection<Variable> Variables
        {
            get;
            set;
        }
    }
}

The activity uses the CacheMetadata method to identify that it requires a BusinessFailureExtension<T> extension and provides the method to create one if it does not already exist. This method also configures the activity to support child activities and variable definitions.

The activity execution will resolve the extension instance before executing a child activity. It notifies the extension about the link between the scope and the child activity. The child activity can then use the extension to add a failure which the extension will store on behalf of the scope. The activity then gets the failures for the scope from the extension when it has executed all child activities. It then throws a BusinessFailureException<T> if there are any failures reported.

On a side note, the next version of this activity will refactor this last step so that the extension manages the exception throwing process as it does for BusinessFailureEvaluator<T>. This will align the code with the Single Responsibility Pattern so that only the extension understands how to handle failures and how to throw the BusinessFailureException<T>.

This post has provided the implementation for handling multiple business failures in a set. The next post will provide the designer implementation for these two activities.

Custom Workflow activity for business failure evaluation–Part 4

The previous post in this series provided the custom workflow extension that manages business failures. This post will provide a custom activity that evaluates a single business failure.

There are three important pieces of information to provide when defining a business failure with WF. These are the type of failure code, the failure condition and the failure details. The BusinessFailureEvaluator<T> activity supports these requirements in a compact way that makes authoring business failures easy in WF.

imageimage

The BusinessFailureEvaluator<T> activity is a generic activity type in order to support the generic type requirement of the Code property in BusinessFailure<T>. For ease of use, Int32 is the default type used for the generic type definition. The ArgumentType property can be used to change the activity use a different type for the Code property.

The activity defines a Nullable<Boolean> Condition property that drives whether the activity results in a failure at runtime. It also supports two methods of defining the failure details. These are to provide a BusinessFailure<T> instance in the Failure property or to define values for the Code and Description properties.

namespace Neovolve.Toolkit.Workflow.Activities
{
    using System;
    using System.Activities;
    using System.Activities.Presentation;
    using System.Activities.Validation;
    using System.ComponentModel;
    using System.Drawing;
    using Neovolve.Toolkit.Workflow.Extensions;
    using Neovolve.Toolkit.Workflow.Properties;
 
    [ToolboxBitmap(typeof(ExecuteBookmark), "shield.png")]
    [DefaultTypeArgument(typeof(Int32))]
    public sealed class BusinessFailureEvaluator<T> : CodeActivity where T : struct
    {
        protected override void CacheMetadata(CodeActivityMetadata metadata)
        {
            metadata.RequireExtension<BusinessFailureExtension<T>>();
            metadata.AddDefaultExtensionProvider(() => new BusinessFailureExtension<T>());

            Boolean conditionBound = Condition != null && Condition.Expression != null;

            if (conditionBound == false)
            {
                ValidationError validationError = new ValidationError(Resources.BusinessFailureEvaluator_NoConditionBoundWarning, true, "Condition");

                metadata.AddValidationError(validationError);
            }

            Boolean failureBound = Failure != null && Failure.Expression != null;
            Boolean codeBound = Code != null && Code.Expression != null;
            Boolean descriptionBound = Description != null && Description.Expression != null;
            const String CodePropertyName = "Code";

            if (failureBound == false && codeBound == false && descriptionBound == false)
            {
                ValidationError validationError = new ValidationError(
                    Resources.BusinessFailureEvaluator_FailureInformationNotBound, false, CodePropertyName);

                metadata.AddValidationError(validationError);
            }
            else if (failureBound && (codeBound || descriptionBound))
            {
                ValidationError validationError = new ValidationError(
                    Resources.BusinessFailureEvaluator_ConflictingFailureInformationBound, false, CodePropertyName);

                metadata.AddValidationError(validationError);
            }
            else if (codeBound && descriptionBound == false)
            {
                ValidationError validationError = new ValidationError(Resources.BusinessFailureEvaluator_DescriptionNotBound, false, "Description");

                metadata.AddValidationError(validationError);
            }
            else if (codeBound == false && descriptionBound)
            {
                ValidationError validationError = new ValidationError(Resources.BusinessFailureEvaluator_CodeNotBound, false, CodePropertyName);

                metadata.AddValidationError(validationError);
            }

            base.CacheMetadata(metadata);
        }

        protected override void Execute(CodeActivityContext context)
        {
            Nullable<Boolean> condition = Condition.Get(context);

            if (condition != null && condition == false)
            {
                // This is not a failure
                return;
            }

            BusinessFailure<T> failure = Failure.Get(context);

            if (failure == null)
            {
                T code = Code.Get(context);
                String description = Description.Get(context);

                failure = new BusinessFailure<T>(code, description);
            }

            BusinessFailureExtension<T> extension = context.GetExtension<BusinessFailureExtension<T>>();

            extension.ProcessFailure(this, failure);
        }

        [Category("Inputs")]
        [Description("The code of the failure")]
        public InArgument<T> Code
        {
            get;
            set;
        }

        [Category("Inputs")]
        [Description("The condition used to determine if there is a failure")]
        public InArgument<Nullable<Boolean>> Condition
        {
            get;
            set;
        }

        [Category("Inputs")]
        [Description("The description of the failure")]
        public InArgument<String> Description
        {
            get;
            set;
        }

        [Category("Inputs")]
        [Description("The failure")]
        public InArgument<BusinessFailure<T>> Failure
        {
            get;
            set;
        }
    }
}

The CacheMetadata method identifies that the activity requires a BusinessFailureExtension<T> extension and provides a default function to create one if it does not yet exist. The remainder of the method defines a set of validation rules for the activity. If no Condition is bound then the designer will display a warning message on the activity to indicate that this will always result in an exception.

image

The validation logic then checks for errors with the configuration of the activity. A Failure value or Code and Description must be provided and these are values are mutually exclusive. If a Code has been bound then a Description must also be bound and visa versa. Like the above warning, these validation errors are displayed in the designer surface however they also result in an exception thrown by the workflow engine if the activity is executed without addressing these errors.

image

The Execute method of the activity is where all the runtime processing occurs.

The only way that the activity will not result in an exception is if a Condition expression has been provided and its runtime value is false. Not binding the Condition property, providing a null value or providing a value of true means that a failure will be processed by the extension. The ability to not bind a Condition expression is useful where prior WF processing (such as an If statement) has already processed the conditional logic for the failure.

The activity will resolve a BusinessFailure<T> instance once the Execute method has determined that a business failure has been encountered. It first looks for a Failure property instance and will then create a new instance with Code and Description properties if it does not find a failure instance. The failure instance is then passed on to the extension for processing.

This post has shown how to create a simple business failure evaluation activity with validation support. The next post will look at how to provide the ability to group a set of failures using a composite activity.

Custom Workflow activity for business failure evaluation–Part 3

The previous post looked at the support for creating business failures and throwing the failures with an exception. This post will look at a custom WF extension that will process business failures in WF.

Using a custom extension to manage business failures abstracts workflow activities from the logic of processing business failures. The extension is responsible for processing business failures as they are reported by activities. It will also need to track relationships between activities in order to support the design goal of grouping related business failures into a single exception. This is achieved by providing a way of linking activities.

The Activity class in WF 3.0 and 3.5 provided a public Activity Parent property that could be used to traverse up the activity tree hierarchy. Unfortunately this property has been marked as internal in WF 4.0. Reflection could be used to get around this restriction but it is a hack at best. This prevents an automated method of walking up the workflow activity hierarchy. Similarly, inspecting child activity hierarchies is unreliable as there is no standard method for exposing or identifying child activities even if they are publicly available on an activity type. This prevents automated discovery of child activities. In addition to these issues, the automatic detection of activity hierarchies for linking activities may produce unintended results as activities are linked when they were not expected to be.

The alternative to these automated methods is to implement an explicit opt-in design where a parent activity informs the extension about a link to a child activity. This makes the parent activity responsible for informing the extension about a link to a child activity. The extension then uses this knowledge in the processing of the business failure.

namespace Neovolve.Toolkit.Workflow.Extensions
{ 
    using System;
    using System.Activities;
    using System.Activities.Persistence;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.Threading;
    using System.Xml.Linq;
    using Neovolve.Toolkit.Threading;

    public class BusinessFailureExtension<T> : PersistenceParticipant, IDisposable where T : struct
    {
        private static readonly XNamespace _persistenceNamespace = XNamespace.Get("http://www.neovolve.com/toolkit/workflow/properties");

        private static readonly XName _scopeEvaluatorsName = _persistenceNamespace.GetName("ScopeEvaluators");

        private static readonly XName _scopeFailuresName = _persistenceNamespace.GetName("ScopeFailures");

        private readonly ReaderWriterLockSlim _scopeEvaluatorsLock = new ReaderWriterLockSlim();

        private readonly ReaderWriterLockSlim _scopeFailuresLock = new ReaderWriterLockSlim();

        private Dictionary<String, String> _scopeEvaluators = new Dictionary<String, String>();

        private Dictionary<String, Collection<BusinessFailure<T>>> _scopeFailures = new Dictionary<String, Collection<BusinessFailure<T>>>();

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public IEnumerable<BusinessFailure<T>> GetFailuresForScope(Activity scopeActivity)
        {
            Contract.Requires<ArgumentNullException>(scopeActivity != null);
            Contract.Requires<ArgumentException>(String.IsNullOrEmpty(scopeActivity.Id) == false);

            String activityId = scopeActivity.Id;

            RemoveActivitiesLinkedToScope(activityId);

            using (new LockWriter(_scopeFailuresLock))
            {
                if (_scopeFailures.ContainsKey(activityId))
                {
                    Collection<BusinessFailure<T>> failures = _scopeFailures[activityId];

                    _scopeFailures.Remove(activityId);

                    return failures;
                }
            }

            return null;
        }

        public Boolean IsLinkedToScope(Activity activity)
        {
            Contract.Requires<ArgumentNullException>(activity != null);
            Contract.Requires<ArgumentException>(String.IsNullOrEmpty(activity.Id) == false);

            String activityId = activity.Id;

            String scopeActivityId = GetOwningScopeId(activityId);

            if (String.IsNullOrWhiteSpace(scopeActivityId))
            {
                return false;
            }

            return true;
        }

        public void LinkActivityToScope(Activity scopeActivity, Activity childActivity)
        {
            Contract.Requires<ArgumentNullException>(scopeActivity != null);
            Contract.Requires<ArgumentNullException>(childActivity != null);
            Contract.Requires<ArgumentException>(String.IsNullOrEmpty(scopeActivity.Id) == false);
            Contract.Requires<ArgumentException>(String.IsNullOrEmpty(childActivity.Id) == false);

            using (new LockWriter(_scopeEvaluatorsLock))
            {
                _scopeEvaluators[childActivity.Id] = scopeActivity.Id;
            }
        }

        public void ProcessFailure(Activity activity, BusinessFailure<T> failure)
        {
            Contract.Requires<ArgumentNullException>(activity != null);
            Contract.Requires<ArgumentException>(String.IsNullOrEmpty(activity.Id) == false);
            Contract.Requires<ArgumentNullException>(failure != null);

            String activityId = activity.Id;

            String scopeActivityId = GetOwningScopeId(activityId);

            if (String.IsNullOrEmpty(scopeActivityId))
            {
                // There is no scope activity that contains this evaluator
                throw new BusinessFailureException<T>(failure);
            }

            using (new LockWriter(_scopeFailuresLock))
            {
                Collection<BusinessFailure<T>> failures;

                if (_scopeFailures.ContainsKey(scopeActivityId))
                {
                    failures = _scopeFailures[scopeActivityId];
                }
                else
                {
                    failures = new Collection<BusinessFailure<T>>();

                    _scopeFailures.Add(scopeActivityId, failures);
                }

                // Store the failure for the scope
                failures.Add(failure);
            }
        }

        protected override void CollectValues(out IDictionary<XName, Object> readWriteValues, out IDictionary<XName, Object> writeOnlyValues)
        {
            Dictionary<String, String> evaluators;

            using (new LockReader(_scopeEvaluatorsLock))
            {
                evaluators = new Dictionary<String, String>(_scopeEvaluators);
            }

            Dictionary<String, Collection<BusinessFailure<T>>> scopeFailures;

            using (new LockReader(_scopeFailuresLock))
            {
                scopeFailures = new Dictionary<string, Collection<BusinessFailure<T>>>(_scopeFailures);
            }

            readWriteValues = new Dictionary<XName, Object>
                              {
                                  {
                                      _scopeEvaluatorsName, evaluators
                                      }, 
                                  {
                                      _scopeFailuresName, scopeFailures
                                      }
                              };

            writeOnlyValues = null;
        }

        protected virtual void Dispose(Boolean disposing)
        {
            if (disposing)
            {
                // Free managed resources
                if (Disposed == false)
                {
                    Disposed = true;

                    _scopeEvaluatorsLock.Dispose();
                    _scopeFailuresLock.Dispose();
                }
            }

            // Free native resources if there are any.
        }

        protected override void PublishValues(IDictionary<XName, Object> readWriteValues)
        {
            base.PublishValues(readWriteValues);

            Object evaluators;

            if (readWriteValues.TryGetValue(_scopeEvaluatorsName, out evaluators))
            {
                using (new LockWriter(_scopeEvaluatorsLock))
                {
                    _scopeEvaluators = (Dictionary<String, String>)evaluators;
                }
            }

            Object failures;

            if (readWriteValues.TryGetValue(_scopeFailuresName, out failures))
            {
                using (new LockWriter(_scopeFailuresLock))
                {
                    _scopeFailures = (Dictionary<String, Collection<BusinessFailure<T>>>)failures;
                }
            }
        }

        private String GetOwningScopeId(String activityId)
        {
            Debug.Assert(String.IsNullOrEmpty(activityId) == false, "No activity id provided");

            using (new LockReader(_scopeEvaluatorsLock))
            {
                if (_scopeEvaluators.ContainsKey(activityId))
                {
                    return _scopeEvaluators[activityId];
                }
            }

            return null;
        }

        private void RemoveActivitiesLinkedToScope(String scopeActivityId)
        {
            List<String> evaluatorIds = new List<String>();

            using (new LockReader(_scopeEvaluatorsLock))
            {
                evaluatorIds.AddRange(
                    from valuePair in _scopeEvaluators
                    where valuePair.Value == scopeActivityId
                    select valuePair.Key);
            }

            using (new LockWriter(_scopeEvaluatorsLock))
            {
                evaluatorIds.ForEach(x => _scopeEvaluators.Remove(x));
            }
        }

        protected Boolean Disposed
        {
            get;
            set;
        }
    }
}

The BusinessFailureExtension exposes a LinkActivityToScope method that creates the link between a scope and child activity. Child activities can check if they are linked to a scope by calling the IsLinkedToScope method. The link between these activities uses a Dictionary<String, String> instance to store the associations. The key of the dictionary is the ActivityId of the linked activity and the value is the ActivityId of the scope activity. This design allows for multiple activities to be linked to a scope while enforcing that an activity is only linked to a single scope activity.

The extension defines a ProcessFailure method for processing failures provided by an activity. The extension will throw a BusinessFailureException<T> straight away if the method does not find a link between the failure activity and a scope activity. The failure is stored in a failure list associated with the scope activity if there is a link found with a scope activity.

The GetFailuresForScope method returns any failures stored against a scope activity. This method returns the collection of failures that have been stored against a scope activity when a linked activity has invoked ProcessFailure. This method also cleans up stored information for the scope by removing any links to other activities and removing the failures stored for it.

The extension must support workflow persistence. This caters for the scenario where a linked activity has stored a failure against a scope activity and the workflow is persisted before the scope activity invokes GetFailuresForScope.

image

Persistence is supported by inheriting from PersistenceParticipant and overriding CollectValues and PublishValues. The CollectValues method provides the activity links and stored failures to the persistence process. The PublishValues method then restores these values again when the workflow is rehydrated from the persistence store.

Lastly, the extension implements IDisposable to ensure that ReaderWriterLock instances are disposed. The workflow execution engine will dispose the extension when the workflow execution has completed.

This post has demonstrated how a custom extension can be used by any activity to work with business failures. The next post will look at the custom activity for evaluating a business failure.