Dynamic assembly compile and load - c#

I'm compiling assembly at runtime and link it via adding to new domain. I use it and then unload domain. But when I try to compile again during same run I can't get access to that assembly because it currently in use.
Here are some of my code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ClassLibrary2
{
public interface IExtension
{
String GetExtensionName();
}
}
My assembly
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ClassLibrary2;
namespace ClassLibrary1
{
public class Extension1 : MarshalByRefObject, IExtension
{
public Extension1()
{
}
public string GetExtensionName()
{
return "Extension 1 from " + AppDomain.CurrentDomain.FriendlyName;
}
}
}
And the app that uses it
namespace ConsoleApplication7
{
class Program
{
static IEnumerable<IExtension> extensions;
static void Main(string[] args)
{
// Create app domain
AppDomain domain = CreateDomain(Directory.GetCurrentDirectory());
try
{
// Get extensions
extensions = EnumerateExtensions(domain);
foreach (IExtension extension in extensions)
// Execute extension method in separate domain.
Console.WriteLine(extension.GetExtensionName());
// Unload domain
UnloadDomain(domain);
}
finally
{
domain = null;
GC.Collect(2);
extensions = null;
}
Console.ReadKey();
}
private static IEnumerable<IExtension> EnumerateExtensions(AppDomain domain)
{
IEnumerable<string> fileNames = Directory.EnumerateFiles(domain.BaseDirectory, "*.dll");
if (fileNames != null)
{
foreach (string assemblyFileName in fileNames)
{
foreach (string typeName in GetTypes(assemblyFileName, typeof(IExtension), domain))
{
System.Runtime.Remoting.ObjectHandle handle;
try
{
handle = domain.CreateInstanceFrom(assemblyFileName, typeName);
}
catch (MissingMethodException)
{
continue;
}
object obj = handle.Unwrap();
IExtension extension = (IExtension)obj;
yield return extension;
}
}
}
}
private static IEnumerable<string> GetTypes(string assemblyFileName, Type interfaceFilter, AppDomain domain)
{
Assembly asm = domain.Load(AssemblyName.GetAssemblyName(assemblyFileName));
Type[] types = asm.GetTypes();
foreach (Type type in types)
{
if (type.GetInterface(interfaceFilter.Name) != null)
{
yield return type.FullName;
}
}
}
static AppDomain CreateDomain(string path)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = path;
return AppDomain.CreateDomain("Temporary domain", null, setup);
}
static void UnloadDomain(AppDomain domain)
{
AppDomain.Unload(domain);
}
}
}
So in Main() during Console.ReadKey(); assembly still locked and I can't get access to it (can't delete it via Windows for example).
Is there way to solve this?

I think the domain.Load is hooking up the assembly to your program try to the load inside the Extension1 try to move the GetTypes to the class Extension1
I don't remember but I think domain.Load just runs Assembly.LoadFrom( and that's what is connecting your application the the DLL.

Ok, I solved this problem. I used shadow copy, just configured shadow copy in that other domain and it worked for me.

Related

Can't read types from assembly using Entity Framework Core 5.0

I am trying to read the types out of an assembly that contains Entity Framework Core, but I am getting this error:
Could not load file or assembly '..file path..\TestRoslyn\Database\bin\Debug\net5.0\Database.dll'. The system cannot find the file specified.
The code I am using to read the types is pretty simple:
using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.MSBuild;
namespace TestRoslyn
{
class Program
{
static async Task Main(string[] args)
{
if (!MSBuildLocator.IsRegistered)
MSBuildLocator.RegisterDefaults();
using var w = MSBuildWorkspace.Create();
// Substitute your file location
var basePath = #"C:\Users\username\source\repos\";
var slnFile = #$"{basePath}TestRoslyn\TestRoslyn.sln";
var sln = await w.OpenSolutionAsync(slnFile);
foreach (var p in sln.Projects)
{
var asm = Assembly.LoadFrom(p.OutputFilePath);
foreach(var t in asm.GetTypes())
Console.WriteLine($"{p.OutputFilePath}\t{t.FullName}");
}
}
}
}
This works as is. However, when I add a simple project to the solution that references nuget package Microsoft.EntityFrameworkCore (5.0) with one file:
using Microsoft.EntityFrameworkCore;
namespace Database
{
public class AppContext: DbContext
{
}
}
(To keep it simple, I didn't include anything in the AppContext class except the base class.)
When I add a project with the EntityFrameworkCore nuget package to the solution above with just this class I get the error indicated above.
Not sure what the cause is? Do I need to somehow load the nuget package into the workspace? If so, how?
Use AssemblyDependencyResolver and custom AssemblyLoadContext (from System.Runtime.Loader) to help load assemblies with dependencies.
Here is the custom AssemblyLoadContext, allowing to resolve dependencies:
using System;
using System.Reflection;
using System.Runtime.Loader;
namespace TestRoslyn
{
public class LoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public LoadContext(string assemblyPath)
{
_resolver = new AssemblyDependencyResolver(assemblyPath);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}
}
It can be used like this:
foreach (var p in sln.Projects)
{
string outputPath = p.OutputFilePath;
AssemblyName assemblyName = AssemblyName.GetAssemblyName(outputPath);
LoadContext loadContext = new LoadContext(outputPath);
Assembly asm = loadContext.LoadFromAssemblyName(assemblyName);
foreach (var t in asm.GetTypes())
Console.WriteLine($"{p.OutputFilePath}\t{t.FullName}");
}
See details here and here.

Cannot GetTypes() in new domain

I have created a new domain, then loaded the assembly into this domain, but when GetTypes() gives an error like the picture attached, hope everyone helps, thanks.
Code
public class Program
{
public static void Main()
{
string assemblyPath = #"D:\Github\BeyConsPlugin\BeyConsProject\bin\x64\Debug\BeyConsRevitProject.dll";
AppDomain appDomain = CreateChildDomain(AppDomain.CurrentDomain, Guid.NewGuid().ToString());
appDomain.AssemblyResolve += AssemblyResolve;
var value = (Proxy)appDomain.CreateInstanceAndUnwrap(typeof(Proxy).Assembly.FullName, typeof(Proxy).FullName);
var assembly = value.GetAssembly(assemblyPath);
var types = assembly.GetTypes();
Console.ReadKey();
}
private static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
AssemblyName assemblyName = new AssemblyName(args.Name);
string dependentAssemblyFilename = Path.Combine(#"D:\Github\BeyConsPlugin\BeyConsProject\bin\x64\Debug", assemblyName.Name + ".dll");
if (File.Exists(dependentAssemblyFilename)) return null;
return Assembly.LoadFile(dependentAssemblyFilename);
}
public static AppDomain CreateChildDomain(AppDomain parentDomain, string domainName)
{
Evidence evidence = new Evidence(parentDomain.Evidence);
AppDomainSetup setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain(domainName, evidence, setup);
}
}
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch { return null; }
}
}
Error
Have you checked if BeyConsRevitProject.dll assembly is in the bin directory of your application? This is a possible cause. Try deleting the bin/ and obj/ folders and rebuilding your solution, if the error persists, use this code below to ascertain the real reason for the error:
using System.IO;
using System.Reflection;
using System.Text;
try
{
//The code that causes the error goes here.
}
catch (ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
sb.AppendLine(exSub.Message);
FileNotFoundException exFileNotFound = exSub as FileNotFoundException;
if (exFileNotFound != null)
{
if(!string.IsNullOrEmpty(exFileNotFound.FusionLog))
{
sb.AppendLine("Fusion Log:");
sb.AppendLine(exFileNotFound.FusionLog);
}
}
sb.AppendLine();
}
string errorMessage = sb.ToString();
//Display or log the error based on your application.
}
This code was suggested by Ben Gripka here:
Error message 'Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.'

C# Class in a restricted AppDomain inherit from an other class located in the main AppDomain

I try to make a simple console modding project in C# where I have my program that contain a list of an abstract class named ElementInGame. I want to be able to create others class that inherit ElementInGame from a .txt file. The class ElementInGame will contain some basic methods (virtual and not virtual). But I don't want these other modded class execute malicious code, I would like that they can only access the methods/properties from the inherited class. Here is my ElementInGame code :
(My C# program #1)
using System;
namespace Modding
{
//The class itself inherit from MarshalByRefObject to be available in 2 differents Domains
public abstract class ElementInGame : MarshalByRefObject
{
public ElementInGame()
{
Console.WriteLine("ElementInGame class created");
}
public virtual int GetNumber()
{
return 10;
}
public void CountToTen()
{
for (int i = 0; i <= 10; i++)
{
Console.WriteLine(i);
}
}
}
}
Then I have my .txt file stored at "C:\program.txt"
(My original .txt file)
using System;
namespace Test
{
public class HelloWorld
{
public HelloWorld()
{
Console.WriteLine("Called Constructor() !");
}
public static int TestMethod()
{
Console.WriteLine("Called TestMethod() !");
return 11;
}
}
}
So I code the main program to read the .txt file, compile it with restrictions, and execute it :
(My C# program #2 in a second .cs file, long code warning)
using System;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;
using System.Reflection;
using System.Security.Permissions;
using System.Security;
using System.Security.Policy;
using System.Runtime.Remoting;
using System.Collections.Generic;
namespace Modding
{
public class Program : MarshalByRefObject
{
public static void Main(string[] args)
{
string assemblyPath = #"C:\program.txt"; // Where the .txt file is stored
string code = File.ReadAllText(assemblyPath); //The code to compile
CompilerResults compile = CompileFromCode(code); //Compile the code in the temporary files
string fullPath = compile.PathToAssembly; //sample : C:\Users\MY_USER_NAME\AppData\Local\Temp\5v2p3qki.dll
string pathWithoutFile = Path.GetDirectoryName(fullPath); //sample : C:\Users\MY_USER_NAME\AppData\Local\Temp
string pathNameOnly = Path.GetFileNameWithoutExtension(fullPath); //sample : 5v2p3qki
Program newDomainInstance = GetOtherProtectedDomainInstance(pathWithoutFile);
newDomainInstance.CallMethod(pathNameOnly, "Test.HelloWorld", "TestMethod", null, null);
newDomainInstance.CreateObject(pathNameOnly,"Test.HelloWorld");
List<ElementInGame> allElement = new List<ElementInGame>();
//allElement.Add ***?***
Console.ReadKey();
}
public static Program GetOtherProtectedDomainInstance(string pathWithoutFile)
{
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = pathWithoutFile;
//Set some permissions to avoid malicious code
PermissionSet permSet = new PermissionSet(PermissionState.None);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
StrongName fullTrustAssembly = new StrongName(
new StrongNamePublicKeyBlob(typeof(Program).Assembly.GetName().GetPublicKey()),
typeof(Program).Assembly.GetName().Name,
typeof(Program).Assembly.GetName().Version);
AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
ObjectHandle handle = Activator.CreateInstanceFrom(
newDomain, typeof(Program).Assembly.ManifestModule.FullyQualifiedName,
typeof(Program).FullName
);
Program newDomainInstance = (Program)handle.Unwrap();
return newDomainInstance;
}
public static CompilerResults CompileFromCode(string code)
{
//Compile the code in a .dll locate in the temporary files
//The following code is based on https://stackoverflow.com/questions/10314815/trying-to-compile-and-execute-c-sharp-code-programmatically
CompilerParameters CompilerParams = new CompilerParameters();
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.GenerateInMemory = false;
CompilerParams.TreatWarningsAsErrors = false;
CompilerParams.GenerateExecutable = false;
CompilerParams.CompilerOptions = "/optimize";
//Adding a reference to the current project to allow the .txt file to inherit the class "ElementInGame" later
string[] references = { "System.dll", Assembly.GetEntryAssembly().Location };
CompilerParams.ReferencedAssemblies.AddRange(references);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, code);
if (compile.Errors.HasErrors)
{
string text = "Compile error: ";
foreach (CompilerError ce in compile.Errors)
{
text += "rn" + ce.ToString();
}
throw new Exception(text);
}
return compile;
}
public static void DisplaySomething()//Useful for later
{
Console.WriteLine("This isn't supposed to be display");
}
//Calling a method from the restricted Domain
public void CallMethod(string assemblyName, string typeName, string entryPoint, object objectToExecute = null, object[] parameters = null)
{
MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
try
{
target.Invoke(objectToExecute, parameters);
}
catch
{
Console.WriteLine("Security Error with Method " + assemblyName + " namespace : " + typeName + " method : " + entryPoint);
}
}
//Create an instance from the restricted Domain
public void CreateObject(string assemblyName, string typeName)
{
try
{
object o = Assembly.Load(assemblyName).CreateInstance(typeName);
}
catch
{
Console.WriteLine("Security Error with Constructor " + assemblyName + " namespace : " + typeName);
}
}
}
}
For the moment the .txt file don't have any link at all with my C# program. The code work properly and I got the following output :
Called TestMethod() !
Called Constructor() !
Then I edit my code in my .txt file to inherit from Modding.ElementInGame :
(My edited .txt file)
using System;
namespace Test
{
public class HelloWorld : Modding.ElementInGame
{
public HelloWorld() : base()
{
Console.WriteLine("Called Constructor() !");
}
public static int TestMethod()
{
Console.WriteLine("Called TestMethod() !");
return 11;
}
}
}
So I expected an output like :
Called TestMethod() !
ElementInGame class created
Called Constructor() !
But after this change, the program crash with a System.NullReferenceException at when calling the TestMethod : newDomainInstance.CallMethod(pathNameOnly, "Test.HelloWorld", "TestMethod", null, null);
However creating an instance of HelloWorld (the .txt file): newDomainInstance.CreateObject(pathNameOnly,"Test.HelloWorld"); seem to works (no crash, the code stay in the try part when doing the try/catch), but my there is nothings print in my console, so it doesn't work I guess ?
Changing the permission of the AppDomain change nothing.
PermissionSet permSet = new PermissionSet(PermissionState.Unrestricted);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags));
So my question is : How can I create and store an instance of the .txt file in my program that inherit from ElementInGame (and add it to the list of ElementInGame) ?
That way I can use from my program the virtual method GetNumber(). I don't want the .txt file have access to the program itself (like calling the method DisplaySomething()), just communicate with ElementInGame.
You are generating and loading reference assemblies from different locations. You did set the current directory for output but forgot to assign it to compiler parameters.
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.OutputAssembly = Path.Combine(outputDirectory, "Test.dll");
This should fix the issue.

Assembly.CreateInstance returning null even though I can see the class in DefinedTypes

I use the following method to load a new Assembly and create an instance of a class into a new AppDomain.
private static object CreateInstanceFromBinary(AppDomain appDomain, string typeName)
{
Assembly entryAssembly = Assembly.GetEntryAssembly();
byte[] assemblyBinary = LoadAssemblyBinary();
Assembly loadedAssembly = appDomain.Load(assemblyBinary);
if (loadedAssembly != null)
{
return loadedAssembly.CreateInstance(typeName);
}
return null;
}
Which get's called like so.
AppDomain appDomain = AppDomain.CreateDomain(domainName);
appDomainHelper = CreateInstanceFromBinary(appDomain, typeof(MyClass).FullName) as MyClass;
Looking into the loadedAssembly I can see that MyClass exists inside of the DefinedTypes and it's name matches typeName. However, when the code runs
loadedAssembly.CreateInstance(typeName)
it returns null.
This code was working, however, I recently moved this class into the same dll as the one that calls it and now it has started returning null.
Any ideas on how to fix this?
For some short(ish) reproducible code you can use the following. In this code ClassLibrary1 has CopyLocal set to false and then included as an EmbeddedResource to mimic what I have in my live project in case that matters.
Inside of ConsoleApplication1 I have Program.
using ClassLibrary1;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static Program()
{
AppDomain.CurrentDomain.AssemblyResolve += Resolve;
}
static void Main(string[] args)
{
WorkerClass workerClass = new WorkerClass();
workerClass.DoWork();
Console.WriteLine("\r\nPress enter to exit...");
Console.ReadLine();
}
static System.Reflection.Assembly Resolve(object sender, ResolveEventArgs args)
{
if (!args.Name.Contains(","))
{
return null;
}
List<string> rn = System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceNames()
.Where(r => r.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
.ToList();
string assemblyName = rn.FirstOrDefault(r => r.EndsWith(args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll"));
if (!String.IsNullOrEmpty(assemblyName))
{
using (Stream stream = System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceStream(assemblyName))
{
byte[] assemblyBinary = new byte[stream.Length];
stream.Read(assemblyBinary, 0, assemblyBinary.Length);
System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(assemblyBinary);
if (Environment.UserInteractive)
{
Console.WriteLine("Loaded Assembly: " + assembly.FullName);
}
return assembly;
}
}
if (Environment.UserInteractive)
{
Console.WriteLine($"** Failed to find an assembly with name: {args.Name} ** ");
}
return null;
}
}
}
Inside of ClassLibrary1 there is WorkerClass.
using System;
namespace ClassLibrary1
{
public class WorkerClass
{
public void DoWork()
{
try
{
HelperClass hc = HelperClass.Create("Name");
Console.WriteLine("Created");
}
catch (Exception ex)
{
Console.WriteLine("Failed to create: " + ex.ToString());
}
}
}
}
and HelperClass.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;
namespace ClassLibrary1
{
[Serializable]
public class HelperClass : MarshalByRefObject
{
public AppDomain Domain { get; private set; }
public HelperClass()
{
}
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
return null;
}
public static HelperClass Create(string domainName)
{
AppDomain appDomain = AppDomain.CreateDomain(domainName);
HelperClass helperClass = CreateInstanceFromBinary(appDomain, typeof(HelperClass).AssemblyQualifiedName) as HelperClass;
if (helperClass == null)
{
throw new Exception("Unable to create instance from binary resource.");
}
helperClass.Domain = appDomain;
return helperClass;
}
private static object CreateInstanceFromBinary(AppDomain appDomain, string typeName)
{
Assembly entryAssembly = Assembly.GetEntryAssembly();
IList<string> rn = entryAssembly.GetManifestResourceNames().Where(r => r.EndsWith(".dll")).ToList();
string assembly = rn.FirstOrDefault(r => r.EndsWith($"{typeof(HelperClass).Assembly.GetName().Name}.dll"));
if (!String.IsNullOrEmpty(assembly))
{
using (Stream stream = entryAssembly.GetManifestResourceStream(assembly))
{
byte[] assemblyBinary = new byte[stream.Length];
stream.Read(assemblyBinary, 0, assemblyBinary.Length);
Assembly loadedAssembly = appDomain.Load(assemblyBinary);
if (loadedAssembly != null)
{
return loadedAssembly.CreateInstance(typeName);
}
}
}
return null;
}
}
}
Where it is return loadedAssembly.CreateInstance(typeName); that returns null.
In your function public static HelperClass Create(string domainName) you are passing the AssemblyQualifiedName as the type of the class to create.
I think you just want to pass the type name, ie: ClassLibrary1.HelperClass
//HelperClass helperClass = CreateInstanceFromBinary(appDomain, typeof(HelperClass).AssemblyQualifiedName) as HelperClass;
HelperClass helperClass = CreateInstanceFromBinary(appDomain, typeof(HelperClass).ToString()) as HelperClass;
I tried some variations, each time passing in the Assembly Qualified Name failed, just the type name worked as expected.
Variations tried, and failed:
// Do not work
var x = loadedAssembly.CreateInstance(typeName); //AssemblyQualifiedName
var loadedType = loadedAssembly.GetType(typeName); //AssemblyQualifiedName
// Work
var x = Activator.CreateInstance(typeof(HelperClass)); // Works
var x = loadedAssembly.CreateInstance("ClassLibrary1.HelperClass");
var loadedType = loadedAssembly.GetType("ClassLibrary1.HelperClass");
var x = Activator.CreateInstance(loadedType);

How to make C# recognize custom attributes in external .dll?

The code below successfully recognizes internal classes which are decorated with my custom "Module" attribute, I load the assembly like this:
Assembly assembly = Assembly.GetExecutingAssembly();
However, when I load in an external module and look through its classes, it finds the classes in the external assembly but does not recognize the custom attributes:
Assembly assembly = Assembly.LoadFrom(#"c:\tests\modules\CustomModules.dll");
What do I have to specify so that C# recognizes custom attributes in external .dlls the same as it does with the internal classes?
Here is the code that successfully runs through and recognizes internal classes decorated with my "Module" attribute:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace DynamicAssembly2
{
class Program
{
static void Main(string[] args)
{
var modules = from t in GetModules()
select t;
foreach (var module in modules)
{
ModuleAttribute[] moduleAttributes = GetModuleAttributes(module);
Console.WriteLine(module.FullName);
foreach (var moduleAttribute in moduleAttributes)
{
Console.WriteLine(moduleAttribute.Description);
}
}
Console.ReadLine();
}
public static IEnumerable<Type> GetModules()
{
//Assembly assembly = Assembly.LoadFrom(#"c:\tests\modules\CustomModules.dll");
Assembly assembly = Assembly.GetExecutingAssembly();
return GetAssemblyClasses(assembly)
.Where((Type type) => {return IsAModule(type);});
}
public static IEnumerable<Type> GetAssemblyClasses(Assembly assembly)
{
foreach (Type type in assembly.GetTypes())
{
Console.WriteLine(type.FullName);
yield return type;
}
}
public static bool IsAModule(Type type)
{
return GetModuleAttribute(type) != null;
}
public static ModuleAttribute GetModuleAttribute(Type type)
{
ModuleAttribute[] moduleAttributes = GetModuleAttributes(type);
Console.WriteLine(moduleAttributes.Length);
if (moduleAttributes != null && moduleAttributes.Length != 0)
return moduleAttributes[0];
return null;
}
public static ModuleAttribute[] GetModuleAttributes(Type type)
{
return (ModuleAttribute[])type.GetCustomAttributes(typeof(ModuleAttribute), true);
}
}
}
Here is my custom Mdoule attribute:
using System;
namespace DynamicAssembly2
{
[AttributeUsage(AttributeTargets.Class)]
public class ModuleAttribute : Attribute
{
public string Description { get; set; }
}
}
Here is a custom module:
namespace DynamicAssembly2
{
[Module(Description="This is the main customer class.")]
class Customers
{
}
}
How many instances of the ModuleAttribute class do you have defined across your assemblies?
It looks like you have one in DynamicAssembly2 and one in CustomModules ... in which case they are different types.
Your code in DynamicAssembly2 should use the attribute class defined in CustomModules (or both assemblies should load the attribute from a 3rd assembly).
I'm pretty sure that it doesn't distinguish... are you sure you are asking for the right attribute? As long as both projects reference the same ModuleAttribute, then typeof(ModuleAttribute) should work. Otherwise you'll need to first find the Type of the attribute you want (from Assembly.GetType()), and use that when calling GetCustomAttributes.
Aloha
I've tested your code but couldn't get it to compile because the type ModuleAttribute must be known in both the Main program and the external dll. I assumed there were no references involved.
I did get it to work by introducing a reference.
Here's the class. This assembly holds a reference to DynamicAssembly
using System;
namespace DynamicAssembly2
{
[DynamicAssembly.Module(Description = "This is the main customer class.")]
public class Customers
{
}
}
Here's the code in DynamicAssembly:
using System.Reflection;
namespace DynamicAssembly
{
[AttributeUsage(AttributeTargets.Class)]
public class ModuleAttribute : Attribute
{
public string Description { get; set; }
}
class Program
{
static void Main(string[] args)
{
var modules = from t in GetModules()
select t;
foreach (var module in modules)
{
ModuleAttribute[] moduleAttributes = GetModuleAttributes(module);
Console.WriteLine(module.FullName);
foreach (var moduleAttribute in moduleAttributes)
{
Console.WriteLine(moduleAttribute.Description);
}
}
Console.ReadLine();
}
public static IEnumerable<Type> GetModules()
{
Assembly assembly = Assembly.LoadFrom(#"C:\Temp\ClassLibrary1\bin\Debug\ClassLibrary1.dll");
return GetAssemblyClasses(assembly)
.Where((Type type) => { return IsAModule(type); });
}
public static IEnumerable<Type> GetAssemblyClasses(Assembly assembly)
{
foreach (Type type in assembly.GetTypes())
{
yield return type;
}
}
public static bool IsAModule(Type type)
{
return GetModuleAttribute(type) != null;
}
public static ModuleAttribute GetModuleAttribute(Type type)
{
ModuleAttribute[] moduleAttributes = GetModuleAttributes(type);
Console.WriteLine(moduleAttributes.Length);
if (moduleAttributes != null && moduleAttributes.Length != 0)
return moduleAttributes[0];
return null;
}
public static ModuleAttribute[] GetModuleAttributes(Type type)
{
return (ModuleAttribute[])type.GetCustomAttributes(typeof(ModuleAttribute), true);
}
}
}
I had the same problem. If the attribute was defined in an internal *cs, I could get the attribute, but if it was defined in an external dll, I would just get null when I queried for it (no error), but no linker error either.
Apparently a custom Attribute defined in an DLL and the same custom Attribute defined in local code (*cs) will happily compile into one application without any warnings about multiple definitions (the name spaces are identical). I got rid of the local code (*cs), and fixed the references and it worked.

Categories

Resources