I ended up having to write my own assembly cache...
Here's the basics of how it works, in order to help others if they need to implement their own assembly cache:
- The cache is a folder with a corresponding wrapper class in the application.
- The method to install an assembly in the cache returns a string 'token' (the name under which the assembly is installed) which the caller can save and use to retrieve the assembly later.
- The method to retrieve an assembly takes the token and returns an object which encapsulates the cached assembly's information.
When installing an assembly, first compare an incoming assembly's bits to the bits of each of the already cached assemblies and determine if the assembly is already installed (if all the bits are the same, they must obviously do the same thing). If the assembly is already installed, then return the name of the previously cached assembly. If it is not already installed, allow multiple versions to be installed by finding an available variant of the assembly name in the cache folder (e.g assembly.dll, assembly - (2).dll, assembly - (3).dll, etc), cache the assembly in the folder, and return its cached name to the caller.
As far a security and strong naming goes, it does not make sense in my application to do strong name verification, as it would be what Bruce Payette calls "lawn gnome mitigation" (a security countermeasure which an attacker could merely walk around). If an attacker decided to poison an assembly, he would also have access to the location where the strong name would be stored and could easily change it to his strong name. It ends up being the user's responsibility to use assemblies only from sources he trusts. If the user installs a malicious assembly, it is his fault, not mine. Still, to provide a little more protection for the user, the use of external assemblies in my application will disabled by default. For other applications, though, it might make sense to implement strong name verification as an extra layer of security.