如何使用Autofac注册(无界)类型层次结构?



我有一个工厂接口(以及具体的实现):

// foo.dll
interface IFooProvider
{
    T GetFoo<T>()
        where T : BaseFoo;
}

我的BaseFoo不是抽象的,但只有它的子类是真正有用的:

// shared.dll
class BaseFoo
{ ... }

我也有一个(可能无限)BaseFoo的子类跨越许多程序集的数量:

// foo.dll
class AFoo : BaseFoo
{ ... }
// foo2.dll
class BFoo : BaseFoo
{ ... }
... and many more ...

我天真地以一种不令人惊讶的方式注册了Foo派生类:

// foo.dll
class ConcreteFooRegistration : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        // a concrete FooProvider is registered elsewhere
        builder.Register(c => c.Resolve<IFooProvider>().GetFoo<AFoo>());
        builder.Register(c => c.Resolve<IFooProvider>().GetFoo<BFoo>());
        ...
    }
}

但这意味着:

  1. 包含ConcreteFooRegistration的程序集(例如foo.dll) 包含AFoo, BFoo等的部分/全部
  2. 包含ConcreteFooRegistration的程序集(例如foo.dll) 引用包含部分/全部AFoo, BFoo等的程序集(例如foo2.dll)
  3. IFooProvider可用于任何其他包含BaseFoo派生类和注册它们的Module的程序集

为了方便讨论,假设这些都是不可能的和/或不需要的。也就是说,我正在寻找解决方案,而不是"移动IFooProvider到shared.dll"。

由于AFooBFoo是其他类型感兴趣的真正依赖项,而IFooProvider(从这个角度来看)只是一个实例化细节,我受到了Nicholas提出的Autofac+Serilog集成的启发。我在其他地方使用了类似的方法,所以我编写了一个AttachToComponentRegistration()实现:

// foo.dll
class ConcreteFooRegistration : Module
{
    // NOTICE: there's no Load() method
    protected override void AttachToComponentRegistration(...)
    {
        ...
        registration.Preparing += (sender, e) =>
        {
          var pFoo = new ResolvedParameter(
              (p, i) => p.ParameterType.IsAssignableTo<BaseFoo>(),
              (p, i) => i.Resolve<IFooProvider>().GetFoo<FooWeNeed>()
          );
          e.Parameters = new [] { pFoo }.Concat(e.Parameters);
        };
    }
}

这是成功的,因为我能够ConcreteFooRegistration中删除所有单个BaseFoo派生的注册,并且仍然成功地通过构造函数注入解决任意 BaseFoo派生的依赖:

// other.dll:
class WorkerRegisteration : Module
{
   protected override void Load(ContainerBuilder builder)
   {
       builder.RegisterType<Worker>();
       // NOTICE: FooYouDidntKnowAbout is NOT explicitly registered
   }
}
class Worker
{
   public Worker(FooYouDidntKnowAbout foo)
   { ... }
   ...
}

BUT:现在我不能在构造函数注入之外任意解析AFoo:

builder.Register(c =>
{
    // here's one use for a BaseFoo outside constructor injection
    var foo = c.Resolve<AFoo>();
    if (foo.PropValue1)
        return new OtherClass(foo.PropValue2);
    else
        return new YetAnother(foo.PropValue3);
}
...
builder.Register(c =>
{
    // here's another
    var foo = c.Resolve<AFoo>();
    return c.Resolve(foo.TypePropValue);
});

假设发布IFooProvider作为foo.dll的公共导出或将其移动到共享.dll是不希望/不可能的,从而消除了上面的天真但不奇怪的实现,(如何)我可以设置我的注册,以便能够从任何地方解决BaseFoo的任意子类?

谢谢!

我认为你正在寻找的是一个注册源。注册源是一个动态的"注册提供者",您可以使用它来根据需要提供Autofac注册。

在写这篇文章的时候,关于注册资源的文档非常少(我只是没有机会写它),但是有一篇博客文章详细介绍了它。

注册源是Autofac支持IEnumerable<T>Lazy<T>之类的东西的方式 -我们不要求您实际注册每个集合,而是使用源动态地将注册提供给容器。

无论如何,让我在这里给你写一个样本,也许我以后可以用它来按摩它到文档中,嗯?:) 首先,让我们定义一个非常简单的工厂和实现。我要用"服务"而不是"Foo",因为我的大脑在看到"Foo"太多次后就会出错。那是"我"的事。但是我离题了。
public interface IServiceProvider
{
    T GetService<T>() where T : BaseService;
}
public class ServiceProvider : IServiceProvider
{
    public T GetService<T>() where T : BaseService
    {
        return (T)Activator.CreateInstance(typeof(T));
    }
}

好的,现在让我们创建服务类型。显然,对于这个示例,所有类型都在一个程序集中,但是当代码引用该类型并且JIT从其他程序集中引入它时,它的工作原理是一样的。不用担心交叉组装的东西。

public abstract class BaseService { }
public class ServiceA : BaseService { }
public class ServiceB : BaseService { }

最后是几个使用服务的类,这样我们就可以看到它是如何工作的。

public class ConsumerA
{
    public ConsumerA(ServiceA service)
    {
        Console.WriteLine("ConsumerA: {0}", service.GetType());
    }
}

public class ConsumerB
{
    public ConsumerB(ServiceB service)
    {
        Console.WriteLine("ConsumerB: {0}", service.GetType());
    }
}

现在是重要的部分:注册源。注册源位于以下位置:

  1. 判断解析操作是否请求BaseService类型。如果它不是,那么你无法处理它,所以你会退出。
  2. 为被请求的特定类型的BaseService派生构建动态注册,其中将包括调用提供者/工厂以获取实例的lambda。
  3. 返回动态注册到解析操作,以便它可以完成工作。

它看起来像这样:

using Autofac;
using Autofac.Core;
using Autofac.Core.Activators.Delegate;
using Autofac.Core.Lifetime;
using Autofac.Core.Registration;
public class ServiceRegistrationSource : IRegistrationSource
{
    public IEnumerable<IComponentRegistration> RegistrationsFor(
        Service service,
        Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
    {
        var swt = service as IServiceWithType;
        if(swt == null || !typeof(BaseService).IsAssignableFrom(swt.ServiceType))
        {
            // It's not a request for the base service type, so skip it.
            return Enumerable.Empty<IComponentRegistration>();
        }
        // This is where the magic happens!
        var registration = new ComponentRegistration(
            Guid.NewGuid(),
            new DelegateActivator(swt.ServiceType, (c, p) =>
            {
                // The factory method is generic, but we're working
                // at a reflection level, so there's a bit of crazy
                // to deal with.
                var provider = c.Resolve<IServiceProvider>();
                var method = provider.GetType().GetMethod("GetService").MakeGenericMethod(swt.ServiceType);
                return method.Invoke(provider, null);
            }),
            new CurrentScopeLifetime(),
            InstanceSharing.None,
            InstanceOwnership.OwnedByLifetimeScope,
            new [] { service },
            new Dictionary<string, object>());
        return new IComponentRegistration[] { registration };
    }
    public bool IsAdapterForIndividualComponents { get{ return false; } }
}

看起来很复杂,但还不算太糟。

最后一步是注册工厂和注册源。对于我的示例,我将它们放在Autofac模块中,因此它们都注册在一起-只有一个而没有另一个是没有意义的。

public class ServiceProviderModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<ServiceProvider>().As<IServiceProvider>();
        builder.RegisterSource(new ServiceRegistrationSource());
    }
}

最后,让我们看看它的实际应用。如果我将这段代码放入控制台应用程序中…

static void Main()
{
    var builder = new ContainerBuilder();
    builder.RegisterType<ConsumerA>();
    builder.RegisterType<ConsumerB>();
    builder.RegisterModule<ServiceProviderModule>();
    var container = builder.Build();
    using(var scope = container.BeginLifetimeScope())
    {
        var a = scope.Resolve<ConsumerA>();
        var b = scope.Resolve<ConsumerB>();
    }
}

你最终在控制台上看到的是:

ConsumerA: ServiceA
ConsumerB: ServiceB

注意,我必须注册我的消费类,但我没有显式注册任何BaseService派生类-所有这些都是由注册源完成的。

如果您想查看更多注册源示例,请查看Autofac源,特别是在Autofac.Features命名空间下。在那里,您会发现CollectionRegistrationSource之类的东西,它负责处理IEnumerable<T>支持。

最新更新