How would one go about resolving Plugin contacts using TinyIoC?
Host.exe /w reference to Core.Contract.dll
var container = new TinyIoCContainer();
container.AutoRegister(new[] { Assembly.LoadFrom("Core.Contracts.dll") },
DuplicateImplementationActions.RegisterMultiple);
container.AutoRegister(new[] { Assembly.LoadFrom("EchoCodeAnalysis.dll") },
DuplicateImplementationActions.RegisterMultiple);
var mi = container.Resolve<IService>();
the contract IService in in Core.Contracts.dll and is reference in the host assembly, this is to give the drag and drop plugin a chance to work without recompilation.
In EchoCodeAnalysis.dll we have the actual plugin implementation which is not referenced in the host assembly but share the host of Core.Contracts.dll using the IService.
Core.Contract.dll:
public interface IService
{
string ID { get; set; }
}
EchoCodeAnalysis.dll:
public class Service : IService
{
string IService.ID
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
}
EDIT:
I managed to resolve the first part of my issue.
var type = typeof(IService);
var types = (new[] { Assembly.LoadFrom("EchoCodeAnalysis.dll") }).ToList()
.SelectMany(s => s.GetTypes())
.Where(x => type.IsAssignableFrom(x) && x.IsClass).ToList();
container.RegisterMultiple<IService>(types.ToArray());
var mi = container.ResolveAll<Core.Contracts.IService>();
Will fetch and resolve all IService interfaces, that limits the plugin to that interface and not any high up implementations. Say, IMenuItem impltemented as IService, the code above could find any class that is traced back to the origin of IService, but those that explicitly implement IMenuItem which has lets say name, when resolved as IService it will only fetch the IService properties and not include IMenuItem properties.
That is where. container.Register(types.ToArray()).AsRespectiveImplementations() would come in handy.
But is there anywhere around this issue? or is this a utility that has to be written up to extend TinyIOC?
Edit 2:
We have then moved to a extension but we are still not getting anything resolved.
public static IEnumerable<T> GetPluginsForModule<T>()
{
var type = typeof(T);
var types = Plugins.SelectMany(s => s.GetTypes())
.Where(x => type.IsAssignableFrom(x) && x.IsClass).ToList();
foreach (var t in types)
{
if (t.CustomAttributes.Where(x => x.AttributeType == typeof(CluraPlugin)).Any())
{
CustomAttributeData attr = t.CustomAttributes.Where(x => x.AttributeType == typeof(CluraPlugin)).FirstOrDefault();
if (attr == null)
break;
string Name = attr.ConstructorArguments.Where(x => x.ArgumentType == typeof(string)).FirstOrDefault().Value as string;
Type InterfaceTypeArgument = attr.ConstructorArguments.Where(x => x.ArgumentType == typeof(Type)).FirstOrDefault().Value as Type;
Container.Register(InterfaceTypeArgument, t, Name).AsMultiInstance();
}
}
return Container.ResolveAll(type) as IEnumerable<T>;
}
I'm passing the correct values, in the Container.Register above we have
InterfaceTypeArgument = IMenuItem, t = EchoMenu : IMenuItem, Name = "EchoMenu"
but when we ask the container to resolve IMenuItem after registering EchoMenu as its implementation we get null back from resolve all.
Any thoughts?
I found a way, not sure about best practice or potential memory issues down the road, with benchmark testing.
Using:
string PluginDirectory = Directory.GetCurrentDirectory() +"\\Plugins\\";
PluginManager.LoadDirectory(PluginDirectory);
var mi = PluginManager.GetPluginsForModule<IService>();
Which resolves things like this:
public static IEnumerable<object> GetPluginsForModule<T>()
{
var type = typeof(T);
var types = Plugins.SelectMany(s => s.GetTypes())
.Where(x => type.IsAssignableFrom(x) && x.IsClass).ToList();
foreach (var t in types)
{
if (t.CustomAttributes.Where(x => x.AttributeType == typeof(CluraPlugin)).Any())
{
CustomAttributeData attr = t.CustomAttributes.Where(x => x.AttributeType == typeof(CluraPlugin)).FirstOrDefault();
if (attr == null)
break;
string Name = attr.ConstructorArguments.Where(x => x.ArgumentType == typeof(string)).FirstOrDefault().Value as string;
Type InterfaceTypeArgument = attr.ConstructorArguments.Where(x => x.ArgumentType == typeof(Type)).FirstOrDefault().Value as Type;
Container.Register(InterfaceTypeArgument, t, Name).AsMultiInstance();
}
}
return Container.ResolveAll(type);
}
It might not be optimal when doing things on the fly, so there might need to be a need for a plugin manager to hold implementations as instances once you start the application and use them from list of plugin types.
Related
I am trying to take a namespace find all the classes that end with service then add them as a singleton to my service.
foreach(Type serviceClass in GetTypesInNamespace(Assembly.GetExecutingAssembly(), "MyProject.Services"))
{
services.AddSingleton<serviceClass.GetType()>;
}
This does not compile, so any help would be appreciated.
The best approach that I found is based on this.
Would required some mother methods and classes:
You need to call this inside you Configure:
services.RegisterAssemblyPublicNonGenericClasses()
.Where(c => c.Namespace == "")
.AsPublicImplementedInterfaces();
Create this class:
public class AutoRegisteredResult
{
internal AutoRegisteredResult(Type classType, Type interfaceType)
{
Class = classType;
Interface = interfaceType;
}
public Type Class { get; }
public Type Interface { get; }
}
And these methods:
public static IEnumerable<Type> RegisterAssemblyPublicNonGenericClasses(this IServiceCollection services,
params Assembly[] assemblies)
{
if (assemblies.Length == 0)
assemblies = new[] { Assembly.GetCallingAssembly() };
var allPublicTypes = assemblies.SelectMany(x => x.GetExportedTypes()
.Where(y => y.IsClass && !y.IsAbstract && !y.IsGenericType && !y.IsNested));
return allPublicTypes;
}
public static IList<AutoRegisteredResult> AsPublicImplementedInterfaces(this IEnumerable<Type> autoRegData)
{
var result = new List<AutoRegisteredResult>();
foreach (var classType in autoRegData)
{
var interfaces = classType.GetTypeInfo().ImplementedInterfaces;
foreach (var infc in interfaces)
{
result.Add(new AutoRegisteredResult(classType, infc));
}
}
return result;
}
I have a .NET Core WEB API project and I would like to be a container for multiples "projects" as .NET CORE Class Library.
What I have is:
Solution "Orbit" with a .NET Core Web API project on it.
Solution "SpaceRadar" with a .NET Core Class Library.
First, inside my "Orbit" project, the Startup class, what I have made so far:
public void ConfigureServices(IServiceCollection services)
{
this.ConfigureMVC(services);
this.ConfigureIOC(services);
}
private void ConfigureMVC(IServiceCollection services)
{
IMvcBuilder mvcBuilder = services.AddMvc(option => { option.EnableEndpointRouting = false; });
// for each assembly inside modules directory, add the controllers
foreach (string assemblyPath in Directory.GetFiles($"{System.AppDomain.CurrentDomain.BaseDirectory}/Modules", "*.dll", SearchOption.AllDirectories))
{
var assembly = Assembly.LoadFile(assemblyPath);
mvcBuilder.AddApplicationPart(assembly);
}
}
This part works well, since we can trigger the Controller that is defined inside my SpaceRadar project.
However I wanted to use dependency injection in my class library project, if possible by scanning the dll to get all the types that extends IScopedServices / ISingletonServices / ITransientServices.
But honestly, I have no idea where to register my interface and their respective implementation.
I tried this solution:
private void ConfigureIOC(IServiceCollection services)
{
// Store all the type that need to be injected in the IOC system
List<Type> implementationTypes = new List<Type>();
List<Type> singletons = new List<Type>();
List<Type> scopeds = new List<Type>();
List<Type> transients = new List<Type>();
// for each assembly, load it, populate the type list of things to be injected
foreach (string assemblyPath in Directory.GetFiles(System.AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.AllDirectories))
{
var assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
implementationTypes.AddRange(assembly.GetExportedTypes().Where(type => type.IsClass && (type.GetInterface("ISingletonServices") != null || type.GetInterface("IScopedServices") != null || type.GetInterface("ITransientServices") != null)));
singletons.AddRange(assembly.GetExportedTypes().Where(type => type.IsInterface && type.GetInterface("ISingletonServices") != null));
scopeds.AddRange(assembly.GetExportedTypes().Where(type => type.IsInterface && type.GetInterface("IScopedServices") != null));
transients.AddRange(assembly.GetExportedTypes().Where(type => type.IsInterface && type.GetInterface("ITransientServices") != null));
}
// Register into the service collection
foreach (Type type in singletons)
{
services.AddSingleton(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in scopeds)
{
services.AddScoped(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in transients)
{
services.AddTransient(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
}
But it seems that there is a problem of context of the assembly.
I tried also the Assembly.LoadFrom() but isn't working, the ReaderLoadContext solution found on a blog
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
namespace Orbit
{
public class ReaderLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public ReaderLoadContext(string readerLocation)
{
_resolver = new AssemblyDependencyResolver(readerLocation);
}
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;
}
}
}
And even to call a "setup" method inside the assembly
private void ConfigureIOC(IServiceCollection services)
{
// for each assembly, load it, add the controller into the mvcBuilder and populate the type list of things to be injected
foreach (string assemblyPath in Directory.GetFiles($"{System.AppDomain.CurrentDomain.BaseDirectory}/Modules", "*.dll", SearchOption.AllDirectories))
{
Assembly assembly = Assembly.LoadFrom(assemblyPath);
Type startup = assembly.GetTypes().SingleOrDefault(t => t.Name == "Startup");
if (startup != null)
{
var setupMethod = startup.GetMethod("Setup");
setupMethod.Invoke(setupMethod, new Object[] { services });
}
}
}
and inside my class library
public static void Setup(IServiceCollection services)
{
List<Type> implementationTypes = new List<Type>();
List<Type> singletons = new List<Type>();
List<Type> scopeds = new List<Type>();
List<Type> transients = new List<Type>();
foreach (string assemblyPath in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "*.dll", SearchOption.AllDirectories))
{
var assembly = Assembly.Load(assemblyPath);
// get all Singleton, Scoped and Transient interfaces
implementationTypes.AddRange(assembly.GetTypes().Where(type => type.IsClass && (type.GetInterface("ISingletonServices") != null || type.GetInterface("IScopedServices") != null || type.GetInterface("ITransientServices") != null)));
singletons.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ISingletonServices") != null));
scopeds.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("IScopedServices") != null));
transients.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ITransientServices") != null));
}
// Register into the service collection
foreach (Type type in singletons)
{
services.AddSingleton(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in scopeds)
{
services.AddScoped(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in transients)
{
services.AddTransient(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
}
I also tried the HostingStartup attribute
[assembly: HostingStartup(typeof(SpaceRadar.ServiceKeyInjection))]
namespace SpaceRadar
{
public class ServiceKeyInjection : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
List<Type> implementationTypes = new List<Type>();
List<Type> singletons = new List<Type>();
List<Type> scopeds = new List<Type>();
List<Type> transients = new List<Type>();
foreach (string assemblyPath in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "*.dll", SearchOption.AllDirectories))
{
var assembly = Assembly.Load(assemblyPath);
// get all Singleton, Scoped and Transient interfaces
implementationTypes.AddRange(assembly.GetTypes().Where(type => type.IsClass && (type.GetInterface("ISingletonServices") != null || type.GetInterface("IScopedServices") != null || type.GetInterface("ITransientServices") != null)));
singletons.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ISingletonServices") != null));
scopeds.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("IScopedServices") != null));
transients.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ITransientServices") != null));
}
// Register into the service collection
foreach (Type type in singletons)
{
services.AddSingleton(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in scopeds)
{
services.AddScoped(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in transients)
{
services.AddTransient(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
});
}
}
}
All this solutions doesn't allow me inside my class library to have this kind of controller:
public class RadarControllers : BaseControllers
{
private readonly IRadarServices _radarServices;
public RadarControllers(IRadarServices radarServices)
{
_radarServices = radarServices;
}
[HttpGet("radars")]
public async Task<IActionResult> GetRadars(CancellationToken cancellationToken)
{
IList<string> data = await _radarServices.GetRadars(cancellationToken);
return Ok(data);
}
}
How to proceed for this dependency injection ?
Thanks.
I finally found a way to deals with that.
The issue is that on each projet, the assembly name is the same (for instance : "Services").
.Net core does not like this and it seems that if I try to load through AssemblyLoadContext.Default.LoadFromAssemblyPath, it keep loading the same assembly no matter the path to indicate.
On the projet, I just right click > Properties and change the assembly name to an another. Thanks to that, AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath) works.
For instance, if I have project A and B, I rename the assembly names services with
A.Services
and B.Services
so The dll is now A.Services.dll instead of Services.dll
Is it possible to set some kind of global configuration that would cause mapping or validation process fail when some properties of mapped objects have same name, but have different types?
Source/destination types
public class UserData1
{
public int Id { get; set; }
}
public class UserData2
{
public string Id { get; set; }
}
Mapping configuration
Mapper.Initialize(cfg =>
{
cfg.CreateMap<UserData1, UserData2>();
});
Expected behavior
Either AssertConfigurationIsValid or Map should trigger some kind of validation exception when types of mapping properties are not same.
Actual behavior
int property is mapped to string without exception.
Steps to reproduce
// Passes OK
Mapper.Configuration.AssertConfigurationIsValid();
// Mapping is successful
var user2 = Mapper.Map<UserData2>(new UserData1 { Id = 156 });
There is no build in solution available, but it can be achieved using custom configuration.
Something like this:
Mapper.Initialize(cfg => {
cfg.CreateMap<UserData1, UserData2>();
...
cfg.ForAllMaps((typeMap, mappingExpr) =>
{
foreach (var map in typeMap.PropertyMaps) {
var sourcePropInfo = map.SourceMember as PropertyInfo;
var destPropInfo = map.DestinationMember as PropertyInfo;
if (sourcePropInfo == null || destPropInfo == null) continue;
if (sourcePropInfo.PropertyType != destPropInfo.PropertyType)
throw new Exception("Wrong property type!");
}
});
});
As reference was used this old post and updated to work with new version of automapper
https://stackoverflow.com/a/38080647/1703620
Using Artyom's answer as inspiration, I wrote a unit test to constrain these implicit type conversions to the few scenarios that we're okay with.
The approach is to look up all property mappings that don't have an explicit mapping, and filter out the few scenarios that we want to allow.
an XUnit test:
[Fact]
public void AllMappedPropertiesAreSameTypeOrAreMappedExplicitly()
{
ServiceCollection theCollection = new ServiceCollection();
theCollection.AddMssAutoMapper();
IMapper theMapper = BuildProductionMapper();
//Store all explicit mappings for easy lookup
System.Collections.Generic.HashSet<(Type SourceType, Type DestType)> theExplicitMappings =
theMapper.ConfigurationProvider.GetAllTypeMaps()
.Select( map => (map.SourceType, map.DestinationType) )
.ToHashSet();
var theIllegalMaps =
from typeMap in theMapper.ConfigurationProvider.GetAllTypeMaps()
from propMap in typeMap.PropertyMaps
let sourceType = propMap.SourceType
let destType = propMap.DestinationType
let bothTypes = new[] { sourceType, destType }
where sourceType != null && destType != null
where sourceType != destType
//Anything that's explicitly mapped is permitted
where !theExplicitMappings.Contains( (sourceType, destType) )
//enums to string and vice versa is permitted
where !( sourceType.IsEnum || sourceType == typeof( string ) && destType.IsEnum || destType == typeof( string ) )
//mapping from one collection to another is okay
where !bothTypes.All( type => type.IsAssignableTo( typeof( IEnumerable ) ) )
select new
{
SourceType = typeMap.SourceType,
DestinationType = typeMap.DestinationType,
SourceMemberName = propMap.SourceMember.Name,
DestMemberName = propMap.DestinationMember.Name,
SourceMemberType = sourceType,
DestinationMemberType = destType
};
var illegalMapsList = theIllegalMaps.ToList();
foreach( var illegalMap in illegalMapsList )
{
Console.Out.WriteLine( $"Found disallowed property mapping from '{illegalMap.SourceType}.{illegalMap.SourceMemberName}' to '{illegalMap.DestinationType}.{illegalMap.DestMemberName}'" );
Console.Out.WriteLine( $"Property name: {illegalMap.SourceMemberName}" );
Console.Out.WriteLine( $"implicit mapping from {illegalMap.SourceMemberType} to {illegalMap.DestinationMemberType} is not allowed." );
Console.Out.WriteLine( $"Please map these types explicitly." );
}
if( illegalMapsList.Any() )
{
throw new Exception( "One or more ambiguous mappings were found that need to be handled explicitly. See console output for details." );
}
}
I'm working on a factory to create concrete instances based in two criteria:
1) The class must inherit from an specific abstract class
2) The class must have an specific value in an overridden property
My code looks like this:
public abstract class CommandBase
{
public abstract string Prefix { get; }
}
public class PaintCommand : CommandBase
{
public override string Prefix { get; } = "P";
}
public class WalkCommand : CommandBase
{
public override string Prefix { get; } = "W";
}
class Program
{
static void Main(string[] args)
{
var paintCommand = GetInstance("P");
var walkCommand = GetInstance("W");
Console.ReadKey();
}
static CommandBase GetInstance(string prefix)
{
try
{
var currentAssembly = Assembly.GetExecutingAssembly();
var concreteType = currentAssembly.GetTypes().Where(t => t.IsSubclassOf(typeof(CommandBase)) &&
!t.IsAbstract &&
t.GetProperty("Prefix").GetValue(t).ToString() == prefix).SingleOrDefault();
if (concreteType == null)
throw new InvalidCastException($"No concrete type found for command: {prefix}");
return (CommandBase)Activator.CreateInstance(concreteType);
}
catch (Exception ex)
{
return default(CommandBase);
}
}
}
I'm getting the error:
{System.Reflection.TargetException: Object does not match target type. at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target) at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
A cleaner way is to define your own attribute to store the prefix.
[AttributeUsage(AttributeTargets.Class)]
public class CommandAttribute : Attribute
{
public String Prefix { get; set; }
public CommandAttribute(string commandPrefix)
{
Prefix = commandPrefix;
}
}
Then use them like so:
[CommandAttribute("P")]
public class PaintCommand : CommandBase
{}
[CommandAttribute("W")]
public class WalkCommand : CommandBase
{}
In reflection:
static CommandBase GetInstance(string prefix)
{
var currentAssembly = Assembly.GetExecutingAssembly();
var concreteType = currentAssembly.GetTypes().Where(commandClass => commandClass.IsDefined(typeof(CommandAttribute), false) && commandClass.GetCustomAttribute<CommandAttribute>().Prefix == prefix).FirstOrDefault();
if (concreteType == null)
throw new InvalidCastException($"No concrete type found for command: {prefix}");
return (CommandBase)Activator.CreateInstance(concreteType);
}
As spender alluded to in his comment, the reason you are getting this specific error is this line:
t.GetProperty("Prefix").GetValue(t)
Here, t is a Type variable containing a class, such as WalkCommand. You're getting the PropertyInfo object for the Prefix property on that class, then trying to use GetValue() to read the value of that property from an instance of a WalkCommand object.
The problem is that you aren't passing GetValue() an instance of the WalkCommand class, you're passing it a Type so Reflection is then throwing this exception.
There are a few of ways to deal with this:
1) Create an instance of each type on the fly, just to read its prefix (I really wouldn't recommend doing this):
var instance = currentAssembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(CommandBase)) && !t.IsAbstract)
.Select(t => new { t, i = (CommandBase)Activator.CreateInstance(t) })
.Where(x => x.t.GetProperty("Prefix").GetValue(x.i).ToString() == prefix)
.Select(x => x.i)
.SingleOrDefault();
return instance;
2) Change the whole thing over to use Attributes, such as in SwiftingDuster's answer
3) Use a static constructor to create a Dictionary that maps the string prefices to the concrete types. Reflection is expensive, and the classes aren't going to change (unless you're dynamically loading assemblies), so do it once.
We could do this by (ab)using my earlier "create an instance to throw it away" code, so we're still creating an instance of each class just to read the property, but at least we only do it once:
static Dictionary<string, Type> prefixMapping;
static Program()
{
prefixMapping = currentAssembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(CommandBase)) && !t.IsAbstract)
.Select(t => new { t, c = (CommandBase)Activator.CreateInstance(t) })
.ToDictionary(x => x.t.GetProperty("Prefix").GetValue(x.c).ToString(), x => x.t);
}
static CommandBase GetInstance(string prefix)
{
Type concreteType;
if ( prefixMapping.TryGetValue(prefix, out concreteType) )
{
return (CommandBase)Activator.CreateInstance(concreteType);
}
return default(CommandBase);
}
Note that this will throw a really horrible exception if you have multiple classes with the same prefix, and as it would be an exception in a static constructor, it'll likely explode your application. Either catch the exception (which will leave prefixMapping as null), or modify the Linq expression to only return one type for each prefix (as below).
4) Use both the Attribute method from SwiftingDuster and also the precomputation of the dictionary. This would be my preferred solution:
static Dictionary<string, Type> prefixMapping;
static Program()
{
prefixMapping = currentAssembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(CommandBase)) && t.IsDefined(typeof(CommandAttribute), false) && !t.IsAbstract)
.Select(t => new { t, p = t.GetCustomAttribute<CommandAttribute>().Prefix })
.GroupBy(x => x.p)
.ToDictionary(g => g.Key, g => g.First().t);
}
static CommandBase GetInstance(string prefix)
{
Type concreteType;
if ( prefixMapping.TryGetValue(prefix, out concreteType) )
{
return (CommandBase)Activator.CreateInstance(concreteType);
}
return default(CommandBase);
}
Hope this helps
I would like to find all classes in my program that implements a given class with a specific call as the generic types.
In the example below would i like to find all classes that implements MyBaseClass<MyScraper, MyElance> in this case would it be MyProperty and not OtherProperty as it implements other generic classes.
How can this be done?
public class MyProperty : MyBaseClass<MyScraper, MyElance>
{
public override string test()
{
var test = base.test();
test += " + the new";
return test;
}
}
public class OtherProperty : MyBaseClass<OtherScraper, OtherElance>
{
public override string test()
{
var test = base.test();
test += " + the other";
return test;
}
}
public class MyBaseClass<S, E>
where S : IScraper
where E : IElance
{
public virtual string test()
{
return "base";
}
}
Edit:
Found a solution, but please tell me if there are a better way
var test = from x in Assembly.GetAssembly(typeof(Program)).GetTypes()
let y = x.BaseType
where !x.IsAbstract && !x.IsInterface &&
y != null && y.IsGenericType &&
y.GetGenericTypeDefinition() == typeof(MyBaseClass<,>) &&
y.GenericTypeArguments[0] == typeof(MyScraper) &&
y.GenericTypeArguments[1] == typeof(MyElance)
select x;
Final solution:
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(x => !x.IsAbstract && !x.IsInterface)
.Where(x => x.BaseType != null &&
x.BaseType.IsGenericType &&
x.BaseType.GetGenericTypeDefinition() == typeof (MyBaseClass<,>) &&
x.BaseType.GenericTypeArguments[0] == typeof (MyScraper) &&
x.BaseType.GenericTypeArguments[1] == typeof (MyElance))
.Select(x => x).ToList();
Assembly scanning is pretty much the best way.
In addition to the algorythm you have, I recommend scanning the AppDomain instead of the specific assembly - this will allow you (or others!) toextend your library more readily...
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
//...
(Disclaimer Pardon the use of Linq calls here, but I detest syntactical linq)
In addition, make sure any other assemblies are loaded, to ensure you don't miss something at scan-time.
var searchType = typeof(MyBaseClass<MyScraper, MyElance>);
var types =
AppDomain.CurrentDomain.GetAssemblies()
// For search only on assemblies where could exsist this types
.Where(a => a.GetName().Name == searchType.Assembly.GetName().Name || a.GetReferencedAssemblies().Any(n => n.Name == searchType.Assembly.GetName().Name))
.Where(t => !t.IsAbstract && !t.IsInterface)
.Select(t => t.GetTypes().Where(a => searchType.IsAssignableFrom(a)))
.SelectMany(a => a);
I use this version, to search in all loaded assemblies, with additional filter not to process assemblies that could not contain my type
Try this:
//var desiredImplementation = typeof (MyBaseClass<>).MakeGenericType(typeof (MyScraper), typeof (MyElance));
var desiredImplementation = typeof (MyBaseClass<MyScraper, MyElance>);
var implementingTypes = Assembly
.GetExecutingAssembly()
.GetExportedTypes()
.Where(type => desiredImplementation.IsAssignableFrom(type))
.ToList();
You can replace GetExportedTypes() with GetTypes() and BindingFlags to further introspect the assembly too.