Is it possible to replace a reference to a strongly-named assembly with a "weak" reference?
Asked Answered
K

3

17

I'm writing a .NET tool that requires the SQL Server SMO library. I don't care if it's the version from Server 2005 (9.0), 2008 (10.0) or 2008 R2 (probably 10.5, didn't check). The SMO library is installed together SQL Server, so I can safely assume that on any system with SQL Server installed, some version of the SMO library is available as well.

Unfortunately, the SMO libraries are strongly-named: If I add a reference to SMO 9.0 in my project, it will fail (FileNotFoundException) if only SMO 10.0 is present on the customer's system, and vice versa.

Is there some way to tell the compiler that any version of the library is fine for me? Or do I really have to distribute 3 identical versions of my tool, each compiled to a different version of the SMO?


Disclaimer: I do know that the SMO libraries (and the libraries required by the SMO libraries) can be redistributed. But there's a big difference between (a) one slim 100KB standalone EXE and (b) a full-blown setup package that installs a whole bunch of prerequisites.

Disclaimer 2: I am aware of the following duplicates:

The solutions provided do not fit, however. In question 1, the developer has control over the referenced DLL (which I do not); in question 2, the developer has control over the target systems (which I do not either).

Killingsworth answered 29/7, 2011 at 14:51 Comment(3)
Interesting question. Knowing both nothing on this subject, and next to nothing on Dependency Injection, could it be achieved that way? I mean, the assembly in their varying versions are a dependency that you intend to inject? Just an idea..Dorsoventral
Sorry, my answer was completely wrong, Specific Version is a build-time only setting.Mannuela
@Dorsoventral Interesting idea, but there is one stumbling block. DI would depend on some type (e.g. an interface) that can be the basis for injection. Given the 3rd party nature of this, the type would live in the 3rd party library - bang - you've got a dependency on the specific version of the 3rd party library.Horsemint
H
6

As I know it is not possible to remove the dependency on exact version. That is one of reasons why strong names exist - to avoid version mismatch. Internals or even public interfaces of the assembly can change among version and you can find that new version is not backward compatible with the old one. Because of that .NET looks for version used during compilation to make sure that application works correctly.

If third party decides that their new version is backward compatible and if they deploy assembly to GAC they can add publisher policy which will do redirect automatically.

If you decide that you want to force loading another assembly you can use the approach mentioned by @chibacity or implement handler for AppDomain.CurrentDomain.AssemblyResolve. This event fires when .NET is not able to find referenced assembly and you can implement your own logic to find it and load it by calling Assembly.LoadFrom. In such case it is completely up to you which version you load.

Haemic answered 29/7, 2011 at 18:37 Comment(2)
Footnote: if unsigned assembly A depends on signed B v1 via a strong name reference to B, by handling the AssemblyResolve event you can make A use B v2 but only if B v2 is also signed. If B v2 is not signed, A will refuse to use it.Dorotheadorothee
NB by "also signed" I mean with the same publicKeyToken. I guess the idea is that the replacement DLL must come from the same author.Dorotheadorothee
H
6

You can use Assembly Binding Redirection.

For example:

 <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Telerik.Web.UI" publicKeyToken="121fae78165ba" />
        <bindingRedirect
               oldVersion="2010.0.0.1"
               newVersion="2011.1.315.40" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

Update

I see from your comment that we have to think about this in reverse a little.

A very promising approach, but, unfortunately, it merely replaces the dependency on version X with a (strong) dependency on version Y. I still have a dependency on one particular version.

I did some experiments where I compiled against a version of an assembly: 4.0.0.0, but wanted to make sure it would load that version, plus some selected older versions. In this way you are not dependent on any single version but against any of the versions you have configured.

The following will ensure that VersionedAssembly will be loaded if any of the following versions are on the system: 4.0.0.0, 3.0.0.0, 2.0.0.0, 1.0.0.0.

   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <dependentAssembly>
            <assemblyIdentity name="VersionedAssembly" publicKeyToken="20d85e" />
            <bindingRedirect oldVersion="4.0.0.0" newVersion="1.0.0.0"/>
         </dependentAssembly>
         <dependentAssembly>
            <assemblyIdentity name="VersionedAssembly" publicKeyToken="20d84e" />
            <bindingRedirect oldVersion="4.0.0.0" newVersion="2.0.0.0"/>
         </dependentAssembly>
         <dependentAssembly>
            <assemblyIdentity name="VersionedAssembly" publicKeyToken="20d84e" />
            <bindingRedirect oldVersion="4.0.0.0" newVersion="3.0.0.0"/>
         </dependentAssembly>
      </assemblyBinding>
   </runtime>
Horsemint answered 29/7, 2011 at 14:54 Comment(7)
A very promising approach, but, unfortunately, it merely replaces the dependency on version X with a (strong) dependency on version Y. I still have a dependency on one particular version.Killingsworth
@Killingsworth Yes, but you would point all the old versions to the latest version. What you end up with is W,X,Y,Z versions pointing to Z. But, what's more important are these are the supported and tested versions. If a different version exists, will it even be compatible with the version you actually compiled against?Horsemint
@Killingsworth You also have the option of using wildcard style versioning e.g. oldVersion="0.0.0.0-65535.65535.65535.65535".Horsemint
Unfortunately, I'm unable to reproduce your result. I've compiled to application with a reference to v9. If I add a redirect 9->10 in the config file, it works only on systems with 10 installed. Apparently, only the first matching dependentAssembly directive found is used: If I add a redirect 9->9 in front of 9->10, it works only on systems with 9 installed.Killingsworth
@Killingsworth I will recheck this tonight.Horsemint
I solved my problem in the meantime using Ladislav's suggestion. I'm still interested in your assembly redirection approach, but just out of scientific curiosity (since my problem has been solved). I just thought I'd mention that before you put too much effort into this.Killingsworth
@Killingsworth Ah yes, am familiar with the technique hadn't thought of using it this way, much better than my suggestion. Can still constrain which assemblies to fall back to which is good.Horsemint
H
6

As I know it is not possible to remove the dependency on exact version. That is one of reasons why strong names exist - to avoid version mismatch. Internals or even public interfaces of the assembly can change among version and you can find that new version is not backward compatible with the old one. Because of that .NET looks for version used during compilation to make sure that application works correctly.

If third party decides that their new version is backward compatible and if they deploy assembly to GAC they can add publisher policy which will do redirect automatically.

If you decide that you want to force loading another assembly you can use the approach mentioned by @chibacity or implement handler for AppDomain.CurrentDomain.AssemblyResolve. This event fires when .NET is not able to find referenced assembly and you can implement your own logic to find it and load it by calling Assembly.LoadFrom. In such case it is completely up to you which version you load.

Haemic answered 29/7, 2011 at 18:37 Comment(2)
Footnote: if unsigned assembly A depends on signed B v1 via a strong name reference to B, by handling the AssemblyResolve event you can make A use B v2 but only if B v2 is also signed. If B v2 is not signed, A will refuse to use it.Dorotheadorothee
NB by "also signed" I mean with the same publicKeyToken. I guess the idea is that the replacement DLL must come from the same author.Dorotheadorothee
K
0

Based on Ladislav's suggestion of overriding AssemblyResolve, I was able to come up with the following solution:

Sub Main()
    ...
    Dim assembly = GetSmoAssembly()
    If assembly Is Nothing Then
        ' no suitable Version of SMO found
        ...
    Else
        ' load correct assembly
        Dim returnAssembly As ResolveEventHandler = Function() assembly
        AddHandler AppDomain.CurrentDomain.AssemblyResolve, returnAssembly
        TestSmo()
        RemoveHandler AppDomain.CurrentDomain.AssemblyResolve, returnAssembly
    End If
    ...
End Sub

Private Function GetSmoAssembly() As Assembly
    Try
        Return Assembly.Load("Microsoft.SqlServer.Smo, Version=9.0.242.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91")
    Catch ex As FileNotFoundException
    End Try

    Try
        Return Assembly.Load("Microsoft.SqlServer.Smo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91")
    Catch ex As FileNotFoundException
    End Try

    Return Nothing
End Function

' Needs to be in a separate method, see https://mcmap.net/q/507019/-how-to-detect-a-missing-net-reference-at-runtime/87698
Private Sub TestSmo()
    Dim srv As New Smo.Server()
End Sub

Note: Using Assembly.Load directly in the AssemblyResolve event handler is not a good idea, since it recursively calls the event handler if Load fails.

Killingsworth answered 17/10, 2018 at 12:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.