Ninject之旅之九:Ninject上下文绑定(附程序下载)

摘要

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

附:代码下载

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

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

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

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

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

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

1 namespace DataMigration.Business.Model
2 {
3     public class Shipper
4     {
5         public int ShipperID { get; set; }
6
7         public string CompanyName { get; set; }
8     }
9 }

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

 1 using DataMigration.Business.Model;
 2 using System.Collections.Generic;
 3
 4 namespace DataMigration.Business.Interface
 5 {
 6     public interface IShippersRepository
 7     {
 8         IEnumerable<Shipper> GetShippers();
 9
10         void AddShipper(Shipper shipper);
11     }
12 }

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

 1 using DataMigration.Business.Interface;
 2 using System.Collections.Generic;
 3 using DataMigration.Business.Model;
 4
 5 namespace DataMigration.SqlDataAccess
 6 {
 7     public class ShippersSqlRepository : IShippersRepository
 8     {
 9         private readonly NorthwindContext _context;
10
11         public ShippersSqlRepository(string connectionString)
12         {
13             _context = new NorthwindContext(connectionString);
14         }
15
16         public void AddShipper(Shipper shipper)
17         {
18             if (shipper.ShipperID == 0)
19             {
20                 _context.Shippers.Add(shipper);
21             }
22             else
23             {
24                 var entity = _context.Shippers.Find(shipper.ShipperID);
25                 if (entity != null)
26                 {
27                     entity.CompanyName = shipper.CompanyName;
28                 }
29             }
30             _context.SaveChanges();
31         }
32
33         public IEnumerable<Shipper> GetShippers()
34         {
35             return _context.Shippers;
36         }
37     }
38 }

NorthwindContext:

 1 using DataMigration.Business.Model;
 2 using System.Data.Entity;
 3
 4 namespace DataMigration.SqlDataAccess
 5 {
 6     public class NorthwindContext : DbContext
 7     {
 8         public NorthwindContext(string connectionString)
 9         {
10             base.Database.Connection.ConnectionString = connectionString;
11         }
12         public DbSet<Shipper> Shippers { get; set; }
13     }
14 }

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

 1 using DataMigration.Business.Interface;
 2 using DataMigration.Business.Model;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Xml.Linq;
 7
 8 namespace DataMigration.XMLDataAccess
 9 {
10     public class ShippersXmlRepository : IShippersRepository
11     {
12         private readonly string documentPath;
13
14         public ShippersXmlRepository(string xmlRepositoryPath)
15         {
16             this.documentPath = xmlRepositoryPath;
17         }
18
19         public IEnumerable<Shipper> GetShippers()
20         {
21             var document = XDocument.Load(documentPath);
22             return from e in document.Elements("Shipper")
23                    select new Shipper
24                    {
25                        ShipperID = Convert.ToInt32(e.Element("ShipperID").Value),
26                        CompanyName = e.Element("CompanyName").Value
27                    };
28         }
29
30         public void AddShipper(Shipper shipper)
31         {
32             var document = XDocument.Load(documentPath);
33             document.Root.Add(new XElement("Shipper",
34                 new XElement("ShipperID", shipper.ShipperID),
35                 new XElement("CompanyName", shipper.CompanyName)));
36             document.Save(documentPath);
37         }
38     }
39 }

在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文件。

 1 using DataMigration.Business.Interface;
 2 using DataMigration.Business.Model;
 3
 4 namespace DataMigration.Business
 5 {
 6     public class ShippersService
 7     {
 8         private readonly IShippersRepository sourceRepository;
 9         private readonly IShippersRepository targetRepository;
10
11         public void MigrateShippers()
12         {
13             foreach (Shipper shipper in sourceRepository.GetShippers())
14             {
15                 targetRepository.AddShipper(shipper);
16             }
17         }
18     }
19 }

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

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

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

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

1 public ShippersService(IShippersRepository sourceRepository,
2               IShippersRepository targetRepository)
3 {
4     this.sourceRepository = sourceRepository;
5     this.targetRepository = targetRepository;
6 }

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

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

准备工作:

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

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

 1 using Ninject.Modules;
 2
 3 namespace DataMigration
 4 {
 5     public class CompositionModule : NinjectModule
 6     {
 7         public override void Load()
 8         {
 9
10         }
11     }
12 }

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

名称绑定

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

1         public ShippersService(
2             [Named("Source")]IShippersRepository sourceRepository,
3             [Named("Target")]IShippersRepository targetRepository)
4         {
5             this.sourceRepository = sourceRepository;
6             this.targetRepository = targetRepository;
7         }

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

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

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

 1 using DataMigration.Business;
 2 using Ninject;
 3 using System;
 4
 5 namespace DataMigration
 6 {
 7     class Program
 8     {
 9         static void Main(string[] args)
10         {
11             var kernel = new StandardKernel(new CompositionModule());
12             var shippersService = kernel.Get<ShippersService>();
13             shippersService.MigrateShippers();
14
15             Console.ReadLine();
16         }
17     }
18 }

运行程序后,到$\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。

 1 using Ninject;
 2
 3 namespace DataMigration.Business.Attributes
 4 {
 5     public class IsSourceAttribute : ConstraintAttribute
 6     {
 7         private readonly bool isSource;
 8
 9         public IsSourceAttribute(bool isSource)
10         {
11             this.isSource = isSource;
12         }
13
14         public override bool Matches(Ninject.Planning.Bindings.IBindingMetadata metadata)
15         {
16             return metadata.Has("IsSource") && metadata.Get<bool>("IsSource") == isSource;
17         }
18     }
19 }

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

public abstract bool Matches(IBindingMetadata metadata);

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

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

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

1         public ShippersService(
2             [IsSource(true)]IShippersRepository sourceRepository,
3             [IsSource(false)]IShippersRepository targetRepository)
4         {
5             this.sourceRepository = sourceRepository;
6             this.targetRepository = targetRepository;
7         }

注意:

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

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

 1     public class NamedLikeAttribute : ConstraintAttribute
 2     {
 3         private readonly string pattern;
 4
 5         public NamedLikeAttribute(string namePattern)
 6         {
 7             this.pattern = namePattern;
 8         }
 9
10         public override bool Matches(IBindingMetadata metadata)
11         {
12             return metadata.Has("Named") && System.Text.RegularExpressions.Regex.IsMatch(metadata.Get<string>("Named"), pattern);13         }
14     }

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

为绑定提供元数据:

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

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

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

基于特性的绑定

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

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

先定义一些自定义特性:

1 using System;
2
3 namespace DataMigration.Business.Attributes
4 {
5     public class SourceAttribute : Attribute { }
6     public class TargetAttribute : Attribute { }
7 }

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

1         public ShippersService(
2             [Source]IShippersRepository sourceRepository,
3             [Target]IShippersRepository targetRepository)
4         {
5             this.sourceRepository = sourceRepository;
6             this.targetRepository = targetRepository;
7         }

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

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

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

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

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

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

1 [MyAttribute]
2 Public class Consumer {...}

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

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

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

基于目标条件

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

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

下面是服务类的结构:

 1 public class SourceShipperService
 2 {
 3   public SourceShipperService(IShippersRepository repository)
 4   { ... }
 5 }
 6 public class TargetShipperService
 7 {
 8   public TargetShipperService(IShippersRepository repository)
 9   { ... }
10 }

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

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

1 Bind<IShippersRepository>().To<ShippersXmlRepository>()
2 .WhenInjectedInto<SourceShipperService>();
3 Bind<IShippersRepository>().To<ShippersSqlRepository>()
4 .WhenInjectedInto<TargetShipperService>();

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

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

一般帮助方法

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

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

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

时间: 2024-10-13 17:52:56

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

Ninject之旅之八:Ninject插件模型

摘要 在前面的章节中,我们看了在单一的绑定条件下Ninject能够处理依赖类型,就是说,每个服务类型只绑定到单一的实现类型.然而,有些情况下我们需要绑定一个抽象服务类型到多个实现,这叫多个绑定.多个绑定有两种情况.第一个是插件模型实现,另一个是上下文绑定.这篇文章介绍插件模型实现,下一篇文章介绍上下文绑定. 插件模型让一个应用程序获得很强的可扩展性而不用修改源代码.下面的例子,我们将实现一个音乐播放器应用程序,使用解码插件来支持不同的音乐格式.这个应用程序使用两个内置的解码器,也可以添加更多的解

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

摘要 如果我们已经知道了一个类所有的依赖项,在我们只需要依赖项的一个实例的场景中,在类的构造函数中引入一系列的依赖项是容易的.但是有些情况,我们需要在一个类里创建依赖项的多个实例,这时候Ninject注入就不够用了.也有些情况,我们不知道一个消费者可能需要哪个服务,因为他可能在不同的场合下需要不同的服务,而且在创建类的时候实例化所有依赖项也不合理.这样的情况,动态工厂可以帮忙.我们可以设计我们的类让他依赖一个工厂,而不是依赖这个工厂能够创建的对象.然后,我们能够命令工厂去通过命令创建需要的类型和

Ninject之旅之五:Ninject XML配置

摘要 使用XML配置,需要添加Ninject XML扩展的引用.下一步是添加一个或多个包含类型注册的XML文件.记得这些文件应该跟应用程序一起发布.因此不要忘记将XML文件的属性设置成“Copy if newer”. XML配置文件像下面的配置一样: <module name="moduleName"> <bind service="Namespace.IService1, AssemblyName" to="Namespace.Conc

Ninject之旅之七:Ninject依赖注入

摘要 可以使用不同的模式向消费者类注入依赖项,向构造器里注入依赖项是期中一种.有一些遵循的模式用来注册依赖项,同时有一些需要避免的模式,因为他们经常导致不合乎需要的结果.这篇文章讲述那些跟Ninject功能相关的模式和反模式.然而,全面的介绍可以在Mark Seemann的书<Dependency Injection in .NET>中找到. 1.构造函数注入 构造函数时推荐的最常用的向一个类注册依赖项的模式.一般来说,这种模式应该经常被用作主要的注册模式,除非我们不得不使用其他的模式.在这个

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

摘要 DI容器的一个责任是管理他创建的对象的生命周期.他应该决定什么时候创建一个给定类型的对象,什么时候使用已经存在的对象.他还需要在对象不需要的时候处理对象.Ninject在不同的情况下管理对象的生命周期提供了强大的支持.在我们定义一个绑定的时候,定义创建对象的范围.在那个范围内,对象将被重用,每次绑定只存在一次.注意,对象不允许依赖于生命周期比自己小的对象. 1.暂时范围 在暂时态范围内,对象生命周期不被Ninject进行管理.任何时候请求一个类型的对象,都将创建一新对象.Ninject不管

JAVA之旅(九)——Object类,equals,toString,getClass,内部类访问规则,静态内部类,内部类原则,匿名内部类

JAVA之旅(九)--Object类,equals,toString,getClass,内部类访问规则,静态内部类,内部类原则,匿名内部类 天天被一些琐事骚扰,学习还得继续 一.Object类 Object是什么呢?我们可以翻阅JAVA API文档看他的介绍 上面介绍说,object是类层次结构的根类,也就是超类 Object:是所有对象的直接后者间继承关系,传说中的老祖宗,你父亲还有父亲,你父亲的父亲还有父亲是爷爷,这是继承关系,但是你的祖宗却只有一位,该类中定义的肯定是所有对象都具备的功能

【WPF学习】第二十九章 元素绑定——将元素绑定到一起

原文:[WPF学习]第二十九章 元素绑定--将元素绑定到一起 数据banding的最简单情形是,源对象时WPF元素而且源属性是依赖性属性.前面章节解释过,依赖项属性具有内置的更改通知支持.因此,当在源对象中改变依赖项属性的值时,会立即更新目标对象中的绑定属性.这正是我们所需要的行为--而且不必为此构建任何额外的基础结构. 为理解如何将一个元素绑定到另一个元素,下面创建一个简单的示例.该示例窗口包含了两个控件:一个Slider控件和一个具有单行文本的TextBlock控件.如果向右拖动滑动条上的滑

进程上下文、中断上下文以及中断程序的特点

进程上下文VS中断上下文 内核空间和用户空间是现代操作系统的两种工作模式,内核模块运行在内核空间,而用户态应用程序运行在用户空间.它们代表不同的级别,而对系统资源具有不同的访问权限.内核模块运行在最高级别(内核态),这个级下所有的操作都受系统信任,而应用程序运行在较低级别(用户态).在这个级别,处理器控制着对硬件的直接访问以及对内存的非授权访问.内核态和用户态有自己的内存映射,即自己的地址空间. www.2cto.com 处理器总处于以下状态中的一种: 1.内核态,运行于进程上下文,内核代表进程

Ninject之旅之十三:Ninject在ASP.NET MVC程序上的应用

摘要: 在Windows客户端程序(WPF和Windows Forms)中使用Ninject和在控制台应用程序中使用Ninject没什么不同.在这些应用程序里我们不需要某些配置用来安装Ninject,因为在Windows客户端应用程序里,开发者可以控制UI组件的实例化(Forms或Windows),可以很容易地委托这种控制到Ninject.然而在Web应用程序里,就不同了,因为框架负责了实例化UI元素.因此,我们需要知道怎样告诉框架委托这种控制责任给Ninject.幸运的是,让ASP.NET M