Creating a simple bootstrapper - Introduction

Last month I was creating a WiX setup package to deploy a new system. In the past we have typically required the user to execute the MSI from the command line and provided all the MSI arguments required by the package. We have also played with transform packages to achieve the same result. I am not a fan of either of these methods and want to push configuration options into the installer UI as a way of minimising user error while maintaining flexibility. I came across the excellent MsiExt proj... [More]

Pitfalls of cancelling a VSIX project template in an IWizard

I’ve been creating a VSIX project template for [VS] over the last week or two. The project contains an IWizard class implementation to allow the user to define some information for the template. I have noticed one weird quirk when it comes to cancelling the wizard. The project template is written to disk before the IWizard.RunStarted method is invoked. This raises some questions about how to cancel the wizard process and make the solution (and disk) look like nothing ever happened. How to can... [More]

Unable to preserve pending changes on gated check in

Gated check in with TFS 2010 is a brilliant feature. Unfortunately the “Preserve my pending changes locally” checkbox is sometimes disabled and it isn’t clear from the UI why this is the case. There is an MSDN forum thread (here) that provides some reasons why this happens. The checkbox will be disabled if there are locks on some of the files in the changeset, the changeset contains binary files or exclusive check-out option is used on the team project. The one that has been h... [More]

Neovolve.BuildTaskExecutor 1.0 Released

Over the last week I have posted a series of entries about how to execute custom tasks as part of a build process without TFS. The result of this process is the Neovolve.BuildTaskExecutor 1.0 application which is now available on Codeplex. The following posts document how BuildTaskExecutor has come about: Executing build tasks without a build server – Design Executing build tasks without a build server – Implementation Executing build tasks without a build server – In action Exe... [More]

Executing build tasks without a build server – Creating a custom ITask

Following on from the previous posts in this series, this post will look at how to create a custom task that can then be executed via BTE. This post will provide an example of how to create a task that will allow execution of multiple tasks from a single command line execution. The task itself is fairly meaningless as you can simply invoke BTE several times to get the same result. It does however provide a good example of how to use some of the services provided by BTE. The first thing to do is ... [More]

Executing build tasks without a build server – Example scenario

My last few posts have described the process of building an application that can manage custom build tasks without the availability of a full TFS environment (here, here and here). This post will look at how I use this application and all its tasks to manage the product version of a [VS] solution. The product I originally wrote BuildTaskExecutor (BTE) for is the .Net port of my old VB6 Switch program which is now hosted out on Codeplex (here). The build tasks I want to execute when this solutio... [More]

Executing build tasks without a build server – In action

The previous post provided the implementation of the BTE application. It described how the implementation of BTE is able to satisfy the original design requirements by providing an extensible task execution model. The following are some examples of BTE running on the command line. I am using a batch file in order to shorten BuildTaskExecutor.exe down to bte. 1) Executing BTE without arguments 2) Executing the help task 3) Executing the help task for a specific task name (or alias in this case)... [More]

Executing build tasks without a build server – Design

At work we run TFS2010 with build controllers, build agents and lab management. I have customised the build workflow so that we can get a lot more functionality out of the automated build process. Some of these customisations are things like: Finding the product version number Incrementing the product version number Updating the build name with the new product version Updating files prior to compilation Updating files after compilation Building Sandcastle documentation Deploying MSIs to remove ... [More]

WPF and the link that just won’t click

I’ve been playing with WPF over the last month. It has been great to finally work on a program that is well suited to this technology. One of the implementation details that has surprised me is that the Hyperlink control doesn’t do anything when you click the link. I can only guess what the design reason is behind this. The only thing the control seems to do is fire off a RequestNavigate event. Every implementation of this control in a form then needs to manually handle this event to fire off t... [More]

Neovolve ReSharper Plugins 2.0 released

It has been a few years since I have updated my ReSharper plugin project that was originally released three years ago. The reason for the project was that ReSharper natively converts CLR types (System.Int32) to its alias (int). StyleCop also adds this support in its ReSharper plugin. Unfortunately nothing in the marketplace provides a conversion from an alias type to its CLR type. I always prefer the CLR types over the alias type names. This allows me to easily identify what is a C# keyword an... [More]
Rory Primrose | Learn from my mistakes, you don't have time to make them yourself

Generic Func IEqualityComparer

Developers generally like the LINQ syntax with all its lambda goodness. It is fluent and easy to write. Then you do something like dataSet.Intersect(otherData, OHNO!).

Signatures like the LINQ Intersect function seems to just get in the way of productive development. With so many things in a lambda syntax, we are now forced back into the world of IEqualityComparer. The easy fix is to drop in something like a generic equality comparer that will support a Func.

public class PredicateComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, T, bool> _comparer;

    public PredicateComparer(Func<T, T, bool> comparer)
    {
        _comparer = comparer;
    }

    public bool Equals(T x, T y)
    {
        return _comparer(x, y);
    }

    public int GetHashCode(T obj)
    {
        // We don't want to use hash code comparison
        // Return zero to force usage of Equals
        return 0;
    }
}

This little helper doesn’t totally fix the syntax problem, but does limit how big your coding speed bumps are. For example:

var matchingEntities = allEntities.Intersect(
    subsetOfEntities,
    new PredicateComparer<MyEntityType>((x, y) => x.Id == y.Id));

Easy.

Azure Table Storage Adapter - Fixes and Features

I have posted over the last couple of months about an adapter class that I have been using for using Azure table storage. The adapter has been really useful to bridge between Azure table entities and application domain models. There have been some interesting scenarios that have been discovered while using this technique. Here has been the history so far:

This class has evolved well although two issues have been identified.

  1. The last post indicates a workaround where your domain model exposes a property from ITableEntity (namely the Timestamp property). While there is a workaround, it would be nice if the adapter just took care of this for you.
  2. Commenter Andy highlighted a race condition where unsupported property types were not read correctly where the first operation on the type was a read instead of a write (see here)

The race condition that Andy found is one of the production issues I have seen on occasion that I was not able to pin down (big shout out and thanks to Andy).

This latest version of the EntityAdapter class fixes the two above issues.

namespace MySystem.Server.DataAccess.Azure
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using Microsoft.WindowsAzure.Storage;
    using Microsoft.WindowsAzure.Storage.Table;
    using Seterlund.CodeGuard;

    /// <summary>
    ///     The <see cref="EntityAdapter{T}" />
    ///     class provides the base adapter implementation for reading and writing a POCO class with Azure Table Storage.
    /// </summary>
    /// <typeparam name="T">
    ///     The type of value.
    /// </typeparam>
    [CLSCompliant(false)]
    public abstract class EntityAdapter<T> : ITableEntity where T : class, new()
    {
        /// <summary>
        ///     The synchronization lock.
        /// </summary>
        /// <remarks>A dictionary is not required here because the static will have a different value for each generic type.</remarks>
        private static readonly object _syncLock = new object();

        /// <summary>
        ///     The additional properties to map for types.
        /// </summary>
        /// <remarks>A dictionary is not required here because the static will have a different value for each generic type.</remarks>
        private static List<AdditionalPropertyMetadata> _additionalProperties;

        /// <summary>
        ///     The partition key
        /// </summary>
        private string _partitionKey;

        /// <summary>
        ///     The row key
        /// </summary>
        private string _rowKey;

        /// <summary>
        ///     The entity value.
        /// </summary>
        private T _value;

        /// <summary>
        ///     Initializes a new instance of the <see cref="EntityAdapter{T}" /> class.
        /// </summary>
        protected EntityAdapter()
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="EntityAdapter{T}" /> class.
        /// </summary>
        /// <param name="value">
        ///     The value.
        /// </param>
        protected EntityAdapter(T value)
        {
            Guard.That(value, "value").IsNotNull();

            _value = value;
        }

        /// <inheritdoc />
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
            Justification = "Parameter is validated using CodeGuard.")]
        public void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext)
        {
            Guard.That(properties, "properties").IsNotNull();

            _value = new T();

            TableEntity.ReadUserObject(Value, properties, operationContext);

            var additionalMappings = GetAdditionPropertyMappings(Value, operationContext);

            if (additionalMappings.Count > 0)
            {
                ReadAdditionalProperties(properties, additionalMappings);
            }

            ReadValues(properties, operationContext);
        }

        /// <inheritdoc />
        public IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
        {
            var properties = TableEntity.WriteUserObject(Value, operationContext);

            var additionalMappings = GetAdditionPropertyMappings(Value, operationContext);

            if (additionalMappings.Count > 0)
            {
                WriteAdditionalProperties(additionalMappings, properties);
            }

            WriteValues(properties, operationContext);

            return properties;
        }

        /// <summary>
        ///     Builds the entity partition key.
        /// </summary>
        /// <returns>
        ///     The partition key of the entity.
        /// </returns>
        protected abstract string BuildPartitionKey();

        /// <summary>
        ///     Builds the entity row key.
        /// </summary>
        /// <returns>
        ///     The <see cref="string" />.
        /// </returns>
        protected abstract string BuildRowKey();

        /// <summary>
        ///     Clears the cache.
        /// </summary>
        protected void ClearCache()
        {
            lock (_syncLock)
            {
                _additionalProperties = null;
            }
        }

        /// <summary>
        ///     Reads the values from the specified properties.
        /// </summary>
        /// <param name="properties">
        ///     The properties of the entity.
        /// </param>
        /// <param name="operationContext">
        ///     The operation context.
        /// </param>
        protected virtual void ReadValues(
            IDictionary<string, EntityProperty> properties,
            OperationContext operationContext)
        {
        }

        /// <summary>
        ///     Writes the entity values to the specified properties.
        /// </summary>
        /// <param name="properties">
        ///     The properties.
        /// </param>
        /// <param name="operationContext">
        ///     The operation context.
        /// </param>
        protected virtual void WriteValues(
            IDictionary<string, EntityProperty> properties,
            OperationContext operationContext)
        {
        }

        /// <summary>
        ///     Gets the additional property mappings.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="operationContext">The operation context.</param>
        /// <returns>
        ///     The additional property mappings.
        /// </returns>
        private static List<AdditionalPropertyMetadata> GetAdditionPropertyMappings(
            T value,
            OperationContext operationContext)
        {
            if (_additionalProperties != null)
            {
                return _additionalProperties;
            }

            List<AdditionalPropertyMetadata> additionalProperties;

            lock (_syncLock)
            {
                // Check the mappings again to protect against race conditions on the lock
                if (_additionalProperties != null)
                {
                    return _additionalProperties;
                }

                additionalProperties = ResolvePropertyMappings(value, operationContext);

                _additionalProperties = additionalProperties;
            }

            return additionalProperties;
        }

        /// <summary>
        ///     Resolves the additional property mappings.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="operationContext">The operation context.</param>
        /// <returns>
        ///     The additional properties.
        /// </returns>
        private static List<AdditionalPropertyMetadata> ResolvePropertyMappings(
            T value,
            OperationContext operationContext)
        {
            var storageSupportedProperties = TableEntity.WriteUserObject(value, operationContext);
            var objectProperties = value.GetType().GetProperties();
            var infrastructureProperties = typeof(ITableEntity).GetProperties();
            var missingProperties =
                objectProperties.Where(
                    objectProperty => storageSupportedProperties.ContainsKey(objectProperty.Name) == false);

            var additionalProperties = missingProperties.Select(
                x => new AdditionalPropertyMetadata
                {
                    IsInfrastructureProperty = infrastructureProperties.Any(y => x.Name == y.Name),
                    PropertyMetadata = x
                });

            return additionalProperties.ToList();
        }

        /// <summary>
        ///     Reads the additional properties.
        /// </summary>
        /// <param name="properties">The properties.</param>
        /// <param name="additionalMappings">The additional mappings.</param>
        /// <exception cref="System.InvalidOperationException">
        ///     The ITableEntity interface now defines a property that is not
        ///     supported by this adapter.
        /// </exception>
        private void ReadAdditionalProperties(
            IDictionary<string, EntityProperty> properties,
            IEnumerable<AdditionalPropertyMetadata> additionalMappings)
        {
            // Populate the properties missing from ReadUserObject
            foreach (var additionalMapping in additionalMappings)
            {
                if (additionalMapping.IsInfrastructureProperty)
                {
                    // We don't want to use a string conversion here
                    // Explicitly map the types across
                    if (additionalMapping.PropertyMetadata.Name == "Timestamp" &&
                        additionalMapping.PropertyMetadata.PropertyType == typeof(DateTimeOffset))
                    {
                        // This is the timestamp property
                        additionalMapping.PropertyMetadata.SetValue(Value, Timestamp);
                    }
                    else if (additionalMapping.PropertyMetadata.Name == "ETag" &&
                             additionalMapping.PropertyMetadata.PropertyType == typeof(string))
                    {
                        // This is the timestamp property
                        additionalMapping.PropertyMetadata.SetValue(Value, ETag);
                    }
                    else if (additionalMapping.PropertyMetadata.Name == "PartitionKey" &&
                             additionalMapping.PropertyMetadata.PropertyType == typeof(string))
                    {
                        // This is the timestamp property
                        additionalMapping.PropertyMetadata.SetValue(Value, PartitionKey);
                    }
                    else if (additionalMapping.PropertyMetadata.Name == "RowKey" &&
                             additionalMapping.PropertyMetadata.PropertyType == typeof(string))
                    {
                        // This is the timestamp property
                        additionalMapping.PropertyMetadata.SetValue(Value, RowKey);
                    }
                    else
                    {
                        const string UnsupportedPropertyMessage =
                            "The {0} interface now defines a property {1} which is not supported by this adapter.";

                        var message = string.Format(
                            CultureInfo.CurrentCulture,
                            UnsupportedPropertyMessage,
                            typeof(ITableEntity).FullName,
                            additionalMapping.PropertyMetadata.Name);

                        throw new InvalidOperationException(message);
                    }
                }
                else if (properties.ContainsKey(additionalMapping.PropertyMetadata.Name))
                {
                    // This is a property that has an unsupport type
                    // Use a converter to resolve and apply the correct value
                    var propertyValue = properties[additionalMapping.PropertyMetadata.Name];
                    var converter = TypeDescriptor.GetConverter(additionalMapping.PropertyMetadata.PropertyType);
                    var convertedValue = converter.ConvertFromInvariantString(propertyValue.StringValue);

                    additionalMapping.PropertyMetadata.SetValue(Value, convertedValue);
                }

                // The else case here is that the model now contains a property that was not originally stored when the entity was last written
                // This property will assume the default value for its type
            }
        }

        /// <summary>
        ///     Writes the additional properties.
        /// </summary>
        /// <param name="additionalMappings">The additional mappings.</param>
        /// <param name="properties">The properties.</param>
        private void WriteAdditionalProperties(
            IEnumerable<AdditionalPropertyMetadata> additionalMappings,
            IDictionary<string, EntityProperty> properties)
        {
            // Populate the properties missing from WriteUserObject
            foreach (var additionalMapping in additionalMappings)
            {
                if (additionalMapping.IsInfrastructureProperty)
                {
                    // We need to let the storage mechanism handle the write of the infrastructure properties
                    continue;
                }

                var propertyValue = additionalMapping.PropertyMetadata.GetValue(Value);
                var converter = TypeDescriptor.GetConverter(additionalMapping.PropertyMetadata.PropertyType);
                var convertedValue = converter.ConvertToInvariantString(propertyValue);

                properties[additionalMapping.PropertyMetadata.Name] =
                    EntityProperty.GeneratePropertyForString(convertedValue);
            }
        }

        /// <inheritdoc />
        public string ETag
        {
            get;
            set;
        }

        /// <inheritdoc />
        public string PartitionKey
        {
            get
            {
                if (_partitionKey == null)
                {
                    _partitionKey = BuildPartitionKey();
                }

                return _partitionKey;
            }

            set
            {
                _partitionKey = value;
            }
        }

        /// <inheritdoc />
        public string RowKey
        {
            get
            {
                if (_rowKey == null)
                {
                    _rowKey = BuildRowKey();
                }

                return _rowKey;
            }

            set
            {
                _rowKey = value;
            }
        }

        /// <inheritdoc />
        public DateTimeOffset Timestamp
        {
            get;
            set;
        }

        /// <summary>
        ///     Gets the value managed by the adapter.
        /// </summary>
        /// <value>
        ///     The value.
        /// </value>
        public T Value
        {
            get
            {
                return _value;
            }
        }

        /// <summary>
        ///     The <see cref="AdditionalPropertyMetadata" />
        ///     provides information about additional storage properties for an entity type.
        /// </summary>
        private struct AdditionalPropertyMetadata
        {
            /// <summary>
            ///     Gets or sets a value indicating whether this instance is infrastructure property.
            /// </summary>
            /// <value>
            ///     <c>true</c> if this instance is infrastructure property; otherwise, <c>false</c>.
            /// </value>
            public bool IsInfrastructureProperty
            {
                get;
                set;
            }

            /// <summary>
            ///     Gets or sets the property metadata.
            /// </summary>
            /// <value>
            ///     The property metadata.
            /// </value>
            public PropertyInfo PropertyMetadata
            {
                get;
                set;
            }
        }
    }
}

Azure Table Storage Adapter Using Reserved Properties

I posted earlier this year about an adapter class to be the bridge between ITableEntity and a domain model class when using Azure table storage. I hit a problem with this today when I was dealing with a model class that had a Timestamp property.

While the adapter class is intended to encapsulate ITableEntity to prevent it leaking from the data layer, this particular model actually wanted to expose the Timestamp value from ITableEntity. This didn’t go down too well.

Microsoft.WindowsAzure.Storage.StorageException: An incompatible primitive type 'Edm.String[Nullable=True]' was found for an item that was expected to be of type 'Ed