Category Archives : Web Services and Interop

A category for WCF and interop


A lightweight implementation OWIN OAuth for ASP.NET Web Forms using Visual Studio 2013 – Part 1

Introduction

Disclaimer: this article assumes the reader is already familiar with OWIN and OAuth standards.  It is published in three parts.

Late last year I wanted to implement Open Web Interface for .Net (OWIN) OAuth  functionality for an administrative subdomain.  As it happened, I wanted to specifically integrate OWIN/OAuth authentication/authorization for an existing Dynamic Data site (using the .NET Framework v4.5 and the Entity Framework v5) but to just authenticate using just the Microsoft API for Microsoft Accounts.

Unfortunately for me, the out-of-the-box (OOTB) ASP.net identity packages use an implicit local DB which, in turn,  uses the Entity Framework v6 – which is entirely incompatible with Dynamic Data templates (v4 and v5 only).

Therefore, I was left with only one option – strip the ASP.net Identity packages out altogether and write a lightweight implementation directly against OWIN/OAuth packages – essentially replacing the  Microsoft OOTB ASP.net packages and removing the local DB implementation altogether.

I’m pleased to say I managed to achieve this, and now I’m going to write about what I had to do to get the final result.  In the end I opted to use an alternative set of OWIN packages (DotNetOpenAuth.*) instead of the OOTB (Microsoft.Owin.*) packages – this is discussed further, below.

I’ll be referring to a clean OOTB web forms project (as a baseline) throughout, but the steps should apply equally to an existing web project.  Note that I haven’t explored the MVC/Web API avenue, but there might be some useful info here for non-web forms based ASP.NET sites as well.

Note: this article was originally published at Sanders Technology – check here for updates to this and related articles.

In this Article

The content for this concept will run across a couple of parts, since there’s much to do to get set up properly.  This current article, Part 1, will focus on establishing the requirements for running the lightweight implementationPart 2 will look at how to configure your development environment and Part 3 will focus on the actual implementation (and will assume you have read parts 1 & 2).

Basic Setup

What you ideally want to do is start with a new site.  If that’s not possible or if you just want to retrofit onto an existing project, then you could just add the necessary packages (via NuGet Package Manager) and extra implementation to your existing project – after all, this is meant to be a very light implementation.

To show the packages I’m adding and removing, I’ve created a brand new out of the box site using Visual Studio 2013, and will compare the vanilla packages.config against my intended configuration.

image
The New Project wizard

An out-of-the-box ASP.net web forms project (as per the above diagram) produces a packages.config with the following definition:

<?xml version="1.0" encoding="utf-8"?> 
<packages> 
  <package id="Antlr" version="3.4.1.9004" targetFramework="net45" /> 
  <package id="AspNet.ScriptManager.bootstrap" version="3.0.0" targetFramework="net45" /> 
  <package id="AspNet.ScriptManager.jQuery" version="1.10.2" targetFramework="net45" /> 
  <package id="bootstrap" version="3.0.0" targetFramework="net45" /> 
  <package id="EntityFramework" version="6.0.0" targetFramework="net45" /> 
  <package id="jQuery" version="1.10.2" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.FriendlyUrls" version="1.0.2" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.FriendlyUrls.Core" version="1.0.2" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.Identity.Core" version="1.0.0" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.Identity.EntityFramework" version="1.0.0" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.Identity.Owin" version="1.0.0" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.Providers.Core" version="1.2" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.ScriptManager.MSAjax" version="5.0.0" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.ScriptManager.WebForms" version="5.0.0" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.Web.Optimization" version="1.1.1" targetFramework="net45" /> 
  <package id="Microsoft.AspNet.Web.Optimization.WebForms" version="1.1.1" targetFramework="net45" /> 
  <package id="Microsoft.Owin" version="2.0.0" targetFramework="net45" /> 
  <package id="Microsoft.Owin.Host.SystemWeb" version="2.0.0" targetFramework="net45" /> 
  <package id="Microsoft.Owin.Security" version="2.0.0" targetFramework="net45" /> 
  <package id="Microsoft.Owin.Security.Cookies" version="2.0.0" targetFramework="net45" /> 
  <package id="Microsoft.Owin.Security.Facebook" version="2.0.0" targetFramework="net45" /> 
  <package id="Microsoft.Owin.Security.Google" version="2.0.0" targetFramework="net45" /> 
  <package id="Microsoft.Owin.Security.MicrosoftAccount" version="2.0.0" targetFramework="net45" /> 
  <package id="Microsoft.Owin.Security.OAuth" version="2.0.0" targetFramework="net45" /> 
  <package id="Microsoft.Owin.Security.Twitter" version="2.0.0" targetFramework="net45" /> 
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" /> 
  <package id="Modernizr" version="2.6.2" targetFramework="net45" /> 
  <package id="Newtonsoft.Json" version="5.0.6" targetFramework="net45" /> 
  <package id="Owin" version="1.0" targetFramework="net45" /> 
  <package id="Respond" version="1.2.0" targetFramework="net45" /> 
  <package id="WebGrease" version="1.5.2" targetFramework="net45" /> 
</packages>

As mentioned in the introduction, you’ll notice that I’m using the “DotNetOpenAuth” packages created by Andrew Arnott which build upon the “DotNetOpenAuth extensions for ASP.NET (WebPages)” and “Microsoft WebPages OAuth library” which are built by Microsoft instead of the OOTB OWIN packages.  I was lead in that direction owing to the complicity of the OOTB packages, the DotNetOpenAuth implementation, IMHO, is much cleaner and easier to work with.

Note: I am referencing an updated version of the Entity Framework here (v6.0.2), in my earlier example site (mentioned in the introduction), I maintained the existing v5 version for compatibility with Dynamic Data templates.  The important point here is that the Entity Framework plays no part in implementing the lightweight OWIN/OAuth solution.

Below is the updated packages.config from my updated web forms website.

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Antlr" version="3.5.0.2" targetFramework="net45" />
  <package id="AspNet.ScriptManager.bootstrap" version="3.1.0" targetFramework="net45" />
  <package id="AspNet.ScriptManager.jQuery" version="2.1.0" targetFramework="net45" />
  <package id="bootstrap" version="3.1.0" targetFramework="net45" />
  <package id="DotNetOpenAuth.AspNet" version="4.3.4.13329" targetFramework="net45" />
  <package id="DotNetOpenAuth.Core" version="4.3.4.13329" targetFramework="net45" />
  <package id="DotNetOpenAuth.OAuth.Consumer" version="4.3.4.13329" targetFramework="net45" />
  <package id="DotNetOpenAuth.OAuth.Core" version="4.3.4.13329" targetFramework="net45" />
  <package id="DotNetOpenAuth.OpenId.Core" version="4.3.4.13329" targetFramework="net45" />
  <package id="DotNetOpenAuth.OpenId.RelyingParty" version="4.3.4.13329" targetFramework="net45" />
  <package id="EntityFramework" version="6.0.2" targetFramework="net45" />
  <package id="jQuery" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.FriendlyUrls" version="1.0.2" targetFramework="net45" />
  <package id="Microsoft.AspNet.FriendlyUrls.Core" version="1.0.2" targetFramework="net45" />
  <package id="Microsoft.AspNet.Providers.Core" version="2.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Razor" version="3.1.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.ScriptManager.MSAjax" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.ScriptManager.WebForms" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Web.Optimization" version="1.1.2" targetFramework="net45" />
  <package id="Microsoft.AspNet.Web.Optimization.WebForms" version="1.1.2" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebPages" version="3.1.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebPages.Data" version="3.1.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebPages.OAuth" version="3.1.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebPages.WebData" version="3.1.0" targetFramework="net45" />
  <package id="Microsoft.Bcl" version="1.1.6" targetFramework="net45" />
  <package id="Microsoft.Bcl.Build" version="1.0.13" targetFramework="net45" />
  <package id="Microsoft.Net.Http" version="2.2.18" targetFramework="net45" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
  <package id="Modernizr" version="2.7.1" targetFramework="net45" />
  <package id="Newtonsoft.Json" version="6.0.1" targetFramework="net45" />
  <package id="Owin" version="1.0" targetFramework="net45" />
  <package id="Respond" version="1.3.0" targetFramework="net45" />
  <package id="WebGrease" version="1.6.0" targetFramework="net45" />
</packages>

Note: I’ve removed these packages:

  <package id=”Microsoft.AspNet.Identity.Core” version=”1.0.0″ targetFramework=”net45″ />
  <package id=”Microsoft.AspNet.Identity.EntityFramework” version=”1.0.0″ targetFramework=”net45″ />
  <package id=”Microsoft.AspNet.Identity.Owin” version=”1.0.0″ targetFramework=”net45″ />
  <package id=”Microsoft.Owin” version=”2.0.0″ targetFramework=”net45″ />
  <package id=”Microsoft.Owin.Host.SystemWeb” version=”2.0.0″ targetFramework=”net45″ />
  <package id=”Microsoft.Owin.Security” version=”2.0.0″ targetFramework=”net45″ />
  <package id=”Microsoft.Owin.Security.Facebook” version=”2.0.0″ targetFramework=”net45″ />
  <package id=”Microsoft.Owin.Security.Google” version=”2.0.0″ targetFramework=”net45″ />
  <package id=”Microsoft.Owin.Security.MicrosoftAccount” version=”2.0.0″ targetFramework=”net45″ />
  <package id=”Microsoft.Owin.Security.Twitter” version=”2.0.0″ targetFramework=”net45″ />

Remember – you also need to ensure that project references need to be removed as well, simply editing the packages.config won’t remove the old packages from your life!

image
Ensure these are removed, and that the new packages are referenced.

With the ASP.net Identity implementation removed, you’ll have to roll your own classes.  This is not as bad as it sounds.

Preparing a new ASP.net web forms project

This only applies if you’re intending on modifying a new project – skip to the next section if you want to integrate with an existing project.

To get this solution compiling (for an OOTB project), you’ll have to strip out the existing OWIN implementation, as the project won’t compile.

image

The following project artefacts would need to be deleted/removed:

  • Startup.cs
  • The entire Models folder
  • App_Start\Startup.Auth.cs
  • The entire Account folder
  • Remove “Unnamed_LoggingOut” function from Global.cs

You’ll also need to edit the Web.Config accordingly:

  • Remove “Microsoft.AspNet.Identity” from <namespaces>
  • Remove entity framework references (if not using the EF)
  • Remove the Membership, profile and roleManager elements

image

You’ll also need to edit the Site.Master to remove old Account artefacts:

<AnonymousTemplate>
    <ul class="nav navbar-nav navbar-right">
        <li><a runat="server" href="~/Account/Register">Register</a></li>
        <li><a runat="server" href="~/Account/Login">Log in</a></li>
    </ul>
</AnonymousTemplate>
<LoggedInTemplate>
    <ul class="nav navbar-nav navbar-right">
        <li><a runat="server" href="~/Account/Manage" title="Manage your account">Hello, 
<%: Context.User.Identity.GetUserName() %> !</a>
</li> <li> <asp:LoginStatus runat="server" LogoutAction="Redirect" LogoutText="Log off"
LogoutPageUrl="~/" OnLoggingOut="Unnamed_LoggingOut" /> </li> </ul>
</LoggedInTemplate>

You can just remove the parts in bold, and add your own implementation later.  You’ll also need to include the “Microsoft ASP.NET Razor” package and the “Microsoft ASP.NET Web Pages” package (if not already installed) and ensure a reference to the System.Web.WebPages.Razor assembly – as this is required by elements of the OWIN assemblies.

Once you get to this point your project should compile, but will it run?  If it doesn’t you might need to double check that you’ve removed all of the parts listed above. If everything’s been done correctly you should be able to compile and run (under IIS Express by default). If you want to save time, here’s a clean OOTB site which compiles:

Clean Solution

OWIN OAuth functionality – Prerequisites

Now I’m going to state up-front: this is a very lean implementation. The functionality stripped out from the OOTB website will not be replaced. Instead, you’ll be able to effectively “log in” to a site with an OpenId, e.g. a Microsoft account.  In other words – it’s up to you to decide what functionality to build around it.

To use OAuth, you’ll need to be familiar with the requirements of each OpenID provider you want to incorporate.

The ASP.NET examples are invariably Facebook, Twitter and Microsoft Accounts, but can be extended in theory to any OpenID provider.

I’ll be using Microsoft Accounts, since that’s what I’ve used in the past.  We’ll go into more detail about this in Part 2, which establishes the development environment and I’ll walk you through how to configure for Microsoft Accounts.  You’ll need this before going any further, or you won’t be able to test or debug.

For now though, ensure you have the following packages installed:

<package id="DotNetOpenAuth.AspNet" version="4.3.4.13329" targetFramework="net45" />
<package id="DotNetOpenAuth.Core" version="4.3.4.13329" targetFramework="net45" />
<package id="DotNetOpenAuth.OAuth.Consumer" version="4.3.4.13329" targetFramework="net45" />
<package id="DotNetOpenAuth.OAuth.Core" version="4.3.4.13329" targetFramework="net45" />
<package id="DotNetOpenAuth.OpenId.Core" version="4.3.4.13329" targetFramework="net45" />
<package id="DotNetOpenAuth.OpenId.RelyingParty" version="4.3.4.13329" targetFramework="net45" />
<package id="Microsoft.AspNet.Razor" version="3.1.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="3.1.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages.Data" version="3.1.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages.OAuth" version="3.1.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages.WebData" version="3.1.0" targetFramework="net45" />

..and we’ll cover off the specific implementation in Part 3.

Summary and Download

So this article really just establishes how to prime a clean web forms project, or what to do to prepare an existing web site project.  We’ve only just begun…  Part 2 will establish the environment and help you to configure the Microsoft Account provider.  Part 3 will walk through the basic implementation.

Clean Visual Studio Solution

Continue to Part 2.


Building a Claims Aware Environment using ADFS 2.0 and WCF

Introduction

This is going to be a multi-part series of articles with the end goal of producing a solution which handles security/identity claims across domain boundaries using WCF services and Active Directory Federation Services 2.0 (with a federation trust) and Active Directory.  In order to demonstrate an approach to handling claims, we need an environment which is capable of supporting the infrastructure configuration we require.

Network Design

I strongly recommend that you put the time and effort into understanding the network topography.  When designing a key foundation of your approach to security, it’s critical that you have a working knowledge of the kind of trust you are placing in your trusted sub-systems.  For the next few articles, I’m going to rely on the following network design, which you can (not without some effort) establish for yourself using virtual machines:

image
Basic Network Design

Host Configuration

To keep resource usage to a minimum, I’ve designed my test environment to reuse hosts for key roles.  In practice, you might not normally mix roles in a production environment – refer to the appropriate ‘best practices’ to properly plan your infrastructure and deployment of critical roles!  Here’s a view of what is on each host:

image
Host Configuration

Installation and Configuration

You’ll need a minimum of two server installs to make this happen, although I’m using four to separate ADFS 2.0 and to configure an Enterprise Certificate Authority rather than a standalone CA.  You’ll also need the Windows Identity Foundation (WIF) SDK installed with your copy of Visual Studio (Visual Studio can be installed elsewhere – not on your test servers). 

I’ll be using Visual Studio 2010 for this, but I’m sure there’s a solution for Visual Studio 2012.  To streamline your configuration, I’ve provided links two some excellent walkthroughs on the MSDN blog site – the one you need to pay attention to is the ADFS 2.0 installation and configuration.

My Mobile Configuration

Since this is a fairly intensive number of operating systems, I’ve put together a fairly decent local configuration which I can take with me.  I’m using a 480 GB SanDisk SSD in an eSATA external enclosure (my laptop does not support USB 3 at the moment).  I’m running VM images off the SSD and getting very respectable performance.  No problems running four VMs in parallel.

 

Kit

How to Get Up and Running

My best advise is to follow the links below.  You’ll need a fair amount of stuff downloaded, so better jump on that.  Once you have some clean OSes and the installation packages, my best advise is to follow the walkthroughs.  Be careful not to accidentally skip anything, the configuration is a bit tricky at times, but if you follow the walkthrough closely you should have a working environment in about half a day or less.  My configuration varies to the walkthrough (as I have two domains), but if you duplicate the configuration for two different directories you should have something which can work.

Links Galore

If you’re going to build a test environment (frankly, just do it – it’s the best way) budget at least a day to get everything configured properly.  Don’t cut corners, it’ll only hurt you later.
Check back soon for the next article, where we’ll start to get familiar with the environment, and build a claims-aware application.

Important Downloads

.NET Framework 4.0 Runtime
http://www.microsoft.com/en-au/download/details.aspx?id=17718

.NET Framework 4.0.3 Update
http://www.microsoft.com/en-au/download/details.aspx?id=29053
Update 4.0.3 for Microsoft .NET Framework 4 – Design-time Update for Visual Studio 2010 SP1
http://www.microsoft.com/en-au/download/details.aspx?id=29054

Active Directory Federation Services 2.0 (RTW)
http://www.microsoft.com/en-us/download/details.aspx?id=10909&hash=Hx4OGpwvFzmf7%2bC7rR1nq18CYhcY%2bSE4ok1ifL%2fvSkYIpezfAxg6ePR2zpfAplmm6g%2fUyL1VU7RtmnuR6T4NWg%3d%3d

Windows Identity Foundation (Runtime)
http://www.microsoft.com/en-us/download/details.aspx?id=17331

Windows Identity Federation SDK
http://www.microsoft.com/en-us/download/details.aspx?id=4451

 

Installation and Configuration Walkthroughs

ADFS 2.0 Installation Walkthrough
http://blogs.msdn.com/b/alextch/archive/2011/06/27/installing-a-stand-along-adfs-service.aspx
Establishing a Federation Trust Walkthrough
http://blogs.msdn.com/b/alextch/archive/2011/06/27/establish-federation-trust.aspx

Building a Claims-aware Web Application Walkthrough
http://blogs.msdn.com/b/alextch/archive/2011/06/27/building-a-test-claims-aware-asp-net-application-and-integrating-it-with-adfs-2-0-security-token-service-sts.aspx

These walkthroughs really helped! 

Finally.. If you hit problems with your STS certificate – check the HTTPS bindings of your local IIS:

http://www.shutuplaura.com/journal/2010/1/5/adfsv2-rc-iis-certificates.html


Common Functionality across WCF Web Service Operations

Introduction

Recently, I started building up a WCF Service Application from scratch to implement a specific set of operations from an existing WSDL.

This was a little cumbersome, but gave me an excellent opportunity to extend and play around with a concept I’ve been trying to perfect for a couple of years now.  In a nutshell, what I wanted was the ability to just focus on implementing the specific functionality of a Web Service operation, yet reuse common validation, logging and exception handling.

This required each web service to inherit from a common base class.  Each request is a class which inherits from a base class also (containing properties which are required for every request).

Class Object View

Let’s take a look at my web service:

image

As you can see, the FormsManager class derives from the obviously named ServiceBase.  Now, as I stated previously, my intention was to move as much “common functionality” into the base as possible.  To do this, let’s take a look at one of the operations:

A Sample Web Service Operation

/// 
/// Submit a form (i.e. lodge a form)
/// 
/// 
/// 
public SubmitFormResponse SubmitForm(SubmitFormRequest request)
{
    return base.ExecuteRequest<SubmitFormRequest, SubmitFormResponse>
	(request, delegate
    {
        AuthoriseAccountId(request.AccountId, request.FormType, 
				FormTypeAuthorisationEnum.Submit);

        if (!String.IsNullOrEmpty(request.ReferenceId))
        {
            AuthoriseAccountId(request.AccountId, 
				request.ReferenceId, 
				FormAuthorisationEnum.Manage);
        }
     
        FormsProvider.SubmitForm(request);

        SubmitFormResponse response = new SubmitFormResponse();
        response.Reference = request.ReferenceId;
        return response;
    });
}

In this scenario, SubmitFormRequest inherits from RequestBase and SubmitFormResponse inherits from ResponseBase.  This will make more sense in the next block of code.

As you can see, this operation only has to worry about implementing functionality specific to it’s requirements – common validation, exception handling and logging can be moved into the ‘ExecuteRequest’ function in the base class like so:

The Base Class

        
/// 
/// Execute a Request where the delegate returns a value
/// 
/// 
protected Z ExecuteRequest<T, Z>(T request, Func<Z> operation)
    where T : RequestBase
    where Z : ResponseBase 
{
    try
    {
        TraceLogger.Log.Trace(
		String.Format("Execute Request: {0}", 
				TraceHelper.GetPreviousMethod()));

        if (request == null)
        {
            throw new 
	    ArgumentNullException("Specified Request Parameter was NULL");
        }
        if (String.IsNullOrEmpty(request.AccountId))
        {
            throw new 
            RequiredArgumentException("Account ID was not specified");
        }

        // Validate the identity of the request
        ValidateAccountId(request);

        return operation();
    }
    catch (Exception ex)
    {
         //Log the exception
        if (ex is ICustomException)
        {
            throw ex;
        }
        throw ex; //TODO: Sanitise the Exception
    }
}

This is still a work in progress, but what it does demonstrate is how to embed a consistent and reusable set of functionality into a base class (reducing code duplication) and allowing you to add code to each web service operation which is specific to the nature of the operation; without the need to explicitly add try/catch or logging.

How does this work?

We’re making use of the Func<> (and in other base functions where no return value is required, Action<>) delegate functionality.  In this instance, Func<Z> defines a delegate which returns a value of type Z.  In the example here, Z is defined as SubmitFormResponse.

Therefore, the pseudo code for executing a SubmitForm request is as follows:

  • Call base class
  • Try
    • Trace (calling function name)
    • Is Request NULL?
    • Is AccountId Null or Empty?
    • Is AccountId Valid?
    • return Call delegate()
      • Is AccountId Authorised?
      • Is ReferenceId Null or Empty?
      • Is AccountId Authorised to Manage this Reference?
      • SubmitForm
      • Create new SubmitFormResponse
      • return response
  • Catch
  • Log exception
  • Is Custom Exception? (throw)
  • Sanitise Exception (throw)

The try/catch block in the ExecuteRequest implementation will also catch any unhandled exceptions thrown by the delegate.  This gives you the potential for automatic exception management – no more uncaught exceptions thrown back to the caller.  You can also make use of standardised logging as well.

Note that the type restriction on the base class function could easily have been defined as the following:

        
/// 
/// Execute a Request where the delegate returns a value
/// 
/// 
protected Z ExecuteRequest<T, Z>(T request, Func<Z> operation)
    where T : RequestBase
    where Z : class
{

Which would allow the delegate to return any class.  Alternatively, you could remove the restriction altogether if you wanted to return value types as well, for example:

/// 
/// Execute a Request where the delegate returns a value
/// 
/// 
protected Z ExecuteRequest<T, Z>(T request, Func<Z> operation)
    where T : RequestBase

//-------------------------------------------------------------------------

return base.ExecuteRequest<SaveDraftFormRequest, int>(request, delegate
{
    return -1;
});

Should your base class be required to handle a delegate which does not return a value, simply replace Func<> with Action<>.

Summary

What I’ve covered off here is just some simple usages of generics and delegates which may help to improve the maintainability and consistency of your WCF web service operations. 

There are limitations (such as the granularity of properties you would be compelled to put into a base class), but the wins are (IMHO) worth the time to implement.

Perhaps you have designed or implemented something similar? 

Please leave a comment.