在dll中动态调用方法时如何使用依赖注入(unity)?

时间:2018-07-30 23:07:43

标签: c# .net dependency-injection

我有一个项目,可以从dll动态调用方法。 dll名称在配置文件中定义。下面是调用“运行”方法的代码。

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

每个DLL是一个从相同的基类MyTask继承的类,并重写Run方法。现在,我们要在每个类中对Unity使用依赖注入。显然,我们可以在每个DLL中应用Unity。但是,我有一个问题:

由于所有DLL都是从同一个基类MyTask继承的,因此在调用方法“运行”时是否可以进行依赖项注入?我认为我们可以在CreateInstance传递注入参数时做到这一点。但是,不同的DLL可能需要注入不同的服务。所以,我被困在这里。

以前有人遇到过类似情况吗?有什么建议吗?

谢谢

2 个答案:

答案 0 :(得分:2)

由于要在运行时加载类型,因此需要工厂。好吧,那个工厂可以接受相同的注射,然后将它们传递下去。例如:

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

然后您注册工厂:

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

并将其注入需要外部类的类中:

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

依赖关系就很好了。

如果发现不同的库需要不同的注入,则可以在工厂中管理所有注入,这就是这种逻辑所属的地方。

如果派生类型具有不同的构造函数参数和不同的注入,或者参数的顺序未知,则可以使用一些LINQ来解决依赖关系,如下所示:

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

然后将其依赖项传递到数组中,它将找出需要哪些依赖项以及将它们放在哪里:

return (BaseClass)TryInject(type, args);

看到我的full working example on DotNetFiddle

答案 1 :(得分:0)

签出以下代码,该代码使用Ninject DI框架完成,它使用Func注入在运行时执行逻辑:

void Main()
{
    var kernel = new StandardKernel();
    kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)
    var test = kernel.Get<Test>();
    test.DoWork("libraryName","typeName");
}

public class Base
{
    public virtual void DoWork()
    {
        Console.WriteLine("Base");
    }
}

public class Derived : Base
{
    public override void DoWork()
    {
        Console.WriteLine("Derived");
    }
}

public class Test
{
    private Func<string,string,Base> BaseFunc {get;}

    public Test(Func<string,string,Base> baseFunc)
    {
        BaseFunc = baseFunc;
    }

    public void DoWork(string libraryName, string typeName)
    {
        BaseFunc(libraryName,typeName).DoWork();
    }       
}

public class Bindings : NinjectModule
{
    public override void Load()
    {
        Bind<Func<string, string, Base>>().ToMethod(ctx => (libraryName,typeName) =>
        {
            Assembly a = Assembly.LoadFile(libraryName);

            Type t = a.GetType(typeName);

            object classInstance = Activator.CreateInstance(t, null);

            return (Base)classInstance;
        });  
    }
}

说明:

  1. Base类是基本类型,在运行时需要使用relfection来调用Derived类型
  2. 测试类注入Func<string,string,Base> BaseFunc,它在运行时获取fullpathdllfullpath之类的参数
  3. 绑定代码包含使用运行时参数的绑定,其中库和类型名称可以在运行时注入
  4. Main方法是测试方法,它在运行时获取Test对象,并调用DoWork方法在运行时注入相关对象

如果您只想使用Unity,那么它只需要一个类似的ToMethod选项即可为Func注入提供帮助。

编辑1:

根据@JohnWu的建议,假设Derived类还需要注入派生类Base1的基类Derived1,现在这是可能的,但是我们需要显式的构造函数参数ctx.Kernel.Get<Base1>(),如下面的代码所示。这不是真正的DI,而是服务位置,但这是使用反射创建Base类的实例的限制,自动DI可以在使用DI框架创建聚合类的情况下工作。

public class Base1
{
    public virtual void DoWork()
    {
        Console.WriteLine("Base..1");
    }
}

public class Derived1 : Base1
{        
    public override void DoWork()
    {
        Console.WriteLine("Derived..1");
    }
}

派生类将修改为:

public class Derived : Base
{
    private Base1 MyBase1 { get;}

    public Derived(Base1 myBase1)
    {
        MyBase1 = myBase1;
    }

    public override void DoWork()
    {
        Console.WriteLine("Derived..0");
        MyBase1?.DoWork();
    }
}

使用反射创建对象实例将是:

 var classInstance = Activator.CreateInstance(t, ctx.Kernel.Get<Base1>());

 return (Base)classInstance;

这将调用构造函数:

  public Derived(Base1 myBase1)