您可以将 Autofac 的 Owned 与标记界面一起使用吗?

Can you use Autofac's Owned with a marker interface?

我想使用 Autofac 为给定的工作单元创建一个或多个 WCF 通道的新实例。我想使用命令模式来表示工作单元,即给定命令 class 注入它需要的通道并实现一系列相关操作。

我尝试了以下方法:

interface IUnitOfWork
{

}

class FooCall : IUnitOfWork
{
    readonly BarChannel _channel;

    public FooCall(BarChannel channel)
    {
        Console.WriteLine($"FooCall({new {channel}})");
        _channel = channel;
    }

    public string Foo()
    {
        return "FOO";
    }
}

class BarChannel
{
    public BarChannel()
    {
        Console.WriteLine("BarChannel()");
    }
}

class FooService
{
    Func<Owned<FooCall>> _helperFn;

    public FooService(Func<Owned<FooCall>> helperFn)
    {
        _helperFn = helperFn;
    }

    public void CallFoo()
    {
        using (var helper = _helperFn())
        {
            Console.WriteLine($"CallFoo(), helper={helper}");
            helper.Value.Foo();
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<BarChannel>().InstancePerOwned<IUnitOfWork>();
        builder.RegisterType<FooCall>().AsImplementedInterfaces().AsSelf();
        builder.RegisterType<FooService>();

        using (var scope = builder.Build().BeginLifetimeScope())
        {
            Console.WriteLine("call 1");
            scope.Resolve<FooService>().CallFoo();
            Console.WriteLine("call 2");
            scope.Resolve<FooService>().CallFoo();
        }
    }
}

简而言之:一个服务方法创建一个拥有的工作单元;工作单元注入了它调用的每个拥有的通道。代码示例应显示正在创建的两个通道实例。

除了为拥有的依赖项创建的生命周期范围似乎只标记了依赖项被解析的类型 - 即 FooCall,而不是 IUnitOfWork。如果我将 BarChannel 注册为 InstancePerOwned<FooCall>,则代码有效;按原样,注册为 InstancePerOwned<IUnitOfWork>,它无法解析 FooService,因为它找不到匹配的生命周期范围。我是不是遗漏了什么或者我想用 Autofac 做不到的事情?我宁愿不必为 每个 命令 class 将我的所有 WCF 通道注册为实例拥有,这似乎会变得非常冗长。另一种解决方法是使用 instance-per-depedency 并直接解析 Func,但这不会让我说在重用通道及其之间的依赖关系时组合工作单元。

问题是 InstancePerOwned<T> 实际上只是 InstancePerMatchingLifetimeScope(params object[] lifetimeScopeTag) 的一个特例,其中的范围被标记为 typeof(T)。就目前而言,那里提供的标签和尝试解析时附加到范围的标签之间需要有一个 direct link,它总是设置为该特定 Owned<> 依赖项中的任何内容。此时没有额外的逻辑来暗示类型之间的关系,它只是标签上的直接匹配。

但是,InstancePerMatchingLifetimeScope 允许指定多个标签,因此可以执行以下操作:

builder.RegisterType<BarChannel>()
    .InstancePerMatchingLifetimeScope(new TypedService(typeof(FooCall)),new TypedService(typeof(AnotherUnitOfWork))); 

为了更整洁地总结一下,您可以使用:

private static IEnumerable<Type> GetTypesImplementingInterface<TInterface>()
{
    return AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany(s => s.GetTypes())
        .Where(p => typeof(TInterface).IsAssignableFrom(p));
}

然后是新的扩展方法:

public static class AutofacRegistrationBuilderExtensions
{
    public static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> InstancePerOwned<TLimit, TActivatorData, TRegistrationStyle>(
        this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> builder, IEnumerable<Type> serviceTypes)
    {
        return builder.InstancePerMatchingLifetimeScope(serviceTypes.Select(s => new TypedService(s)).ToArray());
    }
}

那么用法就是:

builder.RegisterType<BarChannel>().InstancePerOwned(GetTypesImplementingInterface<IUnitOfWork>());

我不确定最后一部分是否值得引入 Autofac 本身,但我想如果确实如此,那么将上述两种方法组合在一起并从现有的类型中检索适用的类型列表可能会更好注册,例如像

InstancePerOwnedImplementing<TInterface>();

或者,扩展匹配范围逻辑以在解析时检查类型之间的关系可能会有点混乱,因为并非所有标签都是类型类型。