4

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();
}
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Jon Gear
  • 1,008
  • 1
  • 11
  • 22
  • 1
    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 Feb 13 '15 at 21:59
  • 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 Feb 13 '15 at 22:12
  • 1
    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 Feb 13 '15 at 22:24
  • 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 Feb 13 '15 at 22:39
  • 1
    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 Feb 13 '15 at 22:43
  • Very correct. Basically when reflecting over Type .net ends up pulling in Unitys implementation of the IServiceLocator which is what causes my application to choke... http://stackoverflow.com/questions/10050027/reflecting-over-assemblies-causes-unity-to-require-microsoft-practices-servicelo ...... That article explains it. Thanks for your help! – Jon Gear Feb 13 '15 at 22:50

3 Answers3

2

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

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
  • 1
    Note that OP claims that `x => x.IsClass` does not filter out interfaces... So while only looking at opted-in assemblies/classes is indeed very good solution it may not help OP... – Alexei Levenkov Feb 13 '15 at 22:21
  • you are both correct. I did not know you could create a custom assembly attribute but regrettably it doesn't solve my issue due to unity being within my referenced assembly however it does help to limit the assemblies reflected over. – Jon Gear Apr 09 '15 at 21:53
1

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;
        }
    }
}
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Yes passing a name when registering multiple implementors of same interface allows you to later call unityContainer.ResolveAll() and it returns all implementors. If you dont specify names when registering, you will get empty list from this method. – Roboblob Mar 20 '16 at 14:18
0

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);
        });
});
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
26a
  • 1
  • The jobFile variable is a path to the XML holding the schedule\job\trigger stuff – 26a Jul 24 '15 at 21:00