Modularity and Loading Multiple Versions of the Same Dependent Assembly

Suppose we want to develop a modular application where different modules can be developed and deployed independently of each other. In this kind of architecture each of the modules might also use some common libraries and frameworks. So we might end up having a file structure inside the Bin folder looking similar to this:

Bin
│   App.exe
│   
├───Module1
│       Infrastructure.dll (version 1.0.0.0)
│       Module1.dll
│       
└───Module2
        Infrastructure.dll (version 2.0.0.0)
        Module2.dll</code></pre>

In order to make this work we need to be able to load modules from subfolders inside our main Bin folder. In case of Module1.dll and Module2.dll the Runtime will automatically search Module1 and Module2 subfolders because they have the same name as the assemblies, so we don’t have to do anything special to make it work.

However, the problems start with the dependent assembly Infrastructure.dll. One might think that the .NET Runtime should try to search for the dependent assemblies in the same folder where it found the assembly referencing it, but unfortunately it doesn’t happen. It will search in GAC, in the Bin folder itself and in the Infrastructure subfolder and will not find anything in neither of those places.

To solve this problem the next logical step would be to try to tell the Runtime about additional places where to look for assemblies using the <probing> element in the App.config (or Web.config):

<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <probing privatePath="Module1;Module2"/>
    </assemblyBinding>   
</runtime>

This time Module1 will load normally and the referenced Infrastructure.dll will be successfully found. But there is still a problem with Module2 because it references a different version of Infrastructure.dll and the Runtime doesn’t seem to find it. So what’s going on?

It turns out that the .NET Runtime will stop looking for the correct assembly as soon as it finds a version of that assembly (no matter correct or not) in one of the probing paths. Here is a quote from How the Runtime Locates Assemblies:

The runtime stops probing the first time it finds an assembly that matches the simple assembly name referenced, whether it is a correct match or not. If it is a correct match, that assembly is used. If it is not a correct match, probing stops and binding fails.

In our case it finds Infrastructure.dll of version 1.0.0.0 in Module1 and fails because Module2 is referencing version 2.0.0.0.

So the solution, as the same MSDN article explains, is to specify explicitly for each of those referenced assemblies where to look for each of the versions using the <codeBase> element:

<runtime>                
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <dependentAssembly>
            <assemblyIdentity name="Infrastructure" publicKeyToken="b037ea4768395de3" />
            <codeBase version="1.0.0.0" href="Module1/Infrastructure.dll"/>
            <codeBase version="2.0.0.0" href="Module2/Infrastructure.dll"/>
        </dependentAssembly>
    </assemblyBinding>
</runtime>
Pavlo Glazkov

Pavlo Glazkov

Programmer. Full stack, with a focus on UI. JavaScript/TypeScript, Angular, Node.js, .NET

Read More