When you’re building components in .Net it’s inevitable that you’ll eventually have to target more than one version of the framework. Not every system is able to upgrade in step with new releases of the framework or even take advantage of in-place upgrades.

For example, Microsoft may be set to withdraw support for Windows 2003 but there will be systems stuck on this environment for some time to come. That means having to provide components that are compiled against .Net 4.0 as these systems will not be compatible with any later framework version.

You can always let the lowest common denominator win and compile against the lowest supported framework. The problem with this approach is that over time you will find a dwindling number of third party libraries providing updates that you can use.

It is better to take an approach based upon graceful degradation where you focus on the more advanced framework but provide a version that can be consumed by older frameworks. This is best done through a single set of source code rather than managing forks or branches.

Project files

You can run multiple project files in the same directory that are targeted for different versions of the framework. Newtonsoft’s Json.Net demonstrates how this is done as it contains numerous project versions targeting .Net versions 2.0 and onwards. Both the application and unit test projects compile for each targeted framework using the same set of files.

Note that you will need to ensure that each project files compiles output into different locations. You have to set this in two places:

The path for the final output is set in the “Output path” field in the “Build” tab of the project properties window. This needs to be set to a unique value for each build configuration.

You will also need to set the intermediate output path so parallel builds do not collide. This is set by adding a new element to the non-conditional PropertyGroup element in the project file, i.e.

Handling language differences

One disadvantage of multiple framework versions is that it becomes ever more difficult to leverage new language and framework features. You risk being bound by the lowest common denominator. For example, if you want to take advantage of the async and await keywords then you either target 4.5 or bring the Micrsofot.Bcl libraries into your project for .Net 4.0.

You can use conditional compilation statements to inject code that will only be executed for a particular framework. This approach is a little dangerous as your code will inevitably become peppered with conditional compilation logic. It’s much the same as having different source files for each framework, except the code can be difficult to read and any changes can be very difficult to keep track of.

Microsoft do not offer any built-in solutions for conditional compilation by framework version so you have to set up your own constants. These are defined in the project file for each build configuration as shown below:

<DefineConstants>TRACE;DEBUG;NET40</DefineConstants>

The constants can also be set in the “Build” tab of the project properties screen by entering them into the “Conditional compilation symbols” field. Once entered, a constant is available to a conditional compilation statement as shown below:

#if NET40
// This code compiles#else // This code does not compile#endif

Managing and restoring NuGet packages

Dependencies are easy to manage for multiple versions so long as you are using the same packages across each build. Managing any divergence can be difficult as Nuget package restore does not support more than one view of the dependencies in a packages.config file.

Note that the package restore is not the same as the references used in each project. In most cases the references that you use in older versions will be static – i.e. they will be stuck on the last supported version. You can always reserve package restore for the current version of your code while older versions are just references in your legacy projects.

If you really want to support automatic package restore then you can create placeholder projects for each framework version that contain direct references to any NuGet packages that are used. These projects should will force Visual Studio to download any necessary packages when the project first compiles.

About me

I am a London-based technical architect who has spent more than twenty years leading development across start-ups, digital agencies, software houses and corporates.
Over the years I have built a lot of stuff including web sites and services, multi-screen applications, systems integrations and middleware.

My current focus is on enabling scalable SaaS delivery and providing architectural leadership in agile environments.
I currently work as Chief Architect for Wolters Kluwer UK leading them to cloud heaven, one service at a time. Opinions are my own and not the views of my employer, etc.

Recent

There does seem to be growing confusion over what Service Fabric is really for. Is it an orchestrator for microservices? A means of lifting and shifting legacy applications into the cloud? An application development framework?

Azure Functions only provides direct support for a narrow range of authentication providers. If you want to use an external token provider or custom solution, you’ll have to create the plumbing yourself.

ArchUnit is a java library that provides a fluent API for creating self-testing architectures via unit tests. A similar library can be written for .Net Standard that acts on compiled assemblies rather than raw code.

There is a growing sense of unease around how larger organisations have implemented agile. In particular, there is a tendency towards centralised control that can be at odds with the agile preference for individuals over process.