Ninject之旅之十:Ninject自定义提供者
摘要
提供者是特殊的工厂类,Ninject使用它来实例化解析类型。任何时候我们绑定一个服务类型到一个组件,我们都隐式地关联那个服务类型到一个可以实例化那个组件的提供者。这个隐藏的提供者被称为StandardProvider,是一个通用的工厂,他可以创建每一个给定类型的实例。尽管我们可以经常依赖StandardProvider而不用对他在背后做了什么费心,Ninject也允许我们创建和注册我们自己自定义的提供者,只要我们需要自定义这个下面的激活过程:
Bind<IService>().ToProvider<MyService>();
public class MyServiceProvider : Provider<MyService>
{
protected override MyService CreateInstance(IContext context)
{
return new MyService();
}
}
尽管继承这个Provider<T>类是推荐的方法来创建一个自定义提供者。作为一个提供者,为一个类继承这个IProvider接口在Ninject就足够了:
public interface IProvider
{
Type Type { get; }
object Create(IContext context);
}
回顾上一篇文章的ShippersSqlRepository类和ShippersXmlRepository类,他们的的构造函数都有一个字符串类型的参数。
ShippersSqlRepository构造函数参数northwindConnectionString提供连接字符串:
public class ShippersSqlRepository : IShippersRepository
{
private readonly NorthwindContext objectContext;
public ShippersSqlRepository(string northwindConnectionString)
{
objectContext = new NorthwindContext(northwindConnectionString);
}
public IEnumerable<Business.Model.Shipper> GetShippers()
{ ... }
public void AddShipper(Business.Model.Shipper shipper)
{ ... }
}
ShippersXmlRepository构造函数参数xmlRepositoryPath提供xml文件路径:
public class ShippersXmlRepository : IShippersRepository
{
private readonly string documentPath;
public ShippersXmlRepository(string xmlRepositoryPath)
{
this.documentPath = xmlRepositoryPath;
}
public IEnumerable<Shipper> GetShippers()
{ ... }
public void AddShipper(Shipper shipper)
{ ... }
}
在这个情况下,这些参数阻止了Ninject实例化我们的repository,因为这个kernel对怎样解析字符串参数没有任何主意。因此,下面几行对于注册我们的repository还不够:
Bind<IShippersRepository>().To<ShippersSqlRepository>()
.When(r => r.Target.Name.StartsWith("source"));
Bind<IShippersRepository>().To<ShippersXmlRepository>()
.When(r => r.Target.Name.StartsWith("target"));
一个提供需要的参数的方法是使用这个WithConstructorArgument方法:
connection = ConfigurationManager.AppSettings["northwindConnectionString"];
Bind<IShippersRepository>()
.To<ShippersSqlRepository>()
.When(r => r.Target.Name.StartsWith("source"))
.WithConstructorArgument("NorthwindConnectionString", connection);
path = ConfigurationManager.ConnectionStrings["xmlRepositoryPath"];
Bind<IShippersRepository>()
.To<ShippersXmlRepository>()
9 .When(r => r.Target.Name.StartsWith("target"))
.WithConstructorArgument("XmlRepositoryPath",path);
它看起来很好,这时候我们不需要注册很多需要这样的配置的repository。然而,在更复杂的情况下,我们需要以某种方式自动注入这些参数。这里所有的这些设置是字符串的实例。因此,我们可以为字符串类型创建一个提供者,基于参数名称来生成我们的配置字符串。这个提供者将在应用程序配置文件(web.config或者app.config)的键里查找参数名,如果这样的一个配置是定义好的(像下面代码一样),它就返回它的值:
using Ninject.Activation;
using System;
using System.Configuration; namespace DataMigration.Business.Provider
{
public class ConfigurationProvider : Provider<string>
{
protected override string CreateInstance(IContext context)
{
if (context.Request.Target == null)
{
throw new Exception("Target required.");
}
var paramName = context.Request.Target.Name;
string value = ConfigurationManager.AppSettings[paramName];
if (string.IsNullOrEmpty(value))
{
value = ConfigurationManager.ConnectionStrings[paramName] == null ? ""
: ConfigurationManager.ConnectionStrings[paramName].ConnectionString;
}
return value;
}
}
}
ConfigurationProvider被提供了一个包含了当前激活过程所有信息的context对象,包含在这篇文章之前提到过的请求对象。这个请求对象包含目标信息。在这种情况下,目标信息是哪个注入的字符串对象作为构造函数参数。如果被请求的字符串类型是直接从kernel使用Get<string>()方法请求的,目标对象将为空。因为我们需要参数名称作为配置键,我们首先检查目标。使用目标名称,我们可以查找AppSettings,如果我们没有找到这样一个配置,我们将继续在ConnectionStrings部分查找。最后,返回得到的值。
唯一的问题是这个提供者将被注册为字符串类型,他将影响到将要被Ninject解析的任何字符串。为了明确规定是那些将要被认为是应用程序配置的字符串,我们定义一个自定义特性,像下面这样在那些参数上运用它:
using System; namespace DataMigration.Business.Attributes
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class ConfigurationAttribute : Attribute { }
}
我们已经声明了这个特性只能运用在属性和参数上。下面是这个特性怎样运用到我们repository类构造函数参数上:
public ShippersSqlRepository([Configuration]string connectionString)
{
_context = new NorthwindContext(connectionString);
} public ShippersXmlRepository([Configuration]string xmlRepositoryPath)
{
this.documentPath = xmlRepositoryPath;
}
最后,绑定代码像下面这样:
Bind<string>().ToProvider<ConfigurationProvider>().WhenTargetHas<ConfigurationAttribute>();
激活上下文
当我们的提供者重载这个CreateInstance方法,我们使用这个上下文context对象,通过方法参数传入。这个对象的类继承IContext接口,这个接口包含了非常多所有跟当前激活过程相关的信息。使用这个对象,我们可以访问当前绑定对象,正在被解析的类型,正在被注入的类型,我们当前在依赖图的什么位置,谁请求了这个解析,等等。在解析一个依赖图的时候,为每一个正在解析的类型创建一个上下文对象,这就导致了一个激活上下文图。从每一个上下文对象开始,我们也可以从他的父上下文节点导航,直到到达图的根,就是最初的发起请求的点。在使用Niinject时,任何我们需要决定怎样解析依赖的地方上下文对象都是可以得到的。
工厂方法
工厂方法是另一个通知Ninject怎样解析一个依赖。像创建一个提供者一样,我们已经访问了这个激活上下文对象,来帮助我们做出决定怎样解析请求类型。然而,我们不需要创建一个新类,我们可以只是内联地写出我们的解析逻辑。工程方法是提供者类的一个很好的替代,在这里解析逻辑是简单和简短的。一个好的使用工厂方法的例子是在一个类里实例化一个日志对象。下面是不使用DI实例化一个日志对象的代码:
class ConsumerClass
{
private ILog log = LogManager.GetLogger(typeof(ConsumerClass));
}
我们可以在前面的类中使用下面的代码实现DI:
class ConsumerClass
{
private ILog log;
public ConsumerClass(ILog log)
{
this.log = log;
}
}
为ILogger接口用To<T>()方法注册一个类型绑定是不合适的,因为具体的日志对象必须通过调用LogManager.GetLogger方法来创建,而不是通过具体日志类的构造函数。在这种情况下,我们可以使用一个工厂方法来通知Ninject创建一个新日志对象:
Bind<ILog>().ToMethod(ctx => LogManager.GetLogger(ctx.Request.ParentRequest.Service));
这个ctx的类型是IContext,我们从这个Ninject激活上下文父请求的服务属性中,得到消费者类的类型。
Ninject之旅之十:Ninject自定义提供者的更多相关文章
- Ninject之旅之五:Ninject XML配置
摘要 使用XML配置,需要添加Ninject XML扩展的引用.下一步是添加一个或多个包含类型注册的XML文件.记得这些文件应该跟应用程序一起发布.因此不要忘记将XML文件的属性设置成“Copy if ...
- Ninject之旅之十二:Ninject在Windows Form程序上的应用(附程序下载)
摘要: 下面的几篇文章介绍如何使用Ninject创建不同类型的应用系统.包括: Windows Form应用系统 ASP.NET MVC应用系统 ASP.NET Web Form应用系统 尽管对于不同 ...
- Ninject之旅之十四:Ninject在ASP.NET Web Form程序上的应用(附程序下载)
摘要 ASP.NET Web Forms没有像MVC那样的可扩展性,也不可能使它创建UI页面支持没有构造函数的的激活方式.这个Web Forms应用程序的的局限性阻止了它使用构造函数注入模式,但是仍能 ...
- Ninject之旅之三:Ninject对象生命周期
摘要 DI容器的一个责任是管理他创建的对象的生命周期.他应该决定什么时候创建一个给定类型的对象,什么时候使用已经存在的对象.他还需要在对象不需要的时候处理对象.Ninject在不同的情况下管理对象的生 ...
- Ninject之旅之八:Ninject插件模型(附程序下载)
摘要 在前面的章节中,我们看了在单一的绑定条件下Ninject能够处理依赖类型,就是说,每个服务类型只绑定到单一的实现类型.然而,有些情况下我们需要绑定一个抽象服务类型到多个实现,这叫多个绑定.多个绑 ...
- Ninject之旅之七:Ninject依赖注入
摘要 可以使用不同的模式向消费者类注入依赖项,向构造器里注入依赖项是其中一种.有一些遵循的模式用来注册依赖项,同时有一些需要避免的模式,因为他们经常导致不合乎需要的结果.这篇文章讲述那些跟Ninjec ...
- Ninject之旅之六:Ninject约定
摘要 在小的应用系统中一个一个注册一些服务类型不怎么困难.但是,如果是一个实际的有上百个服务的应用程序呢?约定配置允许我们使用约定绑定一组服务,而不用一个一个分别绑定. 要使用约定配置,需要添加Nin ...
- Ninject之旅之四:Ninject模块
摘要 随着应用程序的增长,注册的服务列表跟着变长,管理这个列表将变得困难.Ninject模块是一个好的将我们的类型绑定分离到不同的绑定组的方式,它很容易地将分组组织到不同的文件中.将一个类变成一个Ni ...
- Ninject之旅目录
第一章:理解依赖注入 Ninject之旅之一:理解DI 第二章:开始使用Ninject Ninject之旅之二:开始使用Ninject(附程序下载) Ninject之旅之三:Ninject对象生命周期 ...
随机推荐
- http请求报错
手机端上传base64位图片java后台接受 手机端post方式发送 后台报错: Error parsing HTTP request header Note: further occurrences ...
- 改进:js修改iOS微信浏览器的title
问题简介 前端入门没多久,可能连入门也不算,最近网上流行各自书籍改名,什么<前端开发,从入门到放弃>,<Android开发,从入门到改行>之类的,程序员真是个爱自嘲的群体,但我 ...
- div标签上下滚动
<div id="myInfo" style={{width:'100%',height:'100%', overflow:'scroll'}}></div> ...
- php://input
从官网信息来看,php://input是一个只读信息流,当请求方式是post的,并且enctype不等于"multipart/form-data"时,可以使用php://input ...
- ILGenerator.Emit动态 MSIL编程(一)之基础
首先在Framework中,Emit相关的类基本都存在于System.Reflection.Emit命名空间下.可见Emit是作为反射的一个元素存在的. Emit能够实现什么?为什么要学习Emit?首 ...
- 【转】error while loading shared libraries: libevent-2.0.so.5: cannot open shared object file: No such file or directory
错误信息: /usr/local/memcacheq/bin/memcacheq: error while loading shared libraries: libevent-2.0.so.5: c ...
- Windows 2008 R2 强制删除Cluster
在正常删除Cluster 节点之后,再添加节点时,报“节点已经加入群集”,无法加入,注册表信息删除后可正常移除Cluster服务,如下: HKEY_LOCAL_MACHINE\SYSTEM\Curre ...
- 6个错误将杀死你的App
没有开发者或者设计师会故意破坏应用的设计.所有的应用程序创建者都对自己的应用寄予美好的愿望,但是很多错误是在他们无意识的状态下破坏app的设计.以下是应用开发者和设计者经常犯的几个错误,不过这些错误是 ...
- access生成sql脚本,通过VBA调用ADOX
access生成sql脚本,通过VBA调用ADOX. 使用 MS Access 2016 的VBA,读取mdb文件中的所有表结构(数据类型/长度/精度等),生成对应的SQL create table语 ...
- C#Random函数在循环中每次获取一样的值
首先需要了解一点Random函数的随机生成是和当前时间有关系,如果在短时间生成随机数,就会导致随机数生成出来是相同的. 不过我们可以在每次随机时指定一个Seed种子值,这样在循环里就可以每次获取不一样 ...