【问题标题】:Binding wrapper classes with ninject使用 ninject 绑定包装类
【发布时间】:2016-04-18 04:38:36
【问题描述】:

我有一个代表第 3 方 API 中的表的接口。每个实例都提供了使用只进游标搜索单个表的能力:

public interface ITable
{
    string TableName { get; }
    ICursor Search(string whereClause);
}

我编写了一个包装类来处理搜索 ITable 并返回一个可枚举(它比现实中的要复杂一些,但足以显示我的问题):

public interface ITableWrapper
{
    IEnumerable<object> Search(string whereClause);
}

public class TableWrapper : ITableWrapper
{
    private ITable _table;

    public TableWrapper(ITable table)
    {
        _table = table;
    }

    public IEnumerable<Row> Search(string whereClause)
    {
        var cursor = _table.Search(whereClause);
        while(cursor.Next())
        {
            yield return cursor.Row;
        }
    }
}

然后我有几个存储库类应该注入一个表包装器:

public class Table1Repository
{
    private ITableWrapper _table;

    public Table1Reposiroty(ITableWrapper table)
    {
        _table = table;
    }

    //repository methods to actually do things
}

由于每个表都有自己的包装器,并且存储库需要正确的表注入,我的想法是在表和包装器上使用命名绑定,以便 ninject 提供正确的实例。因此,上面的类会将 NamedAttribute 应用于构造函数参数,并且绑定如下:

public void NinjectConfig(IKernel kernel, ITableProvider provider)
{
    Bind<ITable>().ToMethod(ctx => provider.OpenTable("Table1")).Named("Table1").InSingletonScope();
    Bind<ITableWrapper>().ToMethod(ctx => new TableWrapper(ctx.ContextPreservingGet<ITable>("Table1"))).Named("Table1Wrapper").InSingletonScope();
}

我的问题是:

  1. 有没有更简洁的方式来表达这种绑定?我在想也许一种方法可以绑定 ITableWrapper 并为每个命名的 ITable 返回一个新实例,并使用存储库构造函数参数属性选择它想要 ITableWrapper 的命名 ITable。
  2. 如果 ITable 不应该被任何东西使用,并且所有东西都应该始终使用 ITableWrapper,那么是否可以(甚至推荐)只绑定 ITableWrapper 并结合 ToMethod 内容:

public void NinjectConfig(IKernel kernel, ITableProvider provider)
{
    Bind<ITableWrapper>().ToMethod(ctx => new TableWrapper(provider.OpenTable("Table1"))).Named("Table1Wrapper").InSingletonScope();
}

【问题讨论】:

    标签: c# ninject


    【解决方案1】:

    没有 Ninject 内置的方法可以通过属性向 Ninject 提供元数据。它唯一支持的是ConstraintAttribute(和NamedAttribute 作为子类)。这可用于选择特定的绑定,但不能用于为绑定提供参数。

    所以,如果您不想添加大量代码,最简单、最简洁的方法就是按照您自己的建议:

    public static BindTable(IKernel kernel, ITableProvider tableProvider, string tableName)
    {
        kernel.Bind<ITableWrapper>()
              .ToMethod(ctx => new tableWrapper(tableProvider.OpenTable(tableName))
              .Named(tableName);
    }
    

    (我在这里为表名和 ITableWrapper 名称使用了相同的字符串 ID - 这样您就不需要映射它们)。

    另外,如果您不打算使用它,我认为最好不要为ITable 创建绑定。

    注意:如果您要通过工厂创建ITableWrapper(而不是 ctor-injecting),您可以使用参数和从参数中读取 table-id 的绑定。这意味着单个绑定就足够了。

    通用解决方案

    现在,如果您可以添加一些自定义代码,您实际上可以实现通用解决方案。如何?您添加一个自定义属性来替换提供表名的NamedAttribute。此外,您还创建了一个绑定,该绑定从此自定义属性中读取表名。比方说:

    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
    public class TableIdAttribute : Attribute
    {
        public TableIdAttribute(string tableName)
        {
            TableName = tableName;
        }
    
        public string TableName { get; private set; }
    }
    

    让我们实现一个IProvider 来封装增加的绑定复杂性(它也适用于ToMethod 绑定):

    internal class TableWrapperProvider : Provider<ITableWrapper>
    {
        private readonly ITableProvider _tableProvider;
    
        public TableWrapperProvider(ITableProvider tableProvider)
        {
            _tableProvider = tableProvider;
        }
    
        protected override ITableWrapper CreateInstance(IContext context)
        {
            var parameterTarget = context.Request.Target as ParameterTarget;
            if (parameterTarget == null)
            {
                throw new ArgumentException(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "context.Request.Target {0} is not a {1}",
                        context.Request.Target.GetType().Name,
                        typeof(ParameterTarget).Name));
            }
    
            var tableIdAttribute = parameterTarget.Site.GetCustomAttribute<TableIdAttribute>();
            if (tableIdAttribute == null)
            {
                throw new InvalidOperationException(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "ParameterTarget {0}.{1} is missing [{2}]",
                        context.Request.Target,
                        context.Request.Target.Member,
                        typeof(TableIdAttribute).Name));
            }
    
            return new TableWrapper(_tableProvider.Open(tableIdAttribute.TableName));
        }
    }
    

    这是我们如何使用它(示例类​​):

    public class FooTableUser
    {
        public FooTableUser([TableId(Tables.FooTable)] ITableWrapper tableWrapper)
        {
            TableWrapper = tableWrapper;
        }
    
        public ITableWrapper TableWrapper { get; private set; }
    }
    
    public class BarTableUser
    {
        public BarTableUser([TableId(Tables.BarTable)] ITableWrapper tableWrapper)
        {
            TableWrapper = tableWrapper;
        }
    
        public ITableWrapper TableWrapper { get; private set; }
    }
    

    这是绑定和测试:

    var kernel = new StandardKernel();
    kernel.Bind<ITableProvider>().ToConstant(new TableProvider());
    kernel.Bind<ITableWrapper>().ToProvider<TableWrapperProvider>();
    
    kernel.Get<FooTableUser>().TableWrapper.Table.Name.Should().Be(Tables.FooTable);
    kernel.Get<BarTableUser>().TableWrapper.Table.Name.Should().Be(Tables.BarTable);
    

    【讨论】:

    • 您的通用方法正是我的想法,参数上的属性决定了包装的实例。绝对不介意提供者的一些额外代码。我不知道提供者有这么多的控​​制权,谢谢你的指导。
    猜你喜欢
    • 2018-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多