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.
Related
I have a Class Library project First.csproj with one file ICar.cs:
namespace First
{
public interface ICar
{
string Name { get; set; }
}
}
I have an empty Class Library project Second.csproj and Analyzer (source generator) project Second.Generator.csproj:
First.csproj - has no project references
Second.csproj - has references to First.csproj and Second.Generator.csproj
Second.Generator.csproj - has no project references
I want to write Second.Generator.csproj MySourceGenerator.cs which takes Second.csproj, search all its Class Library project references (First.csproj in this case) and implement all its interfaces. Result should be this generated code:
namespace Second
{
public class Car : First.ICar
{
public string Name { get; set; }
}
}
Problem is that I cannot access referenced projects in source generator. I have tried to use reflection:
namespace Second.Generator
{
[Generator]
public class MySourceGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
var first = context.Compilation.References.First(); //this is First.dll
var assembly = Assembly.LoadFrom(first.Display);
}
}
}
But I cannot load the assembly:
Could not load file or assembly
'file:///...First\bin\Debug\net6.0\ref\First.dll' or
one of its dependencies. Reference assemblies should not be loaded for
execution. They can only be loaded in the Reflection-only loader
context.
Any help will be appreciated. Thank you.
I have figured out some way with assembly symbols. Using reflection was not a good idea.
namespace Second.Generator
{
[Generator]
public class MySourceGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
var types = context.Compilation.SourceModule.ReferencedAssemblySymbols.SelectMany(a =>
{
try
{
var main = a.Identity.Name.Split('.').Aggregate(a.GlobalNamespace, (s, c) => s.GetNamespaceMembers().Single(m => m.Name.Equals(c)));
return GetAllTypes(main);
}
catch
{
return Enumerable.Empty<ITypeSymbol>();
}
});
var properties = types.Where(t => t.TypeKind == TypeKind.Interface && t.DeclaredAccessibility == Accessibility.Public).Select(t => new
{
Interface = t,
Properties = t.GetMembers()
});
}
private static IEnumerable<ITypeSymbol> GetAllTypes(INamespaceSymbol root)
{
foreach (var namespaceOrTypeSymbol in root.GetMembers())
{
if (namespaceOrTypeSymbol is INamespaceSymbol #namespace) foreach (var nested in GetAllTypes(#namespace)) yield return nested;
else if (namespaceOrTypeSymbol is ITypeSymbol type) yield return type;
}
}
}
}
I have created an attribute that inherits LocationInterceptionAspect.
For demonstration purposes the code is as follows:
[Serializable]
public class RepeaterAttribute : LocationInterceptionAspect
{
public override bool CompileTimeValidate(PostSharp.Reflection.LocationInfo locationInfo)
{
var propertyInfo = locationInfo.PropertyInfo;
if (propertyInfo == null) return false;
if (propertyInfo.PropertyType != typeof(String))
return false;
return base.CompileTimeValidate(locationInfo);
}
public override void OnSetValue(LocationInterceptionArgs args)
{
args.Value = ((String)args.Value) + ((String)args.Value);
args.ProceedSetValue();
}
}
I have a library called External and in it is a class called Parent.
namespace External
{
public class Parent
{
public String ParentProperty { get; set; }
}
}
In a console application I have a class called Child that inherits from Parent.
The console application references the External library.
public class Child : External.Parent
{
public String ChildProperty { get; set; }
}
In my console application my code is.
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
var child = new Child();
child.ParentProperty = "A";
Console.WriteLine("This should be 'AA' : '{0}'", child.ParentProperty);
child.ChildProperty = "B";
Console.WriteLine("This should be 'BB' : '{0}'", child.ChildProperty);
Console.ReadKey();
}
}
}
and in the console application's AssemblyInfo.cs I have:
[assembly: ConsoleApplication.Repeater(AttributeTargetTypes = "ConsoleApplication.Child")]
But when I run the Repeater attribute is not being applied to the inherited "ParentProperty" from the Parent class.
PostSharp is not able to change classes in different assemblies that in the one it is transforming. The base property is declared in a different assembly. This is a limitation of LocationInterceptionAspect.
You can use MethodInterception, which supports intercepting methods in different assemblies:
[Serializable]
public class SetterRepeaterAttribute : MethodInterceptionAspect
{
public override void OnInvoke( MethodInterceptionArgs args )
{
args.Arguments[0] = ((String)args.Arguments[0]) + ((String)args.Arguments[0]);
args.Proceed();
}
}
And multicast it on assembly level to the setter of the base class:
[assembly: ConsoleApplication2.SetterRepeater(
AttributeTargetMembers = "set_ParentProperty",
AttributeTargetTypes = "External.Parent",
AttributeTargetAssemblies = "regex:.*")]
Note that in this case the interception is done at the call site level, the ParentProperty setter is not changed itself. Calls from the original assembly won't be intercepted.
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.
I'm trying to load an assembly, instantiate a class from that assembly, and then call Run(), which should set a property inside this instance.
Loading the assembly seems to work fine as I'm able to list the types, but the called method seems to be ineffective: the property located in the new instance remains set to null, depsite the fact that it should be set to something.
I also tried calling the method using a type.InvokeMethod(...) syntax.
Method that loads the assembly, calls the constructor and calls the method:
private IEntryPoint ChargerAppli(AppInfo ai)
{
string cheminAssemblies = "D:\\TFS\\OBL Microsoft\\Stages\\2010\\WPF\\Shell\\Shell\\Applications\\";
Assembly a = Assembly.LoadFile(cheminAssemblies + ai.AssemblyName);
Type type = a.GetType(ai.StartupClass);
IEntryPoint instance = Activator.CreateInstance(type) as IEntryPoint;
instance.Run();
return instance;
}
IEntryPoint interface:
public interface IEntryPoint
{
FrameworkElement HostVisual { get; set; }
void Run();
}
IEntryPoint implementation that I'm trying to load, which is located in the new assembly:
class Bootstrap : IEntryPoint
{
private FrameworkElement _visuel;
public Bootstrap()
{
//do some work;
this._visuel = new MainVisual();
}
public System.Windows.FrameworkElement HostVisual { get; set; }
public void Run()
{
HostVisual = this._visuel;
}
}
What may I be missing?
assuming the assembly is working, here is a simplified piece of code that I have used to accomplish the same task.
Assembly assembly = Assembly.LoadFile(file);
Type[] types = assembly.GetTypes();
foreach (Type t in types)
{
MethodInfo[] methods = t.GetMethods();
if (t.Name == "MyType")
{
foreach (MethodInfo method in methods)
{
if (method.Name == "Run")
{
try
{
InterfaceToMyType activeModule = ("InterfaceToMyType")method.Invoke(null, args);
}
catch
{
//do stuff here if needed
}
}
}
}
}
If I apply attributes to a partial class via the MetadataType attribute, those attributes are not found via Attribute.IsDefined(). Anyone know why, or what I'm doing wrong?
Below is a test project I created for this, but I'm really trying to apply custom attributes to a LINQ to SQL entity class - like this answer in this question.
Thanks!
using System;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace MetaDataTest
{
class Program
{
static void Main(string[] args)
{
PropertyInfo[] properties = typeof(MyTestClass).GetProperties();
foreach (PropertyInfo propertyInfo in properties)
{
Console.WriteLine(Attribute.IsDefined(propertyInfo, typeof(MyAttribute)));
Console.WriteLine(propertyInfo.IsDefined(typeof(MyAttribute), true));
Console.WriteLine(propertyInfo.GetCustomAttributes(true).Length);
// Displays:
// False
// False
// 0
}
Console.ReadLine();
}
}
[MetadataType(typeof(MyMeta))]
public partial class MyTestClass
{
public string MyField { get; set; }
}
public class MyMeta
{
[MyAttribute()]
public string MyField { get; set; }
}
[AttributeUsage(AttributeTargets.All)]
public class MyAttribute : System.Attribute
{
}
}
The MetadataType attribute is used to specify help specify the additional information on the data object. To access the additional attributes you would need to do something like the following:
using System;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace MetaDataTest
{
class Program
{
static void Main(string[] args)
{
MetadataTypeAttribute[] metadataTypes = typeof(MyTestClass).GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray();
MetadataTypeAttribute metadata = metadataTypes.FirstOrDefault();
if (metadata != null)
{
PropertyInfo[] properties = metadata.MetadataClassType.GetProperties();
foreach (PropertyInfo propertyInfo in properties)
{
Console.WriteLine(Attribute.IsDefined(propertyInfo, typeof(MyAttribute)));
Console.WriteLine(propertyInfo.IsDefined(typeof(MyAttribute), true));
Console.WriteLine(propertyInfo.GetCustomAttributes(true).Length);
RequiredAttribute attrib = (RequiredAttribute)propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), true)[0];
Console.WriteLine(attrib.ErrorMessage);
}
// Results:
// True
// True
// 2
// MyField is Required
}
Console.ReadLine();
}
}
[MetadataType(typeof(MyMeta))]
public partial class MyTestClass
{
public string MyField { get; set; }
}
public class MyMeta
{
[MyAttribute()]
[Required(ErrorMessage="MyField is Required")]
public string MyField { get; set; }
}
[AttributeUsage(AttributeTargets.All)]
public class MyAttribute : System.Attribute
{
}
}
This also includes a sample attribute to show how to extract info that was added.
I had a similar situation. I ended up writing the following extension method for it.
The idea is to hide the abstraction of looking in 2 places (main class and metadata class).
static public Tattr GetSingleAttribute<Tattr>(this PropertyInfo pi, bool Inherit = true) where Tattr : Attribute
{
var attrs = pi.GetCustomAttributes(typeof(Tattr), Inherit);
if (attrs.Length > 0)
return (Tattr)attrs[0];
var mt = pi.DeclaringType.GetSingleAttribute<MetadataTypeAttribute>();
if (mt != null)
{
var pi2 = mt.MetadataClassType.GetProperty(pi.Name);
if (pi2 != null)
return pi2.GetSingleAttribute<Tattr>(Inherit);
}
return null;
}
My solution for generic use. Get the attribute the property that you are looking for. Return null if not found.
If found, it returns the attribute itself. So you can have access to the properties inside the attribute if you wihs.
Hopes this help.
public static Attribute GetAttribute<T>(this PropertyInfo PI, T t) where T: Type
{
var Attrs = PI.DeclaringType.GetCustomAttributes(typeof(MetadataTypeAttribute), true);
if (Attrs.Length < 1) return null;
var metaAttr = Attrs[0] as MetadataTypeAttribute;
var metaProp = metaAttr.MetadataClassType.GetProperty(PI.Name);
if (metaProp == null) return null;
Attrs = metaProp.GetCustomAttributes(t, true);
if (Attrs.Length < 1) return null;
return Attrs[0] as Attribute;
}
Given the following classes:
public partial class Person
{
public int PersonId { get; set; }
}
[MetadataType(typeof(PersonMetadata))]
public partial class Person
{
public partial class PersonMetadata
{
[Key]
public int PersonId { get; set; }
}
}
I needed to get see if Key has been defined on a property for Person class. I then needed to get the value of the property. Using #AdamGrid answer, I modified the code like this to get it:
private static object GetPrimaryKeyValue(TEntity entity)
{
MetadataTypeAttribute[] metadataTypes = typeof(TEntity).GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray();
MetadataTypeAttribute metadata = metadataTypes.FirstOrDefault();
if (metadata == null)
{
ThrowNotFound();
}
PropertyInfo[] properties = metadata.MetadataClassType.GetProperties();
PropertyInfo primaryKeyProperty =
properties.SingleOrDefault(x => Attribute.GetCustomAttribute(x, typeof(KeyAttribute)) as KeyAttribute != null);
if (primaryKeyProperty == null)
{
ThrowNotFound();
}
object primaryKeyValue = typeof(TEntity).GetProperties().Single(x => x.Name == primaryKeyProperty.Name).GetValue(entity);
return primaryKeyValue;
}
private static void ThrowNotFound()
{
throw new InvalidOperationException
($"The type {typeof(TEntity)} does not have a property with attribute KeyAttribute to indicate the primary key. You must add that attribute to one property of the class.");
}