Многие платформы IoC поддерживают регистрацию фабричной функции (или лямбда-выражения), которая принимает в качестве одного из аргументов экземпляр самого контекста контейнера / области видимости / разрешения.
Это позволяет использовать дополнительные уровни косвенности, а также использовать информацию, которая однозначно определяет контекст или область действия.Кроме того, многие предоставляют хуки, такие как обработчики событий или возможность наследования от класса области жизненного цикла, для взаимодействия с областью, которая начинается или заканчивается.
Принцип
ДляAutoFac и ваш конкретный пример, следующий принцип будет работать, используя дополнительные уровни косвенности в регистрации.
// Inject `Data` instance resolved from current scope.
builder.Register<IMyService>(ctx => new MyService(ctx.Resolve<Data>()));
// Extra level of indirection, get a "factory" for a 'Data' instance.
builder.Register<Data>(ctx => ctx.Resolve<Func<Data>>()()).InstancePerLifetimeScope();
// The indirection resolves to a map of scopes to "factory" functions.
builder.Register<Func<Data>>(ScopedDataExtensions.GetFactory);
Мы можем использовать любое доступное уникальное свойство в контексте / области действия для построения этого отображения.
// Maps scopes to data "factories".
public static class ScopedDataExtensions
{
private static readonly ConcurrentDictionary<object, Func<Data>> _factories = new ConcurrentDictionary<object, Fund<Data>>();
public static Func<Data> GetFactory(this IComponentContext ctx)
{
var factory = default(Func<Data>);
return _factories.TryGetValue(ctx.ComponentRegistry, out factory) ? factory : () => null;
}
public static void SetFactory(this ILifetimeScope scope, Func<Data> factory)
{
_factories[scope.ComponentRegistry] = factory;
}
}
Мы можем использовать его таким образом, чтобы предоставлять «локальные» экземпляры данных, которые будут внедрены в наши экземпляры сервисов с определенной областью.
var myData = new Data("nested");
nestedScope.SetFactory(() => myData);
// ...
var myService = nestedScope.Resolve<IMyService>();
Более полный и общий пример для AutoFac приведен ниже.
Общий класс расширения для этого шаблона
public static class AutofacScopeExtensions
{
// Map from context => factories per type
public static readonly ConcurrentDictionary<object, ConcurrentDictionary<Type, object>> _factories =
new ConcurrentDictionary<object, ConcurrentDictionary<Type, object>>();
private static class ScopedFactoryFor<T>
{
public static Func<T> DefaultFactory = () => default(T);
public static Func<T> GetFactory(ConcurrentDictionary<Type, object> fromContext)
{
object factory;
return (fromContext.TryGetValue(typeof(T), out factory)) ? (Func<T>)factory : DefaultFactory;
}
}
public static IRegistrationBuilder<T, SimpleActivatorData, SingleRegistrationStyle>
WithContextFactoryFor<T>(this ContainerBuilder builder, Func<T> defaultFactory = null)
{
if (defaultFactory != null)
ScopedFactoryFor<T>.DefaultFactory = defaultFactory;
builder.Register<Func<T>>(AutofacScopeExtensions.GetFactory<T>);
return builder.Register<T>(ctx => ctx.Resolve<Func<T>>()());
}
public static IContainer BuildContainer(this ContainerBuilder builder)
{
var container = builder.Build();
container.ChildLifetimeScopeBeginning += OnScopeStarting;
return container;
}
public static ILifetimeScope SetScopeFactory<T>(this ILifetimeScope scope, Func<T> factory)
{
ScopeMapFor(scope)[typeof(T)] = factory;
return scope;
}
public static ILifetimeScope SetScopeValue<T>(this ILifetimeScope scope, T instance)
{
return SetScopeFactory(scope, () => instance);
}
public static Func<T> GetFactory<T>(IComponentContext ctx)
{
return ScopedFactoryFor<T>.GetFactory(ScopeMapFor(ctx));
}
private static ConcurrentDictionary<Type, object> ScopeMapFor(IComponentContext ctx)
{
return _factories.GetOrAdd(ctx.ComponentRegistry, x => new ConcurrentDictionary<Type, object>());
}
private static void OnScopeStarting(object sender, LifetimeScopeBeginningEventArgs evt)
{
evt.LifetimeScope.ChildLifetimeScopeBeginning += OnScopeStarting;
evt.LifetimeScope.CurrentScopeEnding += OnScopeEnding; // so we can do clean up.
}
private static void OnScopeEnding(object sender, LifetimeScopeEndingEventArgs evt)
{
var map = default(ConcurrentDictionary<Type, object>);
if (_factories.TryRemove(evt.LifetimeScope.ComponentRegistry, out map))
map.Clear();
}
}
Допускает следующий синтаксис для регистрации:
builder.WithContextFactoryFor<Data>(() => new Data("Default")).InstancePerLifetimeScope();
builder.Register<IMyService>(ctx => new MyService(ctx.Resolve<Data>()));
И разрешается следующим образом:
// ...
var myData = new Data("Some scope");
// ...
context.SetScopeFactory(() => myData);
// ...
// Will inject 'myData' instance.
var myService = context.Resolve<IMyService>();
Упрощенная альтернатива
Если вы явно запускаете вложенные области и в то же время вы знаете, как должен создаваться экземпляр Data
с областью действия, вы можете пропустить класс расширения и зарегистрировать «фабричный» делегат с вложенной областью действия при создании:
var nestedScope = container.BeginLifetimeScope(
"L2",
x => x.RegisterInstance<Func<Data>>(() => new Data("nested")));