简体   繁体   中英

How to use Dependency Injection (unity) when dynamically invoke method in a dll?

I have a project which dynamically invokes methods from dlls. The dll names are defined in a configuration file. Below, it is the code to invoke the method 'Run'.

Assembly a = Assembly.LoadFile(fullpath);
Type t = a.GetType(dll_fullname);
// Run in subclass
MethodInfo mi = t.GetMethod("Run");
if (mi != null)
{
    // set log file name
    object result = null;
    object classInstance = Activator.CreateInstance(t, null);
    object[] parametersArray = new object[] { t.Name };
    result = mi.Invoke(classInstance, parametersArray);
}
else
{
    myEventLog.WriteEntry("Error: Invoke DLL Failed.", EventLogEntryType.Error, (int)(SharedClass.EventId.Error));
}

Each DLL is a class which inherts from the same base class MyTask, and override the method Run. Now, we want to use Dependency Injection with Unity in the each class. Obviously, we can apply Unity in each DLL. However, I have a question:

Since all DLL are inherted from the same base class MyTask, is it possible to do the dependency injection when it invoke the method 'Run'? I would think we can do it when CreateInstance, pass the parameters for the injection. However, different DLLs may need to inject different services. So, I got stuck in here.

Anyone had this similar situations before? Any suggestions?

Thanks

Since you're loading the type at run-time, you need a factory. Well, that factory can receive the same injections, and just pass them along. For example:

public class Factory : IFactory
{
    protected readonly IDependency1 _dependency1; //Injected
    protected readonly IDependency2 _dependency2; //Injected

    public Factory(IDependency1 dependency1, IDependency2 dependency2)
    {
        _dependency1 = dependency1;
        _dependency2 = dependency2;
    }

    public BaseClass Resolve(string libraryName, string typeName)
    {
        var assembly = Assembly.LoadFile(libraryName);
        var type = assembly.GetType(typeName);
        var args = new object [] { _dependency1, _dependency2 };
        return (BaseClass)Activator.CreateInstance(type, args);
    }
}

Then you register the factory:

public static UnityContainer CompositionRoot()
{
    var container = new UnityContainer();
    container.RegisterType<IDependency1, Dependency1>();
    container.RegisterType<IDependency2, Dependency2>();
    container.RegisterType<IFactory,Factory>();
    return container;
}

And inject it into the class that will need the external class:

public class Application
{
    protected readonly IFactory _factory;

    public Application(IFactory factory)
    {
        _factory = factory;
    }

    public void Run()
    {
        var instance = _factory.Resolve("MyLibrary.dll", "External.DerivedClass");
        //Do something with instance
    }
}

And the dependencies get passed along just fine.

If you find that different libraries need different injections, you can manage all of that in the factory, which is where that sort of logic belongs.

If the derived type has different constructor parameters and different injections, or the parameters are in an unknown order, you can resolve the dependencies with a bit of LINQ like this:

protected object TryInject(Type concreteType, IEnumerable<object> dependencies)
{
    var constructors = concreteType
        .GetConstructors
        (
            BindingFlags.Public | BindingFlags.Instance
        )
        .OrderByDescending
        (
            c => c.GetParameters().Length
        );
    foreach (var c in constructors)
    {
        var parameters = c.GetParameters();
        var arguments = parameters
            .Select
            (
                p => dependencies.FirstOrDefault
                (
                    d => p.ParameterType.IsAssignableFrom(d.GetType())
                )
            )
            .ToArray();
        if (!arguments.Contains( null ))
        {
            return Activator.CreateInstance(concreteType, arguments);
        }
    }
    return null;
}

Then just pass it your dependencies in an array and it'll figure out which ones are needed and where to put them:

return (BaseClass)TryInject(type, args);

See my full working example on DotNetFiddle

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM