views:

425

answers:

8

I'm working on an appender for log4net and I'm faced with the problem of how to manage dependencies on the log4net version my class library is built against vs. the actual version deployed on the site. My class library has to reference log4net dlls and as such it becomes tied to the version I'm referencing at built time. However, the sites this component will be deployed will have various log4net versions, some older than mine, some will be newer. How should I approach this problem? I don't want to release a new version of my appender for each log4net new version and place the burden of correctly matching them on my users. I also don't want to ask my appender users to do complex side-by-side manifest tricks. I just want my appender to be simply copied on the end user location and work out-of-the-box with whatever log4net version is present there.

Is this achievable? Am I missing something obvious?

Update:

The only working solution is to use the manifest. I tested with two 'homemade' log4net builds and adding the following configuration section solves my problem:

<runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <dependentAssembly>
            <assemblyIdentity name="log4net"
                              publicKeyToken="..."
                              culture="neutral" />
            <bindingRedirect oldVersion="1.2.10.0"
                             newVersion="..."/>
         </dependentAssembly>
      </assemblyBinding>
   </runtime>

where the publicKeyToken is the actual key token of the real log4net assembly, 1.2.10 is the version my appender is built with and the newVersion is the currently deployed version on site. The section can be added to the deployed appconfig or webconfig (it can be done in machine config too, but I rather not recommend that...).

+2  A: 

Lot of projects have the same problem that you've just described. As far as I know, this is not something that you as a publisher can control. You can set a publisher policy that allows you to automatically specify that a certain version of your assembly should be used when older versions of your assemblies are referenced, but there is no way to specify this for assemblies that you do not control (like log4net).

On your user side, the administrator can specify that requests for an older version of log4net(which your assembly might reference) by redirected to a specific version via an assembly redirect.

Praveen Angyan
I know your answer is right, but is the one issue I'm trying to avoid (have my customers edit manifest files)
Remus Rusanu
A: 

EDIT: The suggestion below doesn't work: Specific Version is applied at compile-time, not execution time

Select the reference to Log4Net in Visual Studio (assuming you're using Visual Studio!) and show the properties. Set "Specific Version" to "false" and I think you should be okay. Have you already tried that and encountered problems?

Jon Skeet
After 9 years of working with .Net is the first time I hear that option *exists*. I'll give it a try.
Remus Rusanu
I'd really like to know if this works out for you. If I remember correctly Fluent NHibernate (or was it NHibernate itself) dropped their dependency on log4net because of the problem described in the OP.
Praveen Angyan
I left my appender dll with a 'soft' dependecy on log4net v1.2. I then compiled a newer log4net v1.9 and run my test. It refused to load my appender, with the message 'Could not load file or assembly 'log4net, Version=1.2.10.0,...'. I also tried the other way around (appender built on my 'v1.9', runtime v1.2) to no avail. I removed strong names, same result. Based on http://bit.ly/4ViOa I think the 'False' in question applies to compile time only.
Remus Rusanu
Darn, you're right. Sorry to get everyone's hopes up - will edit the answer to make it clear this isn't an option.
Jon Skeet
This will work so long as you remove the strong name from log4net on the version you build against (I've done this myself)
ShuggyCoUk
Ah no - I tell a lie, I had to remove a strong name from another assembly to hack out their dependency on a *different* version of log4net - sorry
ShuggyCoUk
A: 

You can use an IoC container (such as Castle, Spring.net, etc.) that actually instantiates the needed classes for you, according to the interfaces you use, as defined in a configuration file (or a code snippet).
This way, you only work with interfaces, and the actual binding with concrete classes is done by the container (framework). This is (especially) achievable if you work with dependency injection (see also Fowler's article about it).
You get "late binding" to your code, as long as the interfaces you use remain the same.

Ron Klein
That doesn't really work, not matter what I do I have a type dependency on the log4net base class/interfaces I must conform. For IoC to work log4net should have separated the types into a different, invariant, dll kept at v1.0 that I could reference.
Remus Rusanu
So write your own interfaces for logging. They can have the same signatures as the ones in log4net (this way, your code hardly changes). Then you could use an IoC container. Take a look at this one, too: http://tinyurl.com/l7xmd2
Ron Klein
I'm not convinced you understand my problem. *I need my component to be loaded by third parties that use log4net*. I'm not interested in them using my logging instead of log4net, that is the easy case scenario that I solved already.
Remus Rusanu
+1  A: 

You could handle the AssemblyResolve event:

AppDomain current = AppDomain.CurrentDomain;
current.AssemblyResolve += current_AssemblyResolve;

You could then use string manipulation on the Name property (from the ResolveEventArgs) to remove the version number and load the assembly without specifying its version.

marklam
A: 

Simple and stupid solution will be to use proxy library written on vb.net.

I faced similar problem when was trying to build tool for MS OFFICE.

With VB I was not worrying about what version of office is installed.

A: 

I tend to intensely dislike libraries which take dependencies on 'infrastructure' libraries like unit testing frameworks, logging frameworks and code generation tools when their primary purpose does not explicitly require it.

If you were to depend on a library which could be installed in the GAC then you could rely on that and having multiple version of the the library active at once but this would likely not play well with log4net anyway (given the shared configuration state model commonly used) and adds significant complexity on using your library.

One can point out that many libraries shouldn't be outputting any kind of logging since they will be unable to anticipate the requirements of their users well enough. log4net's runtime configurability does mitigate this to some degree though. Would your code be better if it moved to using the Tracing api built into the framework? The resulting code becomes much more lightweight in terms of dependencies and still allows some sophisticated tuning post compile.

ShuggyCoUk
A: 

An alternate to my other answer. Hacky, and there may be complications but:

Build the log4net code into your dll.
Since it is open source and under a reasonably permissive licence you could edit the files (noting that you did) to alter the visibility (and optionally namespaces) so that it was usable for your code and didn't interfere with consumers.

You may wish to add some code to attempt to configure your library from the same location that the rest of the application does, alternatively (and much more tractable) allow configuration of your libraries logging from an entirely separate file/config section.

ShuggyCoUk
I can't, because then my appender would expose the wrong interface (same name, but from a different assembly, therefore a different type)
Remus Rusanu
Ah ok - you aren't simply _using_ log4net you're extending it. You may want to make that clear in the question
ShuggyCoUk
A: 

For the CAD/CAM application I had to face this issue with some support libraries for a 3rd party machine that we had to support. What I did I created two assemblies one linked to one version and other linked to the other version. I put them both behind the same interface.

Then in our setup dialog you can specify which version to use. Of course if the incorrect version is used then a error would occur.

The trick is creating the interface so both assemblies can work seamlessly. It may not be possible to wrap a complex API like Microsoft Office. In which case you need to backtrack your use of the API and see where the minimum number of calls are used and build your interface at that point.

In my case the point came when I had to dump a sheet of material down to the machine. So I created a interface that had a routine the setup dialog for that machine and a routine that took my sheet object. Everything resided in that class.

In your case you need to look at what you use in Log4net and see if you can build a interface around that.

One advantage for doing this is that you no longer be welded to Log4Net. Design the interface correctly then you can reimplement use a different package. In addition creating a interface will precisely define how your application interacts with Log4net. Finally if Log4Net ever implements a breaking change then you have a path to deal with it.

RS Conley