使用ninject绑定包装类

时间:2016-01-13 09:23:14

标签: c# ninject

我有一个代表第三方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内容结合起来是否可行(甚至建议):
  3. public void NinjectConfig(IKernel kernel, ITableProvider provider)
    {
        Bind<ITableWrapper>().ToMethod(ctx => new TableWrapper(provider.OpenTable("Table1"))).Named("Table1Wrapper").InSingletonScope();
    }
    

1 个答案:

答案 0 :(得分: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);