在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)));完整的示例在这里。

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

  1. 在注册B的依赖项时,他们必须知道自己注册在B拥有的作用域中,这感觉就像一种代码气味。

  2. 当B有很多依赖项而这些依赖项又有依赖项等时,它不能很好地扩展。所有这些依赖关系都需要像上面的问题1那样进行额外的注册。

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

更新

我已将OwnedPerRequest<T>相关代码移到下面的答案中

在Autofac中,如何注册Owned<B>当A是单个实例并且B使用InstancePerRequest注册时,

这个答案是基于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),那么你可能只是切换BInstancePerLifetimeScope():

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

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

唯一的警告是,你必须小心不要意外地从其他地方的单例中获取对B的依赖,因为这将不再被检测为错误。可以使用自定义IComponentLifetime来防止这种情况,但可能不值得这样做,除非您经常这样做。