My main focus the last couple of months has been on building components that offer HTTP-based services. This whole component-oriented focus is something we've been focusing on the last year or so because we wanted to actively trying move away from building monolithic systems. Tools like OWIN, NuGet and ILMerge have enabled new ways of building .NET software at scale that we're trying to benefit from. This series of posts intends to list some of the practices that emerged from building those components.
To prevent ending up with a theoretical discussion, I've build an accompanying example project called Piercer. Piercer is a little OWIN middleware component that you can host in your OWIN-aware console application, Windows Service or IIS-based web site. It exposes an end-point at route api/piercer/assemblies that lists the run-time assemblies in the AppDomain as well as their version and culture. For the sake of this post, its functionality is mostly irrelevant. It’s the mechanism of how it has been build that is important for this discussion.
Ingredient 1: Setup your middleware using the UseXXX convention
It's not an official requirement, but almost all middleware components are added to an OWIN pipeline builder using a extension method on IAppBuilder that starts with Use. And if you want to allow developers to configure your component, let them pass in a settings class as well.
var appBuilder = new AppBuilder(); appBuilder.UsePiercer(new PiercerSettings().Ignoring("Piercer.Middleware"));
Following those conventions makes your component more approachable. I myself prefer a fluent API for the settings, but whether you want to adopt that is up to you.
Ingredient 2: Minimize public dependencies
The most painful experience you can give to the consumers of your middleware component is the one where adding the NuGet package involves including several other NuGet packages as well. It gets worse if that consumer is already depending on other packages that on turn depend on different versions of those same packages. Avoiding this so-called diamond dependency problem is crucial to a successful component.
One of the most effective ways of solving that problem is to internalize the assemblies your component depends on and which are not exposed through your public API. Both ILMerge, which has been discontinued by Microsoft, and ILRepack, the open-source replacement for the first, can do the job. You'll have to experiment with which one works best unfortunately. Neither of them are the silver bullet to merging assemblies. I've been successful in using the ILRepack in one project, but it failed in the other. It requires a little bit of trial-and-error to get it right. But if you do, you'll make consuming your component almost painless. In the Piercer project, I've used the following PowerShell snippet to get all its dependencies merged into a single assembly like this:
Merge-Assemblies ` -outputFile "$NugetOutputDir\Piercer.Middleware.dll"` -libPaths @( "$BaseDirectory\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45", "$BaseDirectory\packages\Newtonsoft.Json.6.0.4\lib\net45\" )` -files @( "$SrcDir\Piercer.Middleware\bin\release\Piercer.Middleware.dll", "$SrcDir\Piercer.Middleware\bin\release\Microsoft.Owin.dll", "$SrcDir\Piercer.Middleware\bin\release\Newtonsoft.Json.dll", "$SrcDir\Piercer.Middleware\bin\release\Swashbuckle.Core.dll", "$SrcDir\Piercer.Middleware\bin\release\System.Net.Http.Formatting.dll", "$SrcDir\Piercer.Middleware\bin\release\System.Web.Http.dll", "$SrcDir\Piercer.Middleware\bin\release\System.Web.Http.Owin.dll", "$SrcDir\Piercer.Middleware\bin\release\System.Web.Http.WebHost.dll", "$SrcDir\Piercer.Middleware\bin\release\Autofac.dll", "$SrcDir\Piercer.Middleware\bin\release\Autofac.Integration.WebAPI.dll" )
The Merge-Assemblies function is a little wrapper that invokes ILRepack with the right arguments. You can find its definition here.