Swap strongly signed assembly at runtime - c#

I've a project which references to a 3th party library. The manufacturer of the library releases new versions on a regular basis. My ultimate goal is to be able to choose at runtime which version must be used during execution.
For now, I'm trying to load an assembly at runtime that has a higher version number than the one that was used during compilation. I compile my project, replace the 3th party library with a newer version and try to run the application. This is were I come into problems. I get an error saying:
"The located assembly's manifest definition does not match the assembly reference"
I was not supprised to see this error, since the assembly is strongly signed. I looked for ways to bypass this, but without any luck so far.
I thought that Binding Redirection could help me, but it has the disadvantage that you can't specify a range of "newversions". Any combination should work, older with newer version and vice versa.
<bindingRedirect oldVersion="1.2.7.0" newVersion="1.2.8.0" />
http://msdn.microsoft.com/en-us/library/eftw1fys.aspx
I've also looked at dynamic invocation, but then I loose type safety (my code extensively uses the types defined in the 3th party assembly). --> removing reference is difficult.
Removing the public key in the reference of the project definition didn't helped either. Any other assembly version then the one used during compilation fails.
<Reference Include="<assemblyname>">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Dependencies\<manufacturer>\1.2.7.0\<assemblyname>.dll</HintPath>
</Reference>
Note:
The logic to load and unload assemblies at runtime already exists.
There's no interface available for the 3th party library

You can 'fix' this issue (work around is probably a better description) by handling the AssemblyResolve event on the AppDomain. Handling this event gives your code a chance to provide the assembly that needs to be loaded when all the normal methods of finding an assembly have failed to locate a matching version.
Inside the event handler you will need to check the ResolveEventArgs.Name property to see if the assembly is the one you need to load. The Name property will be the long name of the assembly being loaded - i.e. 'Widget.Net, Version=1.2.3.4, Culture=neutral, PublicKeyToken=xxxxxxxxxxx'.
Once the correct load request has been identified, simply load the correct version of the assembly (Assembly.LoadFrom, Assembly.Load, Assembly.LoadWithPartialName) and return it from the event handler. Note that Assembly.LoadWithPartialName is marked as obsolete, but seems to be the only way to handle this issue if the target assembly is in the GAC.
// application initialization
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.StartsWith("Widget.Net, Version="))
{
Assembly result = Assembly.LoadFrom("Widget.Net.dll");
return result;
}
return null;
}
It's important to be aware that while this 'solves' the problem, it's not a good solution in any sense. It completely subverts the normal version and strong name checking of assemblies used by the .Net framework. It's something that you do when you have no other option because (as in the question) a vendor has messed up versioning their assembly. You are also relying on their being no breaking changes to the classes defined in the assembly between the referenced version and the loaded version - i.e. that all the classes, properties, methods etc that you use still exist and have the same signatures.
To maintain at least a pretence of security, it would be a very good idea to at least check in the AssemblyResolve event handler that:
The assembly version loaded is newer than the version requested
The public key tokens of the loaded and requested assemblies match

Related

Could not load type `A` from assembly `Not.Containing.Type.A`

EDIT: I haven't mentioned one important thing - application which loads my assemblies are actually not in same directory (as other dlls). After fiddling with Fusion log, I noticed that loading of dll are behaving differently than I previously thought. (Yup, I should first RTFM, shame on me)
C:\Test\appLoadingStuff.exe
C:\Lib\Acme.Application.dll
C:\Lib\Acme.Data.dll
...
.NET is probing application base (besides GAC and stuff; directory where loading app is - C:\Test\), and does not care about location where loaded dll are stored (other directory).
While using the .NET framework I found myself getting a ReflectionTypeLoadException exception:
System.TypeLoadException
Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
Could not load type 'Acme.Data.Foo' from assembly 'Acme.Data.Dao, Version=1.1.0.4, Culture=neutral, PublicKeyToken=null'.":"Acme.Data.Foo
I have, for simplicity, 3 assemblies:
Acme.Application my main assembly
Acme.Data my data objects (referenced by 1st one)
Acme.Data.Dao my data access objects (referenced by 1st one)
And there's another application, which actually loads my main assembly. All .dll files are in same directory.
As you could expect, type Acme.Data.Foo is living in assembly Acme.Data. Despite this, .NET is trying to find it in another assembly Acme.Data.Dao - which fails, as the type is not there.
I can't figure out what is making .NET looking for that particular type in wrong assembly. Exception is thrown immediately when accessing types on assembly:
System.Reflection.RuntimeAssembly assembly = Assembly.LoadFile("C:\Lib\Acme.Application.dll")
var types = assembly.GetTypes(); // -> explodes
When I tried to check referenced assemblies using:
assembly.GetReferencedAssemblies()
I can clearly see my desired assembly in list.
There's no assembly redirect (and as far I know, this affects only version). Versions of assemblies are correct.
What else should I look for?
As I stated on edited question, I missed to mention very important property of what I was trying to do. My assembly was loaded by another application which was not in same path as loaded assemblies.
.NET is probing directory of running application, not directories where loaded dlls are. If one needs to handle such case, it is necessary to implement event handler AppDomain.AssemblyResolve to take care of dependencies of loaded dll.
More reading:
How the Runtime Locates Assemblies (notice section Locating the Assembly through Probing)
AppDomain.AssemblyResolve Event
Thanks to anyone who participated on this question and sorry I didn't share such crucial detail (tbh, I didn't expect this little detail matters - now i know).

Load an assembly from another location not in the GAC

I have several applications which are required to use the same assembly. This assembly may be changed regularly, and can be installed by different MSIs. For this reason, I do not want to put it in the GAC, it can become a deployment nightmare over time.
If I turn the CopyLocal attribute for this assembly to NO in the application, how do I tell the runtime where to look for the assembly?
E.g. the application is loaded in C:/Program Files/<some directory>/bin
The DLL is in C:/<some other directory>
Is it thus possible to load the assembly in this way?
I've had a look at <codebase>, but I'm not sure the assembly can be strongly signed. And probing seems to work only for the private paths specified as subdirectories of the application?
Please let me know. Thank you.
Use Assembly.LoadFrom to load the assembly into memory, then you can use Activator.CreateInstance to create an instance of your preferred type. You need to user reflection for this:
Assembly assembly = Assembly.LoadFrom("c:\\path\\MyDll.dll");
Type type = assembly.GetType("MyClass");
object instanceOfMyType = Activator.CreateInstance(type);
Take a look on reflection in order to create instances with parameters.
Why do you want to set CopyLocal to "No"? The usual way to avoid "DLL hell" (aka "deployment nightmare") is to ensure that DLL dependencies are copied into the same directory with your program. IMHO, this is the simplest, most straightforward way to guarantee you are loading the DLL you want.
Note also that if you sign the DLL, install it in the GAC, and then in your own program require a specific version (or a minimum version, depending on your needs), that should also address the "DLL hell" scenarios. I.e. the presence of other versions of the DLL won't conflict, because you've required a specific version and .NET can reliably distinguish the correct version from an incorrect one.
Barring those approaches...
It's not clear what your exact requirements are. However, if you are trying to provide a way to identify an assembly that's not in the usual assembly-loading paths, there are at least a couple of mechanisms you can use.
One way is to use ApplicationBase and PrivateBinPath to control how .NET searches for your assemblies.
Another way is to handle the System.AppDomain.AssemblyResolve event.
That event will be raised any time .NET tries to load a referenced assembly and can't find it. Your handler can then perform whatever search it needs to (or just use a fixed path for that matter), load the assembly itself (e.g. using Assembly.LoadFrom()), and then return that via the event's arguments object.
Note that the AssemblyResolve event is only raised if .NET can't find a DLL to load. So that would not be an appropriate solution if it's not tolerable to have a different instance of the DLL that satisfies the reference requirements for the program of that DLL.

When should we not create Assembly's strong name? What are the disadvantages of "strong named assembly"?

I have a project, i.e. library.exe. In this I have referenced an assembly (logging.dll ver 1.0.3.0) and I have given this assembly a strong name.
Now suppose I changed a method in logging.dll and made version 1.0.4.0.
Now when I copy/replaced the old DLL with this new one I got an exception.
I know that exception is because I have changed version number of the DLL. As it was a strong name DLL it's not allowed unless I rebuilt library.exe.
What I want to say with above story is
Use strong name with assembly only when we have to add it to GAC.
If we have an application where individual assemblies requires updating do not use strong named assemblies.
Am I correct with point 1 and 2?
When should strong named assemblies not be used?
What are the disadvantages of "strong named assembly"?
It is only really needed if you want to place your assemblies in the GAC, but it also helps against tampering. It is fairly easy to change the code in an assembly so this gives bad people an advantage. When you are using strong named assemblies you are signing it with a private key only you have. People could still change your assembly, but they can't give it the same strong name, because they do not have your private key. In that case .Net refuses the assemly tamperd with. When they sign your assembly with a new private key the .Net still refuses to load it since the identity of the assembly has changed.
There are several ways to solve the versioning problem. When your application wants to load a v1 assembly you could tell it to look voor a v2 anyway. See here for more information. The other option would be not to change the Assembly Version at all, but to change the File Version of the assembly only. For .Net the assemblies are the same, but you and your installer can still see which one is newer. See the AssemblyFileVersion attribute.
Both correct.
You don't need a StrongName if your signing the assembly afterwards, like with a real certificate.
In my oppinion strong naming is not realy worth anything, see this link for example. Well you need it for placing an assembly into the GAC but that's it.

How to provide a fallback assembly instead of the one that can't be loaded?

At runtime, if a referenced assembly fails to load with e.g. "Strong name validation failed" (because it's test-signed), is there a way to provide a substitution assembly from another path that is real-signed?
I tried subscribing to AppDomain.CurrentDomain.AssemblyResolve, but it doesn't get fired, because the "bad" assembly technically exists, it just can't be loaded.
Is there a generic way to provide a fallback assembly when an assembly can't be loaded?
I think you can just call assembly.LoadFrom to load the assembly of your choice with practically no security checks. We us this a lot at the start of our app so we can better deal with other assemblies version change.
Also look at Assembly.LoadFrom Method (String, Evidence, Byte[], AssemblyHashAlgorithm)
looks like you can control passing in the hash as well as the hash algorithm.
What triggers the load attempt? IOW do you call Assembly.Load or this is a result of type resolution attempt? If it is the latter you can try to play with the AppDomain TypeResolve event, if the former - you can add additional logic to your call to the Assembly.Load.
If you load the Assembly manually though make sure you load it with Assembly.Load - not Assembly.LoadFrom. There are subtle differences in type resolution depending on what context assembly is loaded into
Looks like what I want is impossible. I decided to go another route. We'll have to modify the build system to conditionally link to signed binaries instead of test-signed binaries at compile time.
Thanks everyone for the suggestions though!
there is a standard way to find an assembly in case the application fails to do so:
// register on assembly resolve exception
AppDomain.CurrentDomain.AssemblyResolve += ResolveEventHandler;
// try to load the assembly yourself
private static Assembly ResolveEventHandler(object sender, ResolveEventArgs args)
{
return Assembly.Load(some_location);
}

C# Plugin Architecture with Strong Names: A Misunderstanding

I have a server executable that talks to Active Directory to retrieve user information. In addition to AD, this exe allows customers to write their own plugins to talk to custom user directories.
This executable is strongly named.
Is the following a true statement:
In order for a strongly named
assembly to load another assembly, the
loaded assembly must also be signed
with the same key.
The following code returns null, if the assembly is not strongly signed, with no error indicating the assembly was not properly signed. Note, that if I do sign the assembly, I get an instance of IService. Which leads me to believe that loaded assemblies must be strongly signed.
Assembly assembly = Assembly.LoadFrom(path);
foreach (Type t in assembly.GetTypes())
{
if (t.GetInterface(typeof(IService).FullName) != null)
{
return (Activator.CreateInstance(t) as IService);
}
}
So, does this mean if you have a strongly signed assembly, and support assembly plugins, they must also be signed--plugin writers must sign them with the same key? That doesn't sound right.
Finally, Say I have an assembly that implements the IService interface, but also references an assembly, that references yet another assembly, each signed with a different key. What happens when I try to load? Should they all be signed by the same key?
The following statement is correct:
In order for a strongly named assembly
to load another assembly, the loaded
assembly must also be signed with the same key.
From MSDN:
If the strong-named assembly then
references an assembly with a simple
name, which does not have these
benefits, you lose the benefits you
would derive from using a strong-named
assembly and revert to DLL conflicts.
Therefore, strong-named assemblies can
only reference other strong-named
assemblies.
Edit: D'oh! Though my answer is true, as P Daddy points out, it's irrelevant!
Using reflection to Load a weakly-named assembly is not the same thing as referencing one, and is not restricted the same way.
I recreated your code (or at least a close approximation), with the following assemblies:
Interface.dll (signed, contains IService)
Loader.exe (signed, a console app that takes a path, uses your code to load and return the first IService it finds in the assembly specified by that path, then calls an IService method)
Plugin.dll (not signed, contains an IService implementation)
Next I added a Plugin.dll reference to Loaded.exe and tried to access its IService implementation, which failed as expected with the following message: "Assembly generation failed -- Referenced assembly 'Plugin' does not have a strong name."
Finally, I ran the console app, passed it the name of the weakly-named Plugin.dll, and it worked just fine.
There seems to be something else afoot. Scott Hanselman has blogged about the vagaries of
dynamic assembly loading on several occasions, and he points to Suzanne Cook's blog for authoritative details about the topic.
If your assembly is strongly typed signed, I think any assembly you load must also be strongly typed signed...but not necessarily signed with the same key.
The statement is wrong - referenced assemblies must be signed, but not necessarily with the same key.
When you reference a strong-named assembly, you expect to get certain benefits, such as versioning and naming protection. If the strong-named assembly then references an assembly with a simple name, which does not have these benefits, you lose the benefits you would derive from using a strong-named assembly and revert to DLL conflicts. Therefore, strong-named assemblies can only reference other strong-named assemblies.
(Taken from MSDN Library)

Categories