Imagine I have a solution with some projects. In the project Proj1.BL I have the abstract class MyAbstractClass, and (strong requirement) I should inherit from it only in another solutions. All of them will contain only one class (e.g. DerivedClass : MyAbstractClass), will have references to corresponding projects from the first solution(Proj1.BL, Proj1.DataModel, etc.) and will be compiled to DLLs (Class Libraries). And in conclusion, these DLLs (another strong requirement) will be stored in $(OutDir of first solution)\Externals.
My question is: how should I load these assemblies within the first solution correctly? I've tried someway like this:
public static Type LoadType(string fileName)
{
if (fileName == null) throw new ArgumentNullException("fileName");
fileName = Path.Combine("Externals", fileName);
if (!File.Exists(fileName)) throw new ApplicationException("File does not exist.");
return
Assembly.LoadFrom(fileName)
.GetTypes()
.SingleOrDefault(_ => _.IsSubclassOf(typeof (MyAbstractClass)));
}
But I'm continuing to get
ReflectionTypeLoadException. Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
LoaderException is about that CLR couldn't load type Proj1.BL.MyAbstractClass from Proj1.BL, Version=*.*.*.*, Culture=neutral, PublicKeyToken=null.
But code that I'm running is in Proj1.BL. What's wrong?
Thanks for any suggestions.
Eh, the answer was easily than expected. There was a version mismatch when Proj1 assemblies' versions' values were autoincremented after each rebuild, but DLL expected to find Proj1 assemblies' versions' that were specified at compile-time.
Related
Setup
I am working on a C# project without external dependencies, named "Hopper". I have split it up into a couple of different modules, each contained within a corresponding sub-folder. The relevant ones for the problem are the following:
Utils/Hopper.Utils.csproj
Shared/Hopper.Shared.csproj
Core/Hopper.Core.csproj, which references Utils and Shared, more details later
Mine/Hopper.Mine.csproj, which references Core
All of these target .NET 4.8, including Hopper.Core.
I have set the AssemblyName property for each of the projects to the corresponding string. For example, the Hopper.Utils.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net4.8</TargetFramework>
<AssemblyName>Hopper.Utils</AssemblyName>
</PropertyGroup>
</Project>
Hopper.Core.csproj references the other projects via ProjectReference, like so:
<ItemGroup>
<ProjectReference Include="..\Utils\Hopper.Utils.csproj" />
<ProjectReference Include="..\Shared\Hopper.Shared.csproj" />
</ItemGroup>
Hopper.Mine.csproj, created for testing purposes, references core like this:
<ItemGroup>
<ProjectReference Include="..\Core\Hopper.Core.csproj" />
</ItemGroup>
The Hopper.Core project contains some types, both classes and structs. Some source files have been autogenerated by a tool and contain #pragma warning disable at the top. (Removing it did not help).
The two projects referenced by Hopper.Core just contain some types and attributes, used by Core, so nothing of interest to the problem. They do not reference any other projects or dll's.
The problem
I am able to compile Hopper.Mine by running dotnet build in the sub-folder with the project file. I am able to see the output dll's of the referenced sub-projects in bin/Debug/net4.8/Hopper.Whatever.dll under each project's folder. Intellisense in VSCode also works correctly and does not report any errors.
Hopper.Mine has a single class with a Main function, created for a test. It lists the types of Hopper.Core that have loaded successfully, and reports the ones that have not been loaded:
using System;
using System.Reflection;
using System.Text;
namespace Hopper.Mine
{
public class Program
{
public static void Main(string[] args)
{
Assembly lib = typeof(Hopper.Core.Action).Assembly;
Type[] types;
try
{
types = lib.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
Console.WriteLine(exSub.Message);
}
types = ex.Types;
}
foreach (Type type in types)
{
if (type != null)
Console.WriteLine(type.FullName);
}
}
}
}
Running this code, I can see a bunch of problematic types, some of which repeat, and the rest of types that have loaded successfully:
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Dig' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Move' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Push' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
... more errors here ...
Hopper.Core.DirectedPredict
Hopper.Core.UndirectedPredict
Hopper.Core.DirectedDo
... more types here ...
Hopper.Core.Stat.Attack+Source+Resistance
Hopper.Core.Stat.Push+Source+Resistance
To be noted 4 things:
The types that failed to load are all structs and derive from IStat.
The source files with these types have been autogenerated by a tool (other files generated by the tool load without errors).
The types do not reference any types from Hopper.Shared or Hopper.Utils.
Some of the types have nested structs, up to 3 level deep, some of the deeper ones seem to have loaded successfully.
I am pretty sure that the dll output from the Hopper.Core's output folder is identical to the one in the output folder of Hopper.Mine. I am also pretty sure that the right dll is linked against when I start the program (I've tried debugging with VS, where I can see which dll is being linked against and the paths matched).
I have tried removing AssemblyName properties, renaming csproj files from Hopper.Whatever.csproj to Hopper_Whatever.csproj, I've obviously tried cleaning up any previous output dll's and building from scratch, none of which helped. I have also been able to reproduce this on a different machine with the exact same results.
I have tried looking at the Hopper.Core dll with a decompiler, ILSpy. I am able to see the "missing" types as they are in code.
I have searched on google for hours for a problem like mine, to no avail. I have found many cases of people setting up their project files incorrectly, due to which they unexpectedly link against a different version of the dll than they expected. For some, such dependency showed up in requirements of one of the referenced third party projects. Some blame GAC for providing incorrect version of the dll. However, I'm pretty sure that my projects are set up right (+ the set up is pretty simple and no third party projects are being referenced) and I am referencing the right dll's (since I can see my types in it). It kind of seems like the types are 'declared' in the dll, but not 'defined' (sort of), so the dll's turn out corrupted. But then why would the decompilation not fail? So I'm currently completely stuck on this.
Loading the assembly manually
I have tried linking against the dll's dynamically, by their names. Listing the types imported from Hopper.Shared and Hopper.Utils works, however, when I try it with Hopper.Core, I get the same output as the example above.
To achieve this, I have copied the previous dll's from the output over to a new folder, removed the reference from Hopper.Mine, compiled the new code with dotnet build, after which moved the executable to the new folder and finally ran it in the console. The code:
using System;
using System.Reflection;
using System.Text;
namespace Hopper.Mine
{
public class Program
{
public static void Main(string[] args)
{
Assembly shared = Assembly.LoadFrom("Hopper.Shared.dll");
Assembly utils = Assembly.LoadFrom("Hopper.Utils.dll");
Assembly core = Assembly.LoadFrom("Hopper.Core.dll");
Type[] types;
try
{
types = core.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
Console.WriteLine(exSub.Message);
}
types = ex.Types;
}
foreach (Type type in types)
{
if (type != null)
Console.WriteLine(type.FullName);
}
}
}
}
To reproduce
If it helps, you may try running my code. Here's the github link to the current state of the project. In order to run it, you need:
.NET 4.8 installed.
Clone the repository from the link above.
Some code is still missing. You need to run the project in the folder Meta to generate it. Just run dotnet run in the Meta folder.
Now you can run the Mine subproject.
Found out the reason. There is a bug in the CLR type system that has been around for 6 years. Basically, fields (either static or non-static) of type of a generic struct declared in other structs cause this behavior. See this github thread for more details.
In my case, I have been able to make a shorter version that illustrates the bug.
public struct Index<T>
{
}
public struct Test
{
public static Index<Test> Index;
}
Use the following code to test the program:
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(typeof(Test).Assembly.GetTypes());
}
}
When the code is run, the following error is thrown:
Unhandled Exception: System.TypeLoadException: Could not load type 'Hopper.Mine.Test' from assembly 'Hopper.Mine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
at Hopper.Mine.Program.Main(String[] args)
However, there is an easy sort of workaround for this. Changing Test from struct to class works:
// This works!
public struct Index<T>
{
}
public class Test
{
public static Index<Test> Index;
}
That may not be acceptable in some cases. So here is another workaround — storing the static field in an array and accessing it with a property also works:
public struct Index<T>
{
}
public struct Test
{
public static Index<Test> Index { get => index[0]; set => index[0] = value; }
public static Index<Test>[] index = new Index<Test>[1];
}
The following experimental codes/projects are using netcore 2.0 and netstandard 2.0 in VS2017. Let's say I have two versions of a third party dll v1.0.0.0 and v2.0.0.0, which contains only one class Constants.cs.
//ThirdPartyDependency.dll v1.0.0.0
public class Constants
{
public static readonly string TestValue = "test value v1.0.0.0";
}
//ThirdPartyDependency.dll v2.0.0.0
public class Constants
{
public static readonly string TestValue = "test value v2.0.0.0";
}
Then I created my own solution named AssemblyLoadTest, which contains:
Wrapper.Abstraction: class library with no project references
namespace Wrapper.Abstraction
{
public interface IValueLoader
{
string GetValue();
}
public class ValueLoaderFactory
{
public static IValueLoader Create(string wrapperAssemblyPath)
{
var assembly = Assembly.LoadFrom(wrapperAssemblyPath);
return (IValueLoader)assembly.CreateInstance("Wrapper.Implementation.ValueLoader");
}
}
}
Wrapper.V1: class library with project reference Wrapper.Abstractions and dll reference ThirdPartyDependency v1.0.0.0
namespace Wrapper.Implementation
{
public class ValueLoader : IValueLoader
{
public string GetValue()
{
return Constants.TestValue;
}
}
}
Wrapper.V2: class library with project reference Wrapper.Abstractions and dll reference ThirdPartyDependency v2.0.0.0
namespace Wrapper.Implementation
{
public class ValueLoader : IValueLoader
{
public string GetValue()
{
return Constants.TestValue;
}
}
}
AssemblyLoadTest: console application with project reference Wrapper.Abstraction
class Program
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
{
Console.WriteLine($"AssemblyResolve: {e.Name}");
if (e.Name.StartsWith("ThirdPartyDependency, Version=1.0.0.0"))
{
return Assembly.LoadFrom(#"v1\ThirdPartyDependency.dll");
}
else if (e.Name.StartsWith("ThirdPartyDependency, Version=2.0.0.0"))
{
//return Assembly.LoadFrom(#"v2\ThirdPartyDependency.dll");//FlagA
return Assembly.LoadFile(#"C:\FULL-PATH-TO\v2\ThirdPartyDependency.dll");//FlagB
}
throw new Exception();
};
var v1 = ValueLoaderFactory.Create(#"v1\Wrapper.V1.dll");
var v2 = ValueLoaderFactory.Create(#"v2\Wrapper.V2.dll");
Console.WriteLine(v1.GetValue());
Console.WriteLine(v2.GetValue());
Console.Read();
}
}
STEPS
Build AssemblyLoadTest in DEBUG
Build Wrapper.V1 project in DEBUG, copy files in Wrapper.V1\bin\Debug\netstandard2.0\ to AssemblyLoadTest\bin\Debug\netcoreapp2.0\v1\
Build Wrapper.V2 project in DEBUG, copy files in Wrapper.V2\bin\Debug\netstandard2.0\ to AssemblyLoadTest\bin\Debug\netcoreapp2.0\v2\
Replace FULL-PATH-TO in AssemblyLoadTest.Program.Main with the correct absolute v2 path that you copied in step 3
Run AssemblyLoadTest - Test1
Comment FlagB line and uncomment FlagA line, run AssemblyLoadTest - Test2
Comment AppDomain.CurrentDomain.AssemblyResolve, run AssemblyLoadTest - Test3
My results and questions:
Test1 succeeds and prints v1.0.0.0 and v2.0.0.0 as expected
Test2 throws exception at v2.GetValue()
System.IO.FileLoadException: 'Could not load file or assembly
'ThirdPartyDependency, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=null'. Could not find or load a specific file.
(Exception from HRESULT: 0x80131621)'
Question1: Why LoadFile with absolute path works as expected, while LoadFrom with relative path not working, meanwhile LoadFrom with relative path works for v1.0.0.0 in the first if statement?
Test3 fails with the same exception above at the same place, here my understanding is CLR locates the assemblies with the following priority rule:
Rule1: Check if AppDomain.AssemblyResolve is registered (highest priority)
Rule2: Otherwise check if the assembly is loaded.
Rule3: Otherwise search the assembly in folders(can be configured in probing and codeBase.
Here in Test3 where AssemblyResolve is not registered, v1.GetValue works because Rule1 and Rule2 is N/A, AssemblyLoadTest\bin\Debug\netcoreapp2.1\v1 is in Rule3 scan candidates. When executing v2.GetValue, Rule1 is still N/A, however Rule2 is applied here (if Rule3 is applied, why exceptions?)
Question2: Why the version is ignored even Wrapper.V2 reference ThirdPartyDependency.dll using
<Reference Include="ThirdPartyDependency, Version=2.0.0.0">
<HintPath>..\lib\ThirdPartyDependency\2.0.0.0\ThirdPartyDependency.dll</HintPath>
</Reference>
Great answer from Vitek Karas, original link here.
Kind of unfortunately all of the behavior you describe is currently as designed. That doesn't mean it's intuitive (which it's totally not). Let me try to explain.
Assembly binding happens based on AssemblyLoadContext (ALC). Each ALC can have only one version of any given assembly loaded (so only one assembly of a given simple name, ignoring versions, culture, keys and so on). You can create a new ALC which then can have any assemblies loaded again, with same or different versions. So ALCs provide binding isolation.
Your .exe and related assemblies are loaded into a Default ALC - one which is created at the start of the runtime.
Assembly.LoadFrom will try to load the specified file into the Default ALC - always. Let me stress the "try" word here. If the Default ALC already loaded assembly with the same name, and the already loaded assembly is equal or higher version, then the LoadFrom will succeed, but it will use the already loaded assembly (effectively ignoring the path you specified). If on the other hand the already loaded assembly is of a lower version then the one you're trying to load - this will fail (we can't load the same assembly for the second time into the same ALC).
Assembly.LoadFile will load the specified file into a new ALC - always creates a new ALC. So the load will effectively always succeed (there's no way this can collide with anything since it's in its own ALC).
So now to your scenarios:
Test1
This works because your ResolveAssembly event handler loads the two assemblies into separate ALCs (LoadFile will create a new one, so the first assembly goes to the default ALC, and the second one goes into its own).
Test2
This fails because LoadFrom tries to load the assembly into the Default ALC. The failure actually occurs in the AssemblyResolve handler when it calls the second LoadFrom. First time it loaded v1 into Default, the second time it tries to load v2 into Default - which fails because Default already has v1 loaded.
Test3
This fails the same way because it internally does basically exactly what Test2 does. Assembly.LoadFrom also registers event handler for AssemblyResolve and that makes sure that dependent assemblies can be loaded from the same folder. So in your case v1\Wrapper.V1.dll will resolve its dependency to v1\ThirdPartyDependency.dll because it's next to it on the disk. Then for v2 it will try to do the same, but v1 is already loaded, so it fails just like in Test2. Remember that LoadFrom loads everything into the Default ALC, so collisions are possible.
Your questions:
Question1
LoadFile works because it loads the assembly into its own ALC, which provides full isolation and thus there are never any conflicts. LoadFrom loads the assembly into the Default ALC, so if that already has assembly with the same name loaded, there might be conflicts.
Question2
The version is actually not ignored. The version is honored which is why Test2 and Test3 fail. But I might not understand this question correctly - it's not clear to me in which context you're asking it.
CLR binding order
The order of Rules you describe is different.
It's basically:
Rule2 - if it's already loaded - use it (including if higher version is already loaded, then use that)
Rule1 - if everything fails - as a last resort - call AppDomain.AssemblyResolve
Rule 3 actually doesn't exist. .NET Core doesn't have a notion of probing paths or code base. It sort of does for the assemblies which are statically referenced by the app, but for dynamically loaded assemblies no probing is performed (with the exception of LoadFrom loading dependent assemblies from the same folder as the parent as described above).
Solutions
To make this fully work, you would need to do either:
Use the LoadFile along with your AssemblyResolve handler. But the problem here is that if you LoadFile an assembly which itself has other dependencies, you will need to handle those in your handler as well (you lose the "nice" behavior of LoadFrom which loads dependencies from the same folder)
Implement your own ALC which handles all dependencies. This is technically the cleaner solution, but potentially more work. And it's similar in that regard that you still have to implement the loading from the same folder if needed.
We are actively working on making scenarios like this easy. Today they are doable, but pretty hard. The plan is to have something which solves this for .NET Core 3. We're also very aware of the lack of documentation/guidance in this area. And last but not least, we are working on improving the error messages, which are currently very confusing.
Please read before marking as duplicate, already answered, on hold, or off topic.
First of all, I know there are SIMILAR question here, with very good answers. Unfortunately they do not answer my question.
My goal: to create a list of all of the types within an assembly that inherit from a specific interface. This assembly may be located at a local path or a UNC path.
My problem: First, I know how to load an assembly, both from a UNC and a local path, and I know how to use GetTypes(). Many of you may know that GetTypes() has it's own issue. Specifically, it will raise an exception if any of the Types inherit from another type that cannot be loaded. How to prevent ReflectionTypeLoadException when calling Assembly.GetTypes() for instance. The problem is, I don't need to load any of the dependencies. I only need to see if a type declared in the assembly inherits from an other specific type.
some code as an example. This is just the first step, to retrieve the types in the assembly:
Assembly asm = Assembly.Load(File.ReadAllBytes(assemblyPath));
Type[] myTypes = GetLoadableTypes(asm);
public static Type[] GetLoadableTypes(Assembly assembly)
{
// TODO: Argument validation
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
}
If any of the Types inside of the assembly inherit from a type that cannot be loaded, a ReflectionTypeLoadException exception will be thrown and they will show up as null inside of e.Types, so there is still no information on them. So I guess I can't use GetTypes still.
For completeness, here is a reference to my original question.
How to get Type information when ReflectionTypeLoadException is thrown from Assembly.GetType()
OK, made some headway with this and, unless I want to write my own metadata parser, it looks like this is what I am stuck with so I thought I'd share. I'll try to give the pros and cons.
This answer all revolves around using an event handler for the
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve
event. It should be pointed out that this event is only raised when an assembly's dependencies are being resolved. I didn't understand that at first but what it simply means is that the event is not fired for the assembly you are trying to load, but any dependencies in that assembly, which makes sense. Why raise that event when you have to have a starting point in the first place. For my solution, this event will be raised when I call .GetTypes().
Before I post the code let me explain the idea and what's good and bad about it. The idea is that, if I have a dependency, the event will be raised. In the event, I will first check a dictionary, created before hand, of all of the assemblies found in the same folder (including subfolders) as the original assembly. If the args.Name is found in that dictionary, then I read in that assembly and return it from the event. If it's not in the list, I attempt to load the assembly by name only, meaning the dependency assembly must either be part of the project or installed in the local GAC.
I already hinted at some of the cons here, so let's start with those.
Since I'm not just looking at metadata to tell me what dependencies are in the main assembly (in other words those dependencies must be loaded), if a dependency's assembly cannot be found with the original assembly (in the same folder or subfolders), and the dependency is not installed in the local GAC, or referenced in the project, GetTypes() will still throw a ReflectionTypeLoadException exception. It may seem like a rare case to run in to a situation where an dependency assembly is not located with the original assembly or installed in the GAC but I would give PRISM as an example of a situation where that may happen. PRISMs assemblies do not necessarily get installed into the GAC and are not always going to be copied local.
So what's good about this method? Well mainly that it gives you at least some way to handle 99% of the situations where you don't have a dependency's assembly in hand.
One or two final thoughts (actually gotchas) before the code. I use AssemblyName to create a dictionary bases on the assembly's full name without actually loading (either straight out or by ReflectionOnlyLoad) an assembly. There are two ways you can create an instance of AssemblyName, either by the constructor where you pass in the path to the assembly, or with a static method, GetAssemblyName(path). The constructor seems to only handle local paths where GetAssemblyName() can handle a UNC path. Those are the gotchas: don't load the assembly just to get the name and make sure you don't limit yourself to local paths.
Finally, some code:
public class TestClass
{
//This dictionary will hold a list of the full path for an assembly, indexed by the assembly's full name
private Dictionary<string, string> _allAsms = new Dictionary<string, string>();
/// <summary>
/// Tries to list all of the Types inside an assembly and any interfaces those types inherit from.
/// </summary>
/// <param name="pathName">The path of the original assembly, without the assembly file</param>
/// <param name="fileName">The name of the assembly file as it is found on disk.</param>
public void Search(string pathName, string fileName)
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
//Getting a list of all possible assembly files that exist in the same location as the original assembly
string[] filePaths = Directory.GetFiles(pathName, "*.dll", SearchOption.AllDirectories);
Assembly asm;
AssemblyName name;
if (!pathName.EndsWith("\\"))
pathName += "\\";
foreach (string path in filePaths)
{
name = AssemblyName.GetAssemblyName(path);
if (!_allAsms.ContainsKey(name.FullName))
_allAsms.Add(name.FullName, path);
}
//This is where we are loading the originaly assembly
asm = System.Reflection.Assembly.ReflectionOnlyLoad(File.ReadAllBytes(pathName + fileName));
Console.WriteLine("Opened assembly:{0}", fileName);
//And this is where the ReflectionOnlyAssemblyResolve will start to be raised
foreach (Type t in asm.GetTypes())
{
Console.WriteLine(" " + t.FullName);
//Get the interfaces for the type;
foreach (Type dep in t.GetInterfaces())
{
Console.WriteLine(" " + dep.FullName);
}
}
}
private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
if (_allAsms.ContainsKey(args.Name))
return Assembly.ReflectionOnlyLoad(File.ReadAllBytes(_allAsms[args.Name]));
else
return System.Reflection.Assembly.ReflectionOnlyLoad(args.Name);
}
}
To check if a type implements a specific interface you can use this:
typeof(yourInterface).IsAssignableFrom(type)
However, if the type could not be loaded there is no information available on it, and you have no chance to figure out whether it implements a specific interface or not.
My task: Find all Forms (WindowsForm or WPF, doesn't matter) in a dll or exe file and return that list. In theory that works (meaning: if I've an assembly with a WPF or WindowsForm my code manages to get all Forms, TextBoxes, Labels etc. ). When it comes to "real" assemblies it fails. I get FileNotFound exceptions when calling GetExportedTypes() for every "custom" assembly (.NET assemblies are found, no problems there). I already use GetReferencedAssemblies() to load the referenced assemblies (Reflection.Assembly.LoadFrom) and yes it does work (all assemblies are found and loaded into the AppDomain) but it doesn't help.
I checked the version numbers (they match), I copied my executable and the assembly into one directory with all referenced assemblies, doesn't work.
Here is my code, maybe someone figures out what I'm (obviously) doing wrong:
foreach (AssemblyName reference in selectedAssembly.GetReferencedAssemblies())
{
if (System.IO.File.Exists(
System.IO.Path.GetDirectoryName(selectedAssembly.Location) +
#"\" + reference.Name + ".dll"))
{
System.Reflection.Assembly.LoadFrom(
System.IO.Path.GetDirectoryName(selectedAssembly.Location) +
#"\" + reference.Name + ".dll");
}
else if (System.IO.File.Exists(#"C:\dll\" + reference.Name + ".dll"))
{
System.Reflection.Assembly.LoadFrom(#"C:\dll\" + reference.Name + ".dll");
}
else
{
System.Reflection.Assembly.ReflectionOnlyLoad(reference.FullName);
}
selectedAssembly.GetExportedTypes();
}
at first check if the referenced dll exists in the directory where the assembly is, if not check if it exists in C:\dll and if it's not there try and use the GAC. it does work and I've no errors from there but as soon as I come to GetExportedTypes it fails with a FileNotFound exception on the first custom library.
*edit 1 what do I mean by "real assemblies": I mean assemblies which are more complex and have references to non-standard-.NET libraries/assemblies
Thanks for the hint to fuslogvw.exe Hans Passant but what do you mean by "with code like this"?
okay I used fuslogvw.exe and I get two exceptions for every single dll that is referenced by the "selectedAssembly".
The first one says something like
"The binding starts in LoadFrom-context
The image owned by the system isn't searched in LoadFrom-Context"
the other logentry says that the dll referenced by the selectedAssembly couldn't be found and it tried to download it from application's base path and all directories below...but not from it's actual location...so, key question: how do I change the Load-context to LoadFrom? And why is .NET so stubborn on this? I mean the assemblies are loaded in the AppDomain, it shouldn't care about the actual location of the assembly.
okay problem solved. Here is the solution:
http://ayende.com/blog/1376/solving-the-assembly-load-context-problem
I implemented that into my existing class (removed the static-keyword and put the body of the Init method into my method), compiled it and it worked.
Thanks for your help guys.
okay problem solved. Here is the solution: http://ayende.com/blog/1376/solving-the-assembly-load-context-problem
I implemented that into my existing class (removed the static-keyword and put the body of the Init method into my method), compiled it and it worked.
Thanks for your help guys.
just in case the website will someday be unavailable, here is the sourcecode from ayende
static Dictionary<string, Assembly>assemblies;
public static void Init()
{
assemblies = new Dictionary<string, Assembly>();
AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Assembly assembly = null;
assemblies.TryGetValue(args.Name, out assembly);
return assembly;
}
static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
Assembly assembly = args.LoadedAssembly;
assemblies[assembly.FullName] = assembly;
}
I would recommend using Reflector to see which references you may not have loaded. For instance, you are only loading the referenced assemblies that the current assembly is looking at. Do you step down through each child to find their referenced assemblies as well? The FileNotFound error is probably pointing you in the direction of a type that is declared in another assembly that isn't loaded.
I'm having a problem with XML deserialization that is baffling me.
I'm building an application that supports local customization of various services that it uses. I've implemented an abstract ServiceLocator class whose methods return various objects. Each custom installation is responsible for implementing a subclass of this and providing implementations of those methods. The meat of this class looks like this:
public abstract class ServiceLocator
{
public static void Initialize(string customFeaturesPath)
{
Assembly a = Assembly.LoadFrom(customFeaturesPath);
Type t = a.GetExportedTypes()
.AsEnumerable()
.Where(x => x.IsSubclassOf(typeof (ServiceLocator)))
.First();
Default = (ServiceLocator)a.CreateInstance(t.FullName);
}
public static ServiceLocator Default { get; private set; }
public abstract DefaultValuesContainer CreateDefaultValuesContainer();
}
This works just fine: I get the path to the custom features assembly from the application configuration file, the program calls Initialize, and then the application can call the various methods on ServiceLocator.Default and they return the appropriate custom implementations of the services.
One of these services is a DefaultValuesContainer. This is a simple object that exposes properties whose values need to be persisted in a user settings file. The idea is that I can serialize this object into a single user setting of type string. It makes for a user setting file that you wouldn't want to edit manually, but I'm cool with that.
Here's a concrete implementation of ServiceLocator.CreateDefaultValuesContainer:
protected override DefaultValuesContainer CreateDefaultValuesContainer(string serializedXml)
{
DefaultValuesContainer c = new ClientDefaultValuesContainer();
if (string.IsNullOrEmpty(serializedXml))
{
return c;
}
XmlSerializer x = new XmlSerializer(c.GetType());
return (DefaultValuesContainer) x.Deserialize(new StringReader(serializedXml));
}
Now here's the thing.
I've built unit tests for this using NUnit. When I run the tests in the test fixture class that exercises the client custom features, they work. When I run the entire test suite, the last line of the above method throws this exception:
System.InvalidOperationException : There is an error in XML document (0, 0).
----> System.IO.FileLoadException : Could not load file or assembly 'ClientCustomFeatures, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Invalid pointer (Exception from HRESULT: 0x80004003 (E_POINTER))
----> System.ArgumentNullException : Value cannot be null.
Parameter name: path1
I'm kind of baffled as to why. The SetUp method still runs, and ServiceLocator.Default still returns an object of type ClientServiceLocator, which means that it has loaded the ClientCustomFeatures assembly. Indeed, the very method that's throwing the exception is in the assembly that I'm being told can't be loaded.
What is the XmlSerializer trying to do here? Why is it trying to load an assembly that's already loaded? What on earth does "Invalid pointer" mean? And above all, how should I be debugging something like this?
If your custom assembly does not know where to load the assembly containing the ClientCustomFeatures class, this will happen. This occurs when you've deployed your custom assembly to a location that is not in the path of your main assembly and your main assembly is not in the gac. So if your custom asseblies are loaded from sub directories of your main assembly this should go away. However, if they are located in arbitrary places, you'll have a problem because they need to load your main assembly as they need access to the ClientCustomFeatures type.
I've had problems with the assembly loader (Fusion?) when one assembly loads another assembly which itself has (non-GAC) references. YourDLL.XmlSerializers.dll might be one such assembly. Try turning off Visual Studio's option to automatically generate an XML serialization assembly (Project options) - this will remove the additional assembly (and hence the dependency on it).
Fusion Log Viewer
To help diagnose assembly loading problems like these, take a look at the Fusion Log Viewer (AKA fuslogvw.exe).
Fusion == the .NET component that locates and loads assemblies.
Try to replace the line:
XmlSerializer x = new XmlSerializer(c.GetType());
with:
XmlSerializer x = new XmlSerializer(c.GetType(), new Type[] { typeof(DefaultValuesContainer), typeof(ClientDefaultValuesContainer) });