Please skip to the UPDATE if you would like to just know the solution:
I have an application that uses the following code to get and run a number of worker methods
var type = typeof(IJob);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.GetTypes())
.Where(x => x.IsClass && type.IsAssignableFrom(x));
foreach (Type t in types)
{
IJob obj = Activator.CreateInstance(t) as IJob;
obj.Run();
}
This code works perfectly as is. However, some of the newer jobs utilize dependency injection to populate their constructors so this method will not be viable going forward. So I was wondering if there's a way to do this with unity?
My original thought was that I would continue with the first half and then replace the foreach logic with resolve so that it looks something like the following.
var type = typeof(IJob);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.GetTypes())
.Where(x => x.IsClass && type.IsAssignableFrom(x));
foreach (Type t in types)
{
IJob obj = Container.Resolve(t) as IJob;
obj.Run();
}
The problem is that as soon as I define my UnityContainer the returned types list that implement IJob suddenly gets bloated with all of these garbage Microsoft.Practices classes as shown below
UPDATE:
It turns out then when refelecting over Assemblies if Unity is present it will attempt to reflect into Unity's assemblies which if Finalized with a ToList will throw an exception due to a missing metadata extension of IServiceLocator. To work around this appending a where clause after GetAssemblies() to limit scope to your desired namespace will allow the application to run properly.
var type = typeof(IJob);
var types = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName.StartsWith("YourNamespace"))
.SelectMany(x => x.GetTypes())
.Where(x => x.IsClass && type.IsAssignableFrom(x));
foreach (Type t in types)
{
IJob obj = Container.Resolve(t) as IJob;
obj.Run();
}
Instead of searching through all assemblies, filter them by a custom attribute. This way you narrow the searching dramatically.
This is how to create a custom assembly level attribute
Custom Assembly Attributes
In Unity, there are a couple of things you need to take care of to get this working:
- You need to register each instance with a different name. Unnamed instances cannot be resolved as an array or
IEnumerable<T>
.
- You have to call the
ResolveAll
method explicitly during registration inside of an InjectionConstructor
and ResolvedArrayParameter
.
Here is a demo application:
using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityExperiment
{
class Program
{
static void Main(string[] args)
{
// Begin composition root
var container = new UnityContainer();
container.AddNewExtension<JobContainerExtension>();
container.RegisterType<IService1, Service1>(new InjectionConstructor(
new ResolvedArrayParameter<IJob>(container.ResolveAll<IJob>().ToArray())));
container.RegisterType<IService2, Service2>(new InjectionConstructor(
new ResolvedArrayParameter<IJob>(container.ResolveAll<IJob>().ToArray())));
// End composition root
var service1 = container.Resolve<IService1>();
var service2 = container.Resolve<IService2>();
}
}
public class JobContainerExtension : UnityContainerExtension
{
protected override void Initialize()
{
var interfaceType = typeof(IJob);
var implementationTypes = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName.StartsWith("UnityExperiment"))
.SelectMany(x => x.GetTypes())
.Where(x => x.IsClass && interfaceType.IsAssignableFrom(x));
foreach (Type implementationType in implementationTypes)
{
// IMPORTANT: Give each instance a name, or else Unity won't be able
// to resolve the collection.
this.Container.RegisterType(interfaceType, implementationType,
implementationType.Name, new ContainerControlledLifetimeManager());
}
}
}
public interface IJob
{
}
public class Job1 : IJob
{
}
public class Job2 : IJob
{
}
public class Job3 : IJob
{
}
public interface IService1
{
}
public class Service1 : IService1
{
private readonly IJob[] jobs;
public Service1(IJob[] jobs)
{
this.jobs = jobs;
}
}
public interface IService2
{
}
public class Service2 : IService2
{
private readonly IEnumerable<IJob> jobs;
public Service2(IEnumerable<IJob> jobs)
{
this.jobs = jobs;
}
}
}
Here is my contribution fellas:
//Register all IJob implementations that are not generic, abstract nor decorators
Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "SomeFilter*.dll")
.Select(file => Assembly.LoadFile(file))
.ForEach(s =>
{
s.GetTypes()
.Where(type => typeof(IJob).IsAssignableFrom(type) && (!type.IsAbstract && !type.IsGenericTypeDefinition))
.Select(type => new { type, ctor = type.GetConstructors().Any(ct => ct.GetParameters().Any(p => p.ParameterType == typeof(IJob))) == false })
.Select(type => type.type)
.ForEach<Type>(o =>
{
string jobFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format("{0}.xml", Path.GetFileNameWithoutExtension(o.Assembly.Location)));
var typeLoadHelper = new SimpleTypeLoadHelper();
typeLoadHelper.Initialize();
XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(typeLoadHelper);
processor.AddJobGroupToNeverDelete("XMLSchedulingDataProcessorPlugin");
processor.AddTriggerGroupToNeverDelete("XMLSchedulingDataProcessorPlugin");
processor.ProcessFileAndScheduleJobs(jobFile, jobFile, this.Scheduler);
});
});