在Autofac中,当A是单实例,B使用InstancePerRequest注册时,如何注册Owned<B>关系?

In Autofac, how do you register the Owned<B> relationship when A is a single instance and B is registered using InstancePerRequest?

我正在使用 Autofac 版本 3.5.2。

我想注册一个单例 class A 直到将来某个时候需要 B。如果 B 是使用 InstancePerRequest 注册的,你该怎么做?

直接使用Owned<B>是行不通的,因为每个请求的生命周期范围不存在。你得到了 DependencyResolutionExceptionhere.

一个解决方案是让 A 直接依赖于 ILifetimeScope。当 A 需要 B 的实例时,它以 MatchingScopeLifetimeTags.RequestLifetimeScopeTag 开始范围。

    using (var scope = this.lifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
    {
        var b = scope.Resolve<B>();
        b.DoSomething();
    }

我不喜欢这种方法,因为它是服务定位器模式。看到这个 fiddle.

第二种解决方案是将 B 注册为 InstancePerRequest 并在 B 的正确拥有范围内。注册 B 如下所示:builder.RegisterType<B>().InstancePerRequest(new TypedService(typeof(B))); 完整的例子是 here.

我更喜欢第二种解决方案,但它也有问题:

  1. 注册 B 的依赖项时,他们必须知道才能在 B 拥有的范围内注册自己,这感觉就像代码的味道。

  2. 当 B 有很多依赖关系并且这些依赖关系有依赖关系等时,它不能很好地扩展。所有这些依赖关系都需要额外注册,如上面的问题 1。

您建议如何解决这个问题?感谢您的帮助。

更新

我已经把OwnedPerRequest<T>相关的代码移到了下面的里面。

我想你想要做的是注入一个 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 的 "request" 下嵌套更多的生命周期范围),那么您可以切换 BInstancePerLifetimeScope():

builder.RegisterType<B>().InstancePerLifetimeScope();

这在很大程度上等同于每个请求,但也允许 Owned<B> 由单例成功解析。

唯一需要注意的是,您必须小心不要不小心从其他地方的单例中依赖 B,因为这将不再被检测为错误。可以使用自定义 IComponentLifetime 来防止这种情况,但除非您经常这样做,否则可能不值得付出努力。

此答案基于 以及他在 IRegistrationSource

上的博客 post

它基于现有的Owned<T> class 及其 registration source.

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";
}