将包装器 类 与 ninject 绑定
Binding wrapper classes with ninject
我有一个接口代表第 3 方 API 中的 table。每个实例都提供了使用只进游标搜索单个 table 的能力:
public interface ITable
{
string TableName { get; }
ICursor Search(string whereClause);
}
我写了一个包装器 class 来处理搜索 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;
}
}
}
然后我有几个存储库 classes 应该有一个 table 包装器注入:
public class Table1Repository
{
private ITableWrapper _table;
public Table1Reposiroty(ITableWrapper table)
{
_table = table;
}
//repository methods to actually do things
}
由于每个 table 都有自己的包装器,并且存储库需要正确的 table 注入,我的想法是在 table 和包装器上使用命名绑定,以便 ninject 提供了正确的实例。因此,上面的 class 会将 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();
}
我的问题是:
- 是否有更简洁的方式来表达这种绑定?我在想也许有一种方法可以绑定 ITableWrapper 一次并为每个命名的 ITable 返回一个新实例,存储库构造函数参数属性选择它想要 ITableWrapper 的命名 ITable。
- 如果 ITable 不应该被任何东西使用,并且所有东西都应该始终使用 ITableWrapper,是否可以(甚至推荐)仅绑定 ITableWrapper 并结合 ToMethod 内容:
public void NinjectConfig(IKernel kernel, ITableProvider provider)
{
Bind<ITableWrapper>().ToMethod(ctx => new TableWrapper(provider.OpenTable("Table1"))).Named("Table1Wrapper").InSingletonScope();
}
没有 Ninject-built-in 方法可以通过属性向 Ninject 提供元数据。它唯一支持的是 ConstraintAttribute
(以及作为子类的 NamedAttribute
)。这可用于 select 特定绑定,但不能用于为绑定提供参数。
因此,如果您不想添加大量代码,最简单、最简洁的方法就是您自己建议的方法:
public static BindTable(IKernel kernel, ITableProvider tableProvider, string tableName)
{
kernel.Bind<ITableWrapper>()
.ToMethod(ctx => new tableWrapper(tableProvider.OpenTable(tableName))
.Named(tableName);
}
(我在这里对 table 名称和 ITableWrapper 名称使用了相同的 string-id - 这样您就不需要映射它们了)。
此外,我认为最好不要为 ITable
创建绑定,如果您不打算使用它的话。
注意:如果您要通过工厂创建 ITableWrapper
(而不是 ctor-injecting),您可以使用参数和从参数中读取 table-id 的绑定.这意味着单个绑定就足够了。
通用解决方案
现在,如果您可以添加一些自定义代码,那么您实际上可以实现通用解决方案。如何?您添加一个自定义属性来替换提供 table 名称的 NamedAttribute
。此外,您还创建了一个绑定,它从该自定义属性中读取 table 名称。比方说:
[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);
我有一个接口代表第 3 方 API 中的 table。每个实例都提供了使用只进游标搜索单个 table 的能力:
public interface ITable
{
string TableName { get; }
ICursor Search(string whereClause);
}
我写了一个包装器 class 来处理搜索 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;
}
}
}
然后我有几个存储库 classes 应该有一个 table 包装器注入:
public class Table1Repository
{
private ITableWrapper _table;
public Table1Reposiroty(ITableWrapper table)
{
_table = table;
}
//repository methods to actually do things
}
由于每个 table 都有自己的包装器,并且存储库需要正确的 table 注入,我的想法是在 table 和包装器上使用命名绑定,以便 ninject 提供了正确的实例。因此,上面的 class 会将 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();
}
我的问题是:
- 是否有更简洁的方式来表达这种绑定?我在想也许有一种方法可以绑定 ITableWrapper 一次并为每个命名的 ITable 返回一个新实例,存储库构造函数参数属性选择它想要 ITableWrapper 的命名 ITable。
- 如果 ITable 不应该被任何东西使用,并且所有东西都应该始终使用 ITableWrapper,是否可以(甚至推荐)仅绑定 ITableWrapper 并结合 ToMethod 内容:
public void NinjectConfig(IKernel kernel, ITableProvider provider)
{
Bind<ITableWrapper>().ToMethod(ctx => new TableWrapper(provider.OpenTable("Table1"))).Named("Table1Wrapper").InSingletonScope();
}
没有 Ninject-built-in 方法可以通过属性向 Ninject 提供元数据。它唯一支持的是 ConstraintAttribute
(以及作为子类的 NamedAttribute
)。这可用于 select 特定绑定,但不能用于为绑定提供参数。
因此,如果您不想添加大量代码,最简单、最简洁的方法就是您自己建议的方法:
public static BindTable(IKernel kernel, ITableProvider tableProvider, string tableName)
{
kernel.Bind<ITableWrapper>()
.ToMethod(ctx => new tableWrapper(tableProvider.OpenTable(tableName))
.Named(tableName);
}
(我在这里对 table 名称和 ITableWrapper 名称使用了相同的 string-id - 这样您就不需要映射它们了)。
此外,我认为最好不要为 ITable
创建绑定,如果您不打算使用它的话。
注意:如果您要通过工厂创建 ITableWrapper
(而不是 ctor-injecting),您可以使用参数和从参数中读取 table-id 的绑定.这意味着单个绑定就足够了。
通用解决方案
现在,如果您可以添加一些自定义代码,那么您实际上可以实现通用解决方案。如何?您添加一个自定义属性来替换提供 table 名称的 NamedAttribute
。此外,您还创建了一个绑定,它从该自定义属性中读取 table 名称。比方说:
[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);