我有一个工厂接口(以及具体的实现):
// 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>());
...
}
}
但这意味着:
- 包含
ConcreteFooRegistration
的程序集(例如foo.dll) 也包含AFoo
,BFoo
等的部分/全部 - 包含
ConcreteFooRegistration
的程序集(例如foo.dll) 引用包含部分/全部AFoo
,BFoo
等的程序集(例如foo2.dll) -
IFooProvider
可用于任何其他包含BaseFoo
派生类和注册它们的Module
的程序集
为了方便讨论,假设这些都是不可能的和/或不需要的。也就是说,我正在寻找解决方案,而不是"移动IFooProvider
到shared.dll"。
由于AFoo
和BFoo
是其他类型感兴趣的真正依赖项,而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>
之类的东西的方式 -我们不要求您实际注册每个集合,而是使用源动态地将注册提供给容器。
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());
}
}
。
现在是重要的部分:注册源。注册源位于以下位置:
- 判断解析操作是否请求
BaseService
类型。如果它不是,那么你无法处理它,所以你会退出。 - 为被请求的特定类型的
BaseService
派生构建动态注册,其中将包括调用提供者/工厂以获取实例的lambda。 - 返回动态注册到解析操作,以便它可以完成工作。
它看起来像这样:
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>
支持。