摘要

既然在插件模型里,每一个服务类型可以被映射到多个实现,绑定方法不用决定要返回哪个实现。因为kernel应该返回所有的实现。然而,上下文绑定是多个绑定场景,在这个场景里,kernel需要根据给定的条件,在多个提供的类型里选择一个实现。

附:代码下载

在下面的例子里,我们将要实现一个数据迁移的应用程序,可以将数据从SQL数据库迁移到XML数据文件。将有一个表现层,一个业务逻辑层和一个数据访问层。

按下面的步骤建立DataMigration基本程序结构。

1. 下载Northwind数据库备份,还原到SQL Server。

2. 创建解决方案DataMigration,并在解决方案下添加下面的工程。

3. 在DataMigration.Business工程下添加如下文件夹结构。

4. 在Model文件夹下添加Shipper.cs文件。

 namespace DataMigration.Business.Model
{
public class Shipper
{
public int ShipperID { get; set; } public string CompanyName { get; set; }
}
}

5. 在Interface文件夹内添加IShippersRepository.cs文件。

 using DataMigration.Business.Model;
using System.Collections.Generic; namespace DataMigration.Business.Interface
{
public interface IShippersRepository
{
IEnumerable<Shipper> GetShippers(); void AddShipper(Shipper shipper);
}
}

6. 在DataMigration.SqlDataAccess工程里创建ShippersSqlRepository类,使用EntityFramework读写数据库,实现IShippersRepository接口。

 using DataMigration.Business.Interface;
using System.Collections.Generic;
using DataMigration.Business.Model; namespace DataMigration.SqlDataAccess
{
public class ShippersSqlRepository : IShippersRepository
{
private readonly NorthwindContext _context; public ShippersSqlRepository(string connectionString)
{
_context = new NorthwindContext(connectionString);
} public void AddShipper(Shipper shipper)
{
if (shipper.ShipperID == )
{
_context.Shippers.Add(shipper);
}
else
{
var entity = _context.Shippers.Find(shipper.ShipperID);
if (entity != null)
{
entity.CompanyName = shipper.CompanyName;
}
}
_context.SaveChanges();
} public IEnumerable<Shipper> GetShippers()
{
return _context.Shippers;
}
}
}

NorthwindContext:

 using DataMigration.Business.Model;
using System.Data.Entity; namespace DataMigration.SqlDataAccess
{
public class NorthwindContext : DbContext
{
public NorthwindContext(string connectionString)
{
base.Database.Connection.ConnectionString = connectionString;
}
public DbSet<Shipper> Shippers { get; set; }
}
}

7. 在DataMigration.XMLDataAccess工程里添加ShippersXmlRepository类,使用System.Xml.Linq读写XML文件,实现IShippersRepository接口。

 using DataMigration.Business.Interface;
using DataMigration.Business.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq; namespace DataMigration.XMLDataAccess
{
public class ShippersXmlRepository : IShippersRepository
{
private readonly string documentPath; public ShippersXmlRepository(string xmlRepositoryPath)
{
this.documentPath = xmlRepositoryPath;
} public IEnumerable<Shipper> GetShippers()
{
var document = XDocument.Load(documentPath);
return from e in document.Elements("Shipper")
select new Shipper
{
ShipperID = Convert.ToInt32(e.Element("ShipperID").Value),
CompanyName = e.Element("CompanyName").Value
};
} public void AddShipper(Shipper shipper)
{
var document = XDocument.Load(documentPath);
document.Root.Add(new XElement("Shipper",
new XElement("ShipperID", shipper.ShipperID),
new XElement("CompanyName", shipper.CompanyName)));
document.Save(documentPath);
}
}
}

在DataMigration.XMLDataAccess工程里添加文件Northwind.xml,并设置文件属性Copy to Output Directory:

Northwind.xml文件内容:

<?xml version="1.0" encoding="utf-8" ?>
<Northwind> </Northwind>

8. 修改DataMigration.Console工程里的App.config文件。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
</configSections>
<connectionStrings>
<add name="connectionString" providerName="System.Data.SqlClient" connectionString="Data Source=localhost;Initial Catalog=NORTHWND;Integrated Security=True" />
</connectionStrings>
<appSettings>
<add key="xmlRepositoryPath" value="Northwind.xml"/>
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
connectionString:数据库连接字符串。
xmlRepositoryPath:Northwind.xml文件名

9. 添加ShippersService.cs文件。

 using DataMigration.Business.Interface;
using DataMigration.Business.Model; namespace DataMigration.Business
{
public class ShippersService
{
private readonly IShippersRepository sourceRepository;
private readonly IShippersRepository targetRepository; public void MigrateShippers()
{
foreach (Shipper shipper in sourceRepository.GetShippers())
{
targetRepository.AddShipper(shipper);
}
}
}
}

这些repositories类型应该是什么,以及他们应该怎样生成?这些问题的答案可以让这个应用程序变成松耦合易维护的程序,或者变成高耦合很难维护的代码。最容易的方法是创建一个XmlRepository对象和一个SQLRepository对象,在ShippersService类里像下面这样实例化他们:

 // The following code leads to a tightly coupled code
var sourceRepository = new ShippersSqlRepository();
var targetRepository = new ShippersXmlRepository();

用这种方法,我们将使得我们的服务依赖于这些具体的repository,将我们的业务逻辑层绑定到数据访问层。可能会修改或者替换数据访问层,而不修改业务逻辑层,然后重新编译。尽管我们的应用层看起来是分离的,他们实际上是紧密的耦合的,代码很难维护。

也可以像下面使用构造函数生成repository。

 public ShippersService(IShippersRepository sourceRepository,
              IShippersRepository targetRepository)
{
this.sourceRepository = sourceRepository;
this.targetRepository = targetRepository;
}

ShippersService类现在变成好的可重用性了。可以将Shipper实例不但从SQL迁移到XML,也可以在任何数据源中间迁移数据,只要他们实现了IShippersRepository接口。有趣的是我们可以很容易地向相反方向迁移数据而不用修改我们的ShippersService类或者数据访问层。

我们知道Ninject将注入具体的repository到ShipperService类的构造函数中。但是请等一下,构造函数的两个参数的类型都是IShippersRepository接口。Ninject怎么知道哪一个具体类型注册到哪个参数?上下文绑定是这个问题的答案。让我们一个一个地看这些不同的解决方案。

准备工作:

1. 使用NutGet向工程DataMigration.Console添加Ninject Package和Entity Framework Package。

2. 在工程DataMigration.Console里添加CompositionModule类。

 using Ninject.Modules;

 namespace DataMigration
{
public class CompositionModule : NinjectModule
{
public override void Load()
{ }
}
}

CompositionModule类继承NinjectModule接口,在Load方法里调用一系列Bind方法,管理绑定类型。

名称绑定

名称绑定是最简单的方法,在这个方法里我们可以向我们的绑定和我们的目标参数指派名称,Ninject就可以决定在哪个目标参数上使用哪个绑定。我们需要向目标以及他们对应的绑定插入名称:

         public ShippersService(
[Named("Source")]IShippersRepository sourceRepository,
[Named("Target")]IShippersRepository targetRepository)
{
this.sourceRepository = sourceRepository;
this.targetRepository = targetRepository;
}

DataMigration.Console工程添加System.Configuration引用。在CompositionModule类添加using System.Configuration语句。添加下面的代码到CompositionModule类的Load方法:

             Bind<IShippersRepository>().To<ShippersSqlRepository>().Named("Source")
.WithConstructorArgument("connectionString", ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString);
Bind<IShippersRepository>().To<ShippersXmlRepository>().Named("Target")
.WithConstructorArgument("xmlRepositoryPath", ConfigurationManager.AppSettings["xmlRepositoryPath"]);

修改DataMigration.Console工程里的Main函数:

 using DataMigration.Business;
using Ninject;
using System; namespace DataMigration
{
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel(new CompositionModule());
var shippersService = kernel.Get<ShippersService>();
shippersService.MigrateShippers(); Console.ReadLine();
}
}
}

运行程序后,到$\DataMigration.Console\bin\Debug文件夹下,找到Northwind.xml文件。打开该文件,得到从SQL Server迁移到XML文件的结果:

<?xml version="1.0" encoding="utf-8"?>
<Northwind>
<Shipper>
<ShipperID>1</ShipperID>
<CompanyName>Speedy Express</CompanyName>
</Shipper>
<Shipper>
<ShipperID>2</ShipperID>
<CompanyName>United Package</CompanyName>
</Shipper>
<Shipper>
<ShipperID>3</ShipperID>
<CompanyName>Federal Shipping</CompanyName>
</Shipper>
</Northwind>

既然我们已经用名称区分了IShipperRepository不同的实现,也可以用下面的语法从kernel对象获得他们:

kernel.Get<IShippersRepository>("Source");

然而,用这种方法解析实例是不推荐的,因为使用这种方法,Ninject将被误用,变成实现服务定位器的反模式。

解析元数据

1. 使用NuGet向DataMigration.Business工程添加Ninject引用。

2. 为每一个绑定提供一些元数据,在类型解析的时候这些元数据将被鉴定(判断)。下面演示如何在Bind方法中设置元数据。

在CompositionModule类的Load方法中,在Bind方法调用后,调用WithMetadata方法,设置(提供或注入)元数据:

1             Bind<IShippersRepository>().To<ShippersSqlRepository>().WithMetadata("IsSource", true)
2 .WithConstructorArgument("connectionString", ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString);
3 Bind<IShippersRepository>().To<ShippersXmlRepository>().WithMetadata("IsSource", false)
4 .WithConstructorArgument("xmlRepositoryPath", ConfigurationManager.AppSettings["xmlRepositoryPath"]);

3. 联系目标和它们对应的绑定。定义一个自定义的ConstraintAttribute类,这是一个抽象类,提供了一个方法匹配特性目标和它所需要的绑定。下面添加这么一个特性类。

在DataMigration.Business工程的Attributes文件夹下,添加类IsSourceAttribute。

 using Ninject;

 namespace DataMigration.Business.Attributes
{
public class IsSourceAttribute : ConstraintAttribute
{
private readonly bool isSource; public IsSourceAttribute(bool isSource)
{
this.isSource = isSource;
} public override bool Matches(Ninject.Planning.Bindings.IBindingMetadata metadata)
{
return metadata.Has("IsSource") && metadata.Get<bool>("IsSource") == isSource;
}
}
}

IsSourceAttribute类继承ConstraintAttribute抽象类,重载了抽象方法Matches。

public abstract bool Matches(IBindingMetadata metadata);

参数metadata包含了Bind注入的元数据信息。使用Has方法和Get方法获得这些信息。

3. 应用这个特性到目标上面,将他们和他们对应的绑定关联起来。

在ShippersService构造函数中插入IsSource特性。

         public ShippersService(
[IsSource(true)]IShippersRepository sourceRepository,
[IsSource(false)]IShippersRepository targetRepository)
{
this.sourceRepository = sourceRepository;
this.targetRepository = targetRepository;
}

注意:

  • 我们可以在解析关联服务的时候,提供尽量多的正在使用的,需要的元数据到我们的绑定。
Bind<IService>().To<Component>()
.WithMetadata("Key1", value1)
.WithMetadata("Key2", value2)
.WithMetadata("Key3", value3);
  • 我们也可以提供尽量多的需要的约束特性到绑定目标上。像下面的代码:
public Consumer([Constraint1(value1, value2), Constraint2(value), Constraint3]IService dependency)
{ }

请记住名称绑定场景也是使用元数据实现的。下面的代码演示了怎样实现一个基于正则匹配而不是基于相同名称的,自定义的约束特性,来解析名称绑定。

     public class NamedLikeAttribute : ConstraintAttribute
{
private readonly string pattern; public NamedLikeAttribute(string namePattern)
{
this.pattern = namePattern;
} public override bool Matches(IBindingMetadata metadata)
{
return metadata.Has("Named") && System.Text.RegularExpressions.Regex.IsMatch(metadata.Get<string>("Named"), pattern); }
}

给定一个正则表达式,上面的特性可以运用到目标上。绑定的名称将被正则表达式判断,名称是否匹配。

为绑定提供元数据:

     Bind<IShippersRepository>().To<ShippersSqlRepository>().WithMetadata("Named", "SourceRepository");
Bind<IShippersRepository>().To<ShippersXmlRepository>().WithMetadata("Named", "TargetRepository");

将特性插入到目标参数,关联对应的绑定:

 public Consumer([NamedLike(@"source\w+") dependency)
{
...
}

基于特性的绑定

尽管名称绑定用起来很简单,元数据很灵活很强大,但是这两个方法都需要依赖的类的类库引用Ninject类库,这样更容易出错。打错了名字或者元数据的键,编译器不会发出警告。

下面的代码演示怎样使用这个基于特性的绑定技术而不引用Ninject类库。

先定义一些自定义特性:

 using System;

 namespace DataMigration.Business.Attributes
{
public class SourceAttribute : Attribute { }
public class TargetAttribute : Attribute { }
}

然后这些特性可以像下面运用在目标参数上:

         public ShippersService(
[Source]IShippersRepository sourceRepository,
[Target]IShippersRepository targetRepository)
{
this.sourceRepository = sourceRepository;
this.targetRepository = targetRepository;
}

现在,我们需要用下面的代码注册我们的绑定:

             Bind<IShippersRepository>().To<ShippersSqlRepository>().WhenTargetHas<SourceAttribute>()
.WithConstructorArgument("connectionString", ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString);
Bind<IShippersRepository>().To<ShippersXmlRepository>().WhenTargetHas<TargetAttribute>()
.WithConstructorArgument("xmlRepositoryPath", ConfigurationManager.AppSettings["xmlRepositoryPath"]);

我们不但可以在参数上运用这些特性,我们也可以在类上面或者在类的其他注册成员上运用这些特性,例如,自身的构造函数。

下面的绑定演示如何基于一个特性,在一个消费者类上做条件绑定:

Bind<IService>().To<MyService>().WhenClassHas<MyAttribute>();

下面是一个关联MyAttribute的消费者类:

 [MyAttribute]
Public class Consumer {...}

这是我们怎样在构造函数中运用这样一个特性:

 [MyAttribute]
public Consumer(IServive service) { ... }

类成员可以是构造函数本身,或者甚至是另一个方法,或者一个注入的属性。

基于目标条件

另一种决定使用哪个绑定的方法是基于目标条件。Ninject提供了几个帮助方法,可以限制匹配的绑定的数量。下面演示一个例子。

在这个例子中,我们有两个服务类名称是SourceShipperService和TargetShipperService,两个都依赖于IShippersRepository接口。

下面是服务类的结构:

 public class SourceShipperService
{
  public SourceShipperService(IShippersRepository repository)
  { ... }
}
public class TargetShipperService
{
  public TargetShipperService(IShippersRepository repository)
  { ... }
}

为了告诉Ninject哪个具体repository应该被注入到哪个服务,我们可以基于服务类型本身为条件,而不是任何的特性或者元数据。

下面的代码演示如何用这种方式注册我们的类型,将ShippersXmlRepository和ShippersSqlRepository实例分别注入到SourceShipperService和TargetShipperService类:

 Bind<IShippersRepository>().To<ShippersXmlRepository>()
.WhenInjectedInto<SourceShipperService>();
Bind<IShippersRepository>().To<ShippersSqlRepository>()
.WhenInjectedInto<TargetShipperService>();

注意,即使目标类是T类型的子类,WhenInjectedInto<T>方法也将被匹配。如果我们确切想要指定的类型,我们应该使用下面替代的方法:

 Bind<IShippersRepository>().To<ShippersSqlRepository>()
.WhenInjectedExactlyInto<TargetShipperService>();

一般帮助方法

正如我们已经看到的,前面所有的方式都是利用了名字以WhenXXX结尾的帮助方法。所有的这些方法是一个更一般化的When方法的具体版本。这个功能强大的帮助方法提供一个回馈机制的参数,参数包含当前绑定请求的所有信息,这些信息就包含了目标信息。下面演示如何为数据迁移程序使用帮助方法注册类型:

             Bind<IShippersRepository>().To<ShippersSqlRepository>().When(r => r.Target.Name.StartsWith("source"))
.WithConstructorArgument("connectionString", ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString);
Bind<IShippersRepository>().To<ShippersXmlRepository>().When(r => r.Target.Name.StartsWith("target"))
.WithConstructorArgument("xmlRepositoryPath", ConfigurationManager.AppSettings["xmlRepositoryPath"]);

前面的代码,只要目标IShippersRepository参数的名称以source开头,就绑定到ShippersSqlRepository。第二个绑定也用了类似的规则。

Ninject之旅之九:Ninject上下文绑定(附程序下载)的更多相关文章

  1. Ninject之旅之八:Ninject插件模型(附程序下载)

    摘要 在前面的章节中,我们看了在单一的绑定条件下Ninject能够处理依赖类型,就是说,每个服务类型只绑定到单一的实现类型.然而,有些情况下我们需要绑定一个抽象服务类型到多个实现,这叫多个绑定.多个绑 ...

  2. Ninject之旅之十三:Ninject在ASP.NET MVC程序上的应用(附程序下载)

    摘要: 在Windows客户端程序(WPF和Windows Forms)中使用Ninject和在控制台应用程序中使用Ninject没什么不同.在这些应用程序里我们不需要某些配置用来安装Ninject, ...

  3. Ninject之旅之十一:Ninject动态工厂(附程序下载)

    摘要 如果我们已经知道了一个类所有的依赖项,在我们只需要依赖项的一个实例的场景中,在类的构造函数中引入一系列的依赖项是容易的.但是有些情况,我们需要在一个类里创建依赖项的多个实例,这时候Ninject ...

  4. Ninject之旅之五:Ninject XML配置

    摘要 使用XML配置,需要添加Ninject XML扩展的引用.下一步是添加一个或多个包含类型注册的XML文件.记得这些文件应该跟应用程序一起发布.因此不要忘记将XML文件的属性设置成“Copy if ...

  5. Ninject之旅之二:开始使用Ninject(附程序下载)

    摘要 这篇文章介绍怎样将Ninject添加到实际的项目中,使用Ninject框架最基本的功能.首先用一个Hello World例子介绍怎么添加和使用Ninject.然后用一个更复杂的例子,介绍Ninj ...

  6. Ninject之旅之十二:Ninject在Windows Form程序上的应用(附程序下载)

    摘要: 下面的几篇文章介绍如何使用Ninject创建不同类型的应用系统.包括: Windows Form应用系统 ASP.NET MVC应用系统 ASP.NET Web Form应用系统 尽管对于不同 ...

  7. Ninject之旅之十四:Ninject在ASP.NET Web Form程序上的应用(附程序下载)

    摘要 ASP.NET Web Forms没有像MVC那样的可扩展性,也不可能使它创建UI页面支持没有构造函数的的激活方式.这个Web Forms应用程序的的局限性阻止了它使用构造函数注入模式,但是仍能 ...

  8. Ninject之旅之三:Ninject对象生命周期

    摘要 DI容器的一个责任是管理他创建的对象的生命周期.他应该决定什么时候创建一个给定类型的对象,什么时候使用已经存在的对象.他还需要在对象不需要的时候处理对象.Ninject在不同的情况下管理对象的生 ...

  9. Ninject之旅之七:Ninject依赖注入

    摘要 可以使用不同的模式向消费者类注入依赖项,向构造器里注入依赖项是其中一种.有一些遵循的模式用来注册依赖项,同时有一些需要避免的模式,因为他们经常导致不合乎需要的结果.这篇文章讲述那些跟Ninjec ...

随机推荐

  1. Pyhton的发展历程

    Python的由来 在1989年12月时,吉多·范罗苏姆——龟叔,想寻找一门“课余”编程项目来打发圣诞节前后的时间.Guido决定为当时正构思的一个新的脚本语言写一个解释器,它是ABC语言(教学语言. ...

  2. unity, 只发射一个粒子的粒子系统

  3. tar 命令详解

    tar命令[root@Linux ~]# tar [-cxtzjvfpPN] 文件与目录 -C 目标目录(注:解压时)参数:-c :建立一个压缩文件的参数指令(create 的意思):-x :解开一个 ...

  4. B 最熟悉的陌生人 (纪念当年就读的梅州市江南高级中学)

    最熟悉的陌生人 作者:张慧桥 枪与玫瑰 我看了一下聊天室的名单,哈哈哈,我不禁喜出望外:蝶恋花那丫头片子挂在线上呢,真是天助我也.初时的担心一扫而光,我精神抖擞地喝下一大口咖啡,猛抽了三口烟,现在的我 ...

  5. 大数据量下,分页的解决办法,bubuko.com分享,快乐人生

    大数据量,比如10万以上的数据,数据库在5G以上,单表5G以上等.大数据分页时需要考虑的问题更多. 比如信息表,单表数据100W以上. 分页如果在1秒以上,在页面上的体验将是很糟糕的. 优化思路: 1 ...

  6. 服务 在初始化安装时发生异常:System.IO.FileNotFoundException: "file:///D:\testService"未能加载文件或程序集。系统找不到指定文件。

    @echo.@if exist "%windir%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe" goto INSTALL ...

  7. ruby : nil?, empty? and blank?的选择

    article = nil article.nil? # => true empty? checks if an element - like a string or an array f.e. ...

  8. ubuntu中常用软件的安装

    1.有道词典 1.百度有道词典,进入有道首页,点"下载词典客户端",下载对应版本. 2.打开终端,进入下载目录,输入sudo dpkg -i youdao-dict_1.0.2~u ...

  9. MSSQL MERGE语法

    Merge的用法 Merge可以完成以下功能: 1.    两个表之间数据的更新 2.    进行进销存更新库存 3.    进行表之间数据的复制 语法说明: 1.    在语句结束后一定要用分号,否 ...

  10. WCF服务客户端首页调用慢的问题处理

    场景: WCF服务架设于IIS服务中,走TCP协议.客户端首次调用特别慢,第一次加载完后,都正常. 解决: 把服务中需要序列化的模型所在的工程 > 属性 > 生成 > 生成序列化程序 ...