Tuesday, March 18, 2014

.Net Assembly versioning in Plug-in framework

As everybody knows assembly versioning in .net is a good concept. It helps us to deliver upgraded versions very easily without affecting behaviour of old versions ie easier to maintain backward compatibility. It also helps us to sign our assemblies with strong name which means our delivered dlls are unique.

In normal cases, the assembly version contains 4 numbers separated by period "." . Those denote Major version,Minor version, Build number and Revision number respectively. When to change or increment the assembly version is a tough decision especially when it comes to applications which uses reflection extensively. As best practices some people changes the assembly version for the shipping builds only. Anyhow when we change the assembly version we need to make sure that all assemblies including dependent assemblies are loading correctly as those are signed and needs to be in same version.

In my current project we are changing the assembly version once in a year as we are using reflection in its maximum. Recently we had to deal with a versioning issue related to a plug-in architecture, we introduced last year. For simplicity I can explain the plug-in architecture using simple drawing application framework which uses IShape and its implemented classes.

Problem

The aim is to have a plug-in architecture in place where there will be a framework which will be always latest. There will be an interface IShape with a method Draw().The framework creates the object of IShape implemented classes based on configuration using reflection and calls its Draw() method. We released it in 2013 and the structure was as follows
  • Core.dll {Version:1.0.0.0}
    • Contains IShape interface
  • Framework.exe{1.0.0.0}
    • The controller class which creates the object of IShape using reflection and call Draw()
  • Impl10.dll{Verion:1.0.0.0}
    • Contains Circle class which implements IShape.Draw() method to draw circle shape.
In 2014 we are not supposed to release Impl10.dll as there is no change. But the Framework.exe might have feature improvements and it's version needs to be incremented. Similarly the version of Core.dll needs to be incremented as its always expected to be latest.

The problem starts from here. If we deliver the new version of Core and Framework with incremented assembly version 2.0.0.0 for Core.dll and Framework.dll, the Impl10.dll will not get loaded as its in old version.

Dlls present after new release.
  • Core.dll {Version:2.0.0.0}
    • Contains IShape interface
  • Framework.exe{2.0.0.0}
    • The controller class which creates the object of IShape using reflection and call Draw()
  • Impl10.dll{Verion:1.0.0.0}
    • Contains Circle class which implements IShape.Draw() method to draw circle shape
  • Impl20.dll{Version 2.0.0.0}
    • Contains Rectangle class which implements IShape.Draw() method to draw rectangle shape

Possible solutions

  1. Assembly binding redirection 
    Using this technique we are forcing the .net runtime to use latest version of Core.dll even for Impl10.dll which is pointing towards previous version of Core.dll .But needs to maintain list of all old versions.
  2. Manual Assembly resolution
    This technique loads the assembly by writing the assembly loading code in the AppDomain.AssemblyResolve event.
  3. Constant version for Core.dll
    Simple one. Don't change the version of Core.dll even there are changes for new features. So that always the classes are implementing same interface. The assembly version of implementing classes can be incremented.
We opted the 3rd solution as its simple. Attached a sample which explains the plug-in scenario.


Things to remember

When we select any of these options we need to make sure below points
  1. Never remove any method or change the method signature in interface methods. If we do so .net runtime verification will fail and it will throw method not found exceptions if we try to load old assemblies.
  2. If we want to add more methods to the interface, we need to create new interface inheriting from IShape and have new methods there. When creating objects in framework.exe make sure we cast to respective interfaces to invoke operations. Since deployed framework.exe will be always latest, it can be done easily.

No comments: