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

时间:2015-08-07 19:32:39

标签: autofac

我有一个Factory界面(以及具体的实现):

// 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) 包含部分/全部AFooBFoo等。
  2. 包含ConcreteFooRegistration的程序集(例如foo.dll)引用包含部分/全部AFooBFoo的程序集(例如foo2.dll),等
  3. IFooProvider可用于包含BaseFoo派生类的任何其他程序集以及注册它们的Module
  4. 为了便于讨论,假设这些都不可能和/或不可取。也就是说,我正在寻找除#34;移动IFooProvider以外的解决方案。进入shared.dll&#34;。

    由于AFooBFoo是其他类型感兴趣的真正依赖关系,而IFooProvider(从那个角度来看)只是一个实例化细节,我受{{3尼古拉斯想出来的。我在其他地方使用了类似的方法,所以我写了一个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);
            };
        }
    }
    

    这是成功的,因为我能够BaseFoo 删除所有<{strong>个人ConcreteFooRegistration来源的注册,但仍能成功解析任意 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)
       { ... }
    
       ...
    }
    

    但是现在我无法在构造函数注入之外任意解析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的公共导出或将其移至shared.dll是不可取/不可能的,因此消除上面的天真但不足为奇的实现,(如何)我可以设置我的注册能够从任何地方解析BaseFoo的任意子类吗?

    谢谢!

1 个答案:

答案 0 :(得分:2)

我认为您所寻找的是注册来源。注册来源是一个动态的“注册提供商”,您可以根据需要提供自动注册。

截至撰写本文时,the doc on registration sources is pretty thin(我只是没有机会写出来),但a blog article有一些细节。

注册来源是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. 将动态注册返回到解析操作,以便它可以完成工作。
  4. 看起来像这样:

    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来源particularly under the Autofac.Features namespace。在那里,您会找到things like the CollectionRegistrationSource,负责处理IEnumerable<T>支持。