我有一个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>());
...
}
}
但这意味着:
ConcreteFooRegistration
的汇编(例如foo.dll) 包含部分/全部AFoo
,BFoo
等。ConcreteFooRegistration
的程序集(例如foo.dll)引用包含部分/全部AFoo
,BFoo
的程序集(例如foo2.dll),等IFooProvider
可用于包含BaseFoo
派生类的任何其他程序集以及注册它们的Module
为了便于讨论,假设这些都不可能和/或不可取。也就是说,我正在寻找除#34;移动IFooProvider
以外的解决方案。进入shared.dll&#34;。
由于AFoo
和BFoo
是其他类型感兴趣的真正依赖关系,而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
的任意子类吗?
谢谢!
答案 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());
}
}
好。
这是重要的一点,现在:注册来源。注册来源是您的目的:
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来源,particularly under the Autofac.Features
namespace。在那里,您会找到things like the CollectionRegistrationSource
,负责处理IEnumerable<T>
支持。