Tape: Dependency management for .NET

Managing dependencies in .NET can be painful. You download the latest version of NServiceBus, only to find that it uses an old version of Castle Windsor, which isn't compatibile with the new version of Windsor that you need for Caliburn.

Package managers like OpenWrap and NuGet attempt to solve this problem, but they don't. Unless the NServiceBus team (or someone else) releases a new version of NServiceBus for the latest Castle Windsor, you're out of luck.

Personally, I like the way NDesk.Options goes about things. When you download the ZIP package, there's a single C# file under the ndesk-options folder that has all of the source code combined. You can just copy that C# file and paste it into your solution. No more DLL references.

Thus, I've created Tape, the solution to dependency management in .NET**.

Getting started with Tape

Let's use Tape to set up a project that depends on Autofac.

Step 1: Download tape.exe

Step 2: Download the latest Autofac source code

Step 3: Run tape over the directory containing Autofac.csproj:

The command line interface for Tape

Here I'm telling Tape to package the code in the Source\Autofac directory, packaging it into Autofac.cs. The -i switch tells tape to turn all public types into internal.

Step 4: Create a new VS project using Autofac.cs. I like to put my dependencies into a lib folder:

A VS solution with Autofac source code embedded

Step 5: Autofac source code does have a couple of MEF dependencies, so you'll need to add a reference to System.ComponentModel.Composition.

Step 6: Add some code that uses it:

interface IFoo
{
    void DoSomething();
}

public class Foo : IFoo
{
    public void DoSomething()
    {
        Console.WriteLine("Done!");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<Foo>().As<IFoo>();

        var container = builder.Build();
        var foo = container.Resolve<IFoo>();
        foo.DoSomething();

        Console.ReadKey();
    }
}

And you are done!

What it does

Tape scans the directory for all .cs files, and turns them into one big, unified .cs file. Along the way, it:

  • Removes [assembly:] attributes
  • Moves using statements into the namespace body
  • If the -i switch is passed, changes public types to internal types

What does the output look like?

Here's a couple of tapes that I taped with tape (wow, a verb AND a noun):

  1. Autofac.cs
  2. Castle.cs

Why this approach

If NServiceBus, NHibernate and Castle were available as single .cs files, making the latest version of any library work with another might be much easier.

Also, for really small libraries, it's annoying to have to reference an entire DLL. The ability to download (and embed as internal types) a single .cs file and paste it into my solution is pretty attractive.

Disclaimers

  1. Horn would probably be a better tool to use.
  2. I didn't test this on anything but Autofac and Castle
  3. It probably doesn't work on anything else
  4. There are probably a heap of edge cases it doesn't support
  5. Assemblies contain much more than just code, so yes, a lot of projects won't work with it

** probably not

A picture of me

Welcome, my name is Paul Stovell. I live in Brisbane and work full time bootstrapping my own product company around Octopus Deploy, an automated deployment tool for .NET applications.

Prior to Octopus Deploy, I worked for an investment bank in London building WPF applications, and before that I worked for Readify, an Australian .NET consulting firm, where I was lucky enough to work with some of the best in the business. I also worked on a number of open source projects and was an active user group presenter. I've been a Microsoft MVP for WPF since 2006.

jinge
jinge
15 Jan 2011

If multiple projects depends on the same small library, will you copy the cs file to each of the projects?

I think this way of dependency management will make the projects hard to maintain when dependencies grow more and more.

15 Jan 2011

Why would NServiceBus have to be built against the latest Castle dlls for you to use them? As long as they don't use strong names and versioned references, you should be able to happily use the latest Castle DLLs with their assemblies.

The only issue would be if the Castle APIs which they were using changed, you would end up with MethodMissingException, etc. With your approach you'd end up with compilation errors instead, that's the only difference here?

tobi
tobi
15 Jan 2011

Nice idea. I like that some projects to an ILMerge.xe run on their dependencies. The code duplication is usually not a concern at all.

16 Jan 2011

I don't see that this offers a particular advantage over just making the source available; which is what most projects I use end up doing. You just include that.

I do, however, agree that there is probably some idealised system for this that is as-yes unrealised. But I don't think this is it.

16 Jan 2011

I don't think this solves the problem of combining NServiceBus v1 + Castle Windsor v1 with Caliburn v2 + Castle Windsor v2; all you are going to end up with is a naming conflict between Castle Windsor v1 and Castle Windsor v2 in one big .cs file that won't compile. (Dynamic libraries are probably better for this, especially with assembly version redirect.)

What it would be very useful for, however, is releasing a project as a single EXE.

This is very useful for command line or other utility tools that you want to easily move around. (Moving a single EXE that 'just works' is simpler than also making sure you get all the needed DLL dependencies.)

Sometimes I share library code between EXE tools, rather than DLL references, just for this reason. (Or use ILMerge as mentioned above.)

17 Jan 2011

When a new version comes out we can create a new .cs file and compare it with the old one. This way we may be able to see (using Diff) the changes in the codebase. Tape rocks, very nice idea!