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();
}
6
  • 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? Commented Feb 13, 2015 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 Commented Feb 13, 2015 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... Commented Feb 13, 2015 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"} Commented Feb 13, 2015 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. Commented Feb 13, 2015 at 22:43

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

2 Comments

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

1 Comment

Yes passing a name when registering multiple implementors of same interface allows you to later call unityContainer.ResolveAll<ISomething>() and it returns all implementors. If you dont specify names when registering, you will get empty list from this method.
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);
        });
});

1 Comment

The jobFile variable is a path to the XML holding the schedule\job\trigger stuff

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.