4
votes

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

enter image description here

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();
}
3
I'm not exactly sure how you get interfaces in your types list - could you please clarify what exactly is the "my type return" you are talking about? - Alexei Levenkov
Of course. I basically want to pull out all types that implement IJob then invoke their run method. The problem is that for some reason when I try to do this with the container the var types returned gets bloated with a bunch of Microsoft.Practices.Unity type references which do not implement IJob. Which to be honest, I don't understand how that's possible - Jon Gear
Can you please show result of types.ToList().Count()? I suspect that screenshot you posted is not the list you are looking for... - Alexei Levenkov
I think you may be on to something. When I put the toList on which caused a finalization of my query VS threw the following error -- Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information. -------------------------------- > {"Could not load file or assembly 'Microsoft.Practices.ServiceLocation, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.":"Microsoft.Practices.ServiceLocation, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"} - Jon Gear
Thought so - you were looking at pre-filtered inner list of "Where" which indeed shows all types. Also as somewhat expected you are getting problem with loading some types - you really should drop Linq and manually iterate over assemblies/types with careful try/catch code around each. - Alexei Levenkov

3 Answers

2
votes

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

1
votes

In Unity, there are a couple of things you need to take care of to get this working:

  1. You need to register each instance with a different name. Unnamed instances cannot be resolved as an array or IEnumerable<T>.
  2. 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;
        }
    }
}
0
votes

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);
        });
});