【问题标题】:Autofac - How to create a generated factory with parametersAutofac - 如何使用参数创建生成的工厂
【发布时间】:2017-03-30 03:06:38
【问题描述】:

我正在尝试使用 Autofac 创建一个“生成”工厂,它将根据枚举参数实时解决依赖关系。

给定以下接口/类

public delegate IConnection ConnectionFactory(ConnectionType connectionType);

public enum ConnectionType
{
    Telnet,
    Ssh
}

public interface IConnection
{
    bool Open();
}

public class SshConnection : ConnectionBase, IConnection
{
    public bool Open()
    {
        return false;
    }
}

public class TelnetConnection : ConnectionBase, IConnection
{
    public bool Open()
    {
        return true;
    }
}

public interface IEngine
{
    string Process(ConnectionType connectionType);
}

public class Engine : IEngine
{
    private ConnectionFactory _connectionFactory;

    public Engine(ConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public string Process(ConnectionType connectionType)
    {
        var connection = _connectionFactory(connectionType);

        return connection.Open().ToString();
    }
}

我想使用 Autofac 生成某种工厂,该工厂有一个接收一个参数的方法:ConnectionType 并返回正确的连接对象。

我从以下注册开始:

builder.RegisterType<AutoFacConcepts.Engine.Engine>()
    .As<IEngine>()
    .InstancePerDependency();

builder.RegisterType<SshConnection>()
    .As<IConnection>();
builder.RegisterType<TelnetConnection>()
    .As<IConnection>();

然后我继续使用不同选项的 TelnetConnection/SshConnection 注册:

  1. 命名
  2. 键控
  3. 元数据

我找不到允许我定义生成的工厂委托的正确注册组合,该委托将返回正确的连接对象(用于 ConnectionType.Ssh 的 SshConnection 和用于 ConnectionType.Telnet 的 TelnetConnection)。

【问题讨论】:

    标签: c# .net autofac


    【解决方案1】:

    更新Engine 类以采用Func&lt;ConnectionType, IConnection&gt; 而不是委托。 Autofacsupports creating delegate factories on the fly via Func&lt;T&gt;.

    public class Engine : IEngine
    {
        private Func<ConnectionType, IConnection> _connectionFactory;
    
        public Engine(Func<ConnectionType, IConnection> connectionFactory)
        {
            _connectionFactory = connectionFactory;
        }
    
        public string Process(ConnectionType connectionType)
        {
            var connection = _connectionFactory(connectionType);
    
            return connection.Open().ToString();
        }
    }
    

    在您的注册中,使用 lambda 获取参数并返回正确的 IConnection 实例。

    builder.Register<IConnection>((c, p) =>
    {
        var type = p.TypedAs<ConnectionType>();
        switch (type)
        {
            case ConnectionType.Ssh:
                return new SshConnection();
            case ConnectionType.Telnet:
                return new TelnetConnection();
            default:
                throw new ArgumentException("Invalid connection type");
        }
    })
    .As<IConnection>();
    

    如果连接本身需要依赖项,您可以在 c 参数上调用 Resolve 以从当前调用上下文中解析它。例如,new SshConnection(c.Resolve&lt;IDependency&gt;())

    【讨论】:

    • 使用IIndex 的目的是避免手动编写这个大的switch case,这样这个逻辑就可以由Autofac 处理。从长远来看,如果这种类型有多个构造函数参数,那么整个代码很快就会变得混乱。
    • 如果 OP 想要保留 ConnectionFactory 委托而不是 Func&lt;ConnectionType, IConnection&gt;,那么以下注册应该有效:builder.Register&lt;IConnection&gt;((c, p) =&gt; { var type = p.Named&lt;ConnectionType&gt;("connectionType"); switch (type) { case ConnectionType.Ssh: .... } }) .As&lt;IConnection&gt;(); 重点是命名参数名称 "connectionType" 匹配委托声明中的参数。
    • 我当然不介意IIndex,但话说回来,我总是非常热衷于将 Autofac 作为我的容器! :D
    • 谢谢亚历克斯!您的解决方案肯定回答了我原来的问题。查看您的代码,我想知道是否可以将其用作创建某种对象构建器(或服务定位器)的基础-尽管听起来很糟糕,但我确实有一些其他要求要求我根据类型信息创建对象将从数据库中提供。评论有点太长了,所以也许我会问另一个问题,但我很想听听您对使用 Autofac 创建这样的东西的想法
    • 我不确定您的要求是什么,但如果您能避开服务地点,那绝对是一件好事。如果您在构建容器时知道类型是什么,则可以使用程序集扫描来注册类型。 docs.autofac.org/en/latest/register/scanning.html 这将有多大用处取决于何时需要创建对象以及负责创建它们的对象。我会留意另一个问题,我可以用一些更具体的反馈来回答。
    【解决方案2】:

    如果需要根据参数选择实现类型需要使用IIndex&lt;T,B&gt; implicit relation type:

    public class Engine : IEngine
    {
        private IIndex<ConnectionType, IConnection> _connectionFactory;
    
        public Engine(IIndex<ConnectionType, IConnection> connectionFactory)
        {
            _connectionFactory = connectionFactory;
        }
    
        public string Process(ConnectionType connectionType)
        {
            var connection = _connectionFactory[connectionType];
    
            return connection.Open().ToString();
        }
    }
    

    并使用枚举键注册您的 IConnection 实现:

    builder.RegisterType<Engine>()
    .           As<IEngine>()
        .InstancePerDependency();
    
    builder.RegisterType<SshConnection>()
        .Keyed<IConnection>(ConnectionType.Ssh);
    builder.RegisterType<TelnetConnection>()
        .Keyed<IConnection>(ConnectionType.Telnet);
    

    如果您想保留您的ConnectionFactory,您可以手动注册它以在内部使用IIndex&lt;T,B&gt;

    builder.Register<ConnectionFactory>(c =>
    {
        var context = c.Resolve<IComponentContext>();
        return t => context.Resolve<IIndex<ConnectionType, IConnection>>()[t];
    });
    

    在这种情况下,您仍然需要将 IConnection 类型注册为键控类型,但您的 Engine 实现可以保持不变。

    【讨论】:

    • 我尝试了第一个解决方案,效果很好。我的问题是,现在我的应用程序代码引用了 Autofac dll,以便识别 IIndex 接口。
    • 至于第二个选项,我还不确定如何使它起作用。 't' 参数无法识别,因此无法编译。我错过了什么吗?
    • 如果您不想在您的 dll 中注册 Autofac,您需要使用第二种解决方案,我不知道为什么它不为您编译.. 错误信息是什么?你也可以试试:builder.Register&lt;ConnectionFactory&gt;(c =&gt; { var context = c.Resolve&lt;IComponentContext&gt;(); ConnectionFactory result = (ConnectionType t) =&gt; context.Resolve&lt;IIndex&lt;ConnectionType, IConnection&gt;&gt;()[t]; return result; });
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    • 2018-01-11
    • 1970-01-01
    • 2016-12-12
    相关资源
    最近更新 更多