Rory Primrose

Learn from my mistakes, you don't have time to make them yourself

View project on GitHub

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.

Read More

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.

Read More

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 Visual Studio 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.

Read More

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.

Read More

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.

Read More

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

Read More

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.

Read More

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.

Read More

Custom Workflow activity for business failure evaluation–Part 2

The previous post provided the high level design requirements for a custom WF activity that evaluates business failures. This post will provide the base classes that will support the custom WF activity.

The first issue to work on is how to express a business failure. The design requirements indicated that a business failure needs to identify a code and a description. The design also defined that the code value must be generic in order to avoid placing an implementation constraint on the consuming application.

Read More

Custom Workflow activity for business failure evaluation–Part 1

Following on from my series about custom WF support for dependency resolution, this series of posts will look at support for business failure evaluation.

Every project I have worked on has some kind of requirement to execute data validation and/or business rules when running a business process. Data validation could be a test that a request message contains an email address whereas a business rule may be that the provided email address is unique. Providing a custom WF activity to facilitate validation allows for rapid development of business processes.

Read More