在Autofac中,如何注册Owned当A是单个实例并且B使用InstancePerRequest注册时,
本文关键字:注册 单个 实例 InstancePerRequest 使用 何注册 Autofac Owned | 更新日期: 2023-09-27 18:03:58
我使用的是Autofac 3.5.2版。
我想注册一个单例类a,它需要B,直到将来的某个点。如果B是使用InstancePerRequest
注册的,您如何做到这一点?
直接使用Owned<B>
不能工作,因为每个请求生命周期范围不存在。你得到的DependencyResolutionException
如下图所示。
一个解决方案是使A直接依赖于ILifetimeScope
。当A需要b的实例时,用MatchingScopeLifetimeTags.RequestLifetimeScopeTag
开始作用域
using (var scope = this.lifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
var b = scope.Resolve<B>();
b.DoSomething();
}
我不喜欢这种方法,因为它是服务定位器模式。看这提琴
第二个解决方案是将B注册为InstancePerRequest
,并在B的正确拥有范围内。注册B看起来像这样:builder.RegisterType<B>().InstancePerRequest(new TypedService(typeof(B)));
完整的示例在这里。
我更喜欢第二个解决方案,但它也有问题:
在注册B的依赖项时,他们必须知道自己注册在B拥有的作用域中,这感觉就像一种代码气味。
当B有很多依赖项而这些依赖项又有依赖项等时,它不能很好地扩展。所有这些依赖关系都需要像上面的问题1那样进行额外的注册。
你建议如何解决这个问题?谢谢你的帮助。
更新我已将OwnedPerRequest<T>
相关代码移到下面的答案中
这个答案是基于Nicholas的评论和他在IRegistrationSource的博客文章
它基于现有的Owned<T>
类及其注册源。
public class OwnedPerRequest<T> : Owned<T>
{
public OwnedPerRequest(T value, IDisposable lifetime) : base(value, lifetime) { }
}
public class OwnedPerRequestInstanceRegistrationSource : IRegistrationSource
{
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
if (service == null)
throw new ArgumentNullException(nameof(service));
if (registrationAccessor == null)
throw new ArgumentNullException(nameof(registrationAccessor));
var swt = service as IServiceWithType;
if (swt == null
|| !(swt.ServiceType.IsGenericType
&& swt.ServiceType.GetGenericTypeDefinition() == typeof(OwnedPerRequest<>)))
return Enumerable.Empty<IComponentRegistration>();
var ownedInstanceType = swt.ServiceType.GetGenericArguments()[0];
var ownedInstanceService = swt.ChangeType(ownedInstanceType);
return registrationAccessor(ownedInstanceService)
.Select(r =>
{
var rb = RegistrationBuilder.ForDelegate(swt.ServiceType, (c, p) =>
{
var lifetime = c.Resolve<ILifetimeScope>().BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
try
{
var value = lifetime.ResolveComponent(r, p);
return Activator.CreateInstance(swt.ServiceType, value, lifetime);
}
catch
{
lifetime.Dispose();
throw;
}
});
return rb
.ExternallyOwned()
.As(service)
.Targeting(r)
.CreateRegistration();
});
}
public bool IsAdapterForIndividualComponents => true;
public override string ToString() => "OwnedPerRequestInstanceregistrationSource";
}
我认为你要做的是注入一个Func<Owned<B>>
,当你需要b时调用它。这就删除了服务定位器模式,我很确定它在功能上与这个
using (var scope = this.lifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
var b = scope.Resolve<B>();
b.DoSomething();
}
如果你注入一个Func<Owned<B>>
,用法将是这样的:
public void DoSomethingThatUsesB()
{
//_bFactory is your Func<Owned<B>>
using(var b = _bFactory.Invoke())
{
... (use b)
}
}
如果你的应用程序的生命周期结构是相当简单的(即你不嵌套进一步的生命周期范围下的"请求",需要解析共享的B
),那么你可能只是切换B
到InstancePerLifetimeScope()
:
builder.RegisterType<B>().InstancePerLifetimeScope();
这将在很大程度上等同于每个请求,但也将允许Owned<B>
被一个单例成功解析。
唯一的警告是,你必须小心不要意外地从其他地方的单例中获取对B
的依赖,因为这将不再被检测为错误。可以使用自定义IComponentLifetime
来防止这种情况,但可能不值得这样做,除非您经常这样做。