IoC、Dll 引用和程序集扫描

10
虽然这个问题与StructureMap相关,但我的一般问题是:

在使用IoC容器编写代码时(而不是通过xml进行配置),你通常需要明确指定所有程序集的项目/构建引用吗?

为什么要分离程序集呢?因为:


"把抽象类和它们的具体实现放在不同的程序集中是实现这种分离的好方法。" -Framework Design Guidelines p.91


例如:

假设我有PersonBase.dllBob.dll

Bob继承自抽象类PersonBase。它们都在Person命名空间中,但在不同的程序集中

我编写的代码是面向PersonBase的,而不是面向Bob的。

回到我的主要代码,我需要一个人。 StructureMap可以扫描程序集。太好了,我会要求StructureMap提供一个人!

现在,在我的主要代码中,当然只涉及PersonBase,而不是Bob。我实际上不希望我的代码了解Bob的任何信息。没有项目引用,什么也没有。这就是整个问题的重点。

所以我想说:

//Reference: PersonBase.dll (only)
using Person;  
...

//this is as much as we'll ever be specific about Bob:
Scan( x=> { x.Assembly("Bob.dll"); }

//Ok, I should now have something that's a PersonBase (Bob). But no ?
ObjectFactory.GetAllInstances<PersonBase>().Count == 0

没什么运气。起作用的是明确表达我想要Bob:

//Reference: PersonBase.dll and Bob.dll
using Person; 
...
Scan( x => {x.Assembly("Bob.dll"); }

//If I'm explicit, it works. But Bob's just a PersonBase, what gives?
ObjectFactory.GetAllInstances<Bob>().Count == 1 //there he is!

但现在我不得不在我的项目中引用 Bob.dll,这正是我所不想要的。

我可以使用Spring + Xml配置来避免这种情况。但是接着我又回到了Spring + Xml配置...!

我是否在使用StructureMap时遗漏了什么,或者说,作为一般原则,(流畅的)IoC配置需要显式引用所有程序集?

可能相关的问题: StructureMap和扫描程序集

4个回答

15

我终于解决了这个问题。它看起来像这样:

IoC Uml http://img396.imageshack.us/img396/1343/iocuml.jpg

使用以下程序集:

  • Core.exe
  • PersonBase.dll (由Core.exe编译时引用)
  • Bob.dll (运行时通过StructureMap扫描加载)
  • Betty.dll (运行时通过StructureMap扫描加载)

为了在StructureMap中实现它,我需要一个自定义的"ITypeScanner"来支持程序集扫描:

public class MyScanner : ITypeScanner {
  public void Process(Type type, PluginGraph graph) {

    if(type.BaseType == null) return;

    if(type.BaseType.Equals(typeof(PersonBase))) {
      graph.Configure(x => 
        x.ForRequestedType<PersonBase>()
        .TheDefault.Is.OfConcreteType(type));
    }
  }
} 

所以我的主要代码看起来像:

ObjectFactory.Configure(x => x.Scan (
  scan =>
  {
    scan.AssembliesFromPath(Environment.CurrentDirectory 
    /*, filter=>filter.You.Could.Filter.Here*/);

    //scan.WithDefaultConventions(); //doesn't do it

    scan.With<MyScanner>();
  }
));

ObjectFactory.GetAllInstances<PersonBase>()
 .ToList()
  .ForEach(p => 
  { Console.WriteLine(p.FirstName); } );

3
无需创建自己的扫描器。在扫描表达式中,使用AddAllTypesOf来扫描PersonBase。http://structuremap.net/structuremap/ScanningAssemblies.htm#section5 - Joshua Flanagan

1

你也可以使用StructureMap进行XML配置。如果需要,甚至可以混合使用。

此外,你还可以在Bob类中添加StructureMap属性,以告诉StructureMap如何加载程序集。其中一个我经常使用的是DefaultConstructor。


谢谢回复,Chris :) 如果我不得不回到Xml装配,我会坚持使用Spring。我认为我上面的东西在结构/概念上是有根据的。我的问题是,似乎Ioc-configured-in-code始终需要dll引用,而我特别想避免这种情况。 - Jeffrey Knight
没问题。Spring.net 也没有问题。但要小心不要过度设计。引入太多项目到一个解决方案中会增加构建时间。让某些事情更可配置,就会创建更多的测试表面。 - Chris Brandsma

0

只有在遵循命名、程序集和命名空间约定时,自动扫描选项才能正常工作。您可以使用流畅的接口手动配置StructureMap。例如:

ObjectFactory.Initialize(initialization => 
   initialization.ForRequestedType<PersonBase>()
    .TheDefault.Is.OfConcreteType<Bob>());

在这种情况下,基本上是我的第二种情况,它可以工作但涉及dll引用 - 我需要对Bob.dll的dll引用。这就是我想避免的。 - Jeffrey Knight
1
[...] .Is.OfConcreteType<Bob>() //<-- 你需要引用 Bob.dll 的 dll 参考才能这样做 - Jeffrey Knight
我理解你的意思,但是仍然不可能在没有对要配置的类型产生依赖的情况下设置IoC容器。 - Paco
已经解决了 - 请查看下面的答案 - Jeffrey Knight

0

我们在当前项目中所做的事情(使用AutoFac而不是StructureMap,但我认为这不应该有任何区别):

我们在核心程序集中定义了应用程序使用的外部服务接口,比如说App.Core(就像你的PersonBase一样)。

然后我们在Services.Real(比如Bob.dll)中实现了这些接口。

在我们的情况下,我们还有Service.Fake,用于便于UI测试,具有对其他企业服务和数据库等的依赖关系。

前端“客户端”应用程序本身(在我们的情况下,是ASP.NET MVC应用程序)引用了App.Core

当应用程序启动时,我们使用Assembly.Load来加载适当的“服务”实现DLL,基于配置设置。

这些DLL中的每一个都有一个IServiceRegistry的实现,返回它实现的服务列表:

public enum LifestyleType { Singleton, Transient, PerRequest}

public class ServiceInfo {
    public Type InterfaceType {get;set;}
    public Type ImplementationType {get;set;}
    // this might or might not be useful for your app, 
    // depending on the types of services, etc.
    public LifestyleType Lifestyle {get;set;} 
}

public interface IServiceRegistry {
    IEnumerable<ServiceInfo> GetServices();
}

...应用程序通过反射找到这个ServiceRegistry,枚举这些ServiceInfo实例并在容器中注册它们。对于我们来说,这个register-all-services存在于Web应用程序中,但在许多情况下,将其放在单独的程序集中是可能的(也更可取)。

这样,我们就可以将领域逻辑与基础设施代码隔离开来,并防止“只是这一次”的解决方法,其中应用程序最终依赖于对基础设施代码的直接引用。我们还避免了在每个服务实现中都必须引用容器的情况。

如果您正在执行此操作,则有一个非常重要的事情:确保您有测试来验证您可以使用IOC容器的每个潜在配置创建每个“顶级”类型(在我们的情况下,ASP.NET MVC控制器)。

否则,很容易忘记实现一个接口并破坏应用程序的大部分内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接