当前位置: 移动技术网 > IT编程>开发语言>.net > 解读ASP.NET 5 & MVC6系列教程(7):依赖注入

解读ASP.NET 5 & MVC6系列教程(7):依赖注入

2017年12月12日  | 移动技术网IT编程  | 我要评论

hostages,童宜云,萌化危机

在前面的章节(middleware章节)中,我们提到了依赖注入功能(dependency injection),asp.net 5正式将依赖注入进行了全功能的实现,以便开发人员能够开发更具弹性的组件程序,mvc6也利用了依赖注入的功能重新对controller和view的服务注入功能进行了重新设计;未来的依赖注入功能还可能提供更多的api,所有如果还没有开始接触依赖注入的话,就得好好学一下了。

在之前版本的依赖注入功能里,依赖注入的入口有mvc中的icontrollerfactory和web api中的ihttpcontrolleractivator中,在新版asp.net5中,依赖注入变成了最底层的基础支撑,mvc、routing、signalr、entity framrwork等都依赖于依赖注入的iserviceprovider接口,针对该接口微软给出了默认的实现serviceprovider,以及ninject和autofac版本的包装,当然你也可以使用其它第三方的依赖注入容器,如castle windsor等;一旦应用了第三方容器,所有的依赖解析都会被路由到该第三方容器上。

针对通用的依赖类型的解析与创建,微软默认定义了4种类别的生命周期,分别如下:

类型 描述
instance 任何时间都只能使用特定的实例对象,开发人员需要负责该对象的初始化工作。
transient 每次都重新创建一个实例。
singleton 创建一个单例,以后每次调用的时候都返回该单例对象。
scoped 在当前作用域内,不管调用多少次,都是一个实例,换了作用域就会再次创建实例,类似于特定作用内的单例。

类型注册与示例

依赖注入类型的注册一般是在程序启动的入口中,如startup.cs中的configureservices中,该类的主要目的就是注册依赖注入的类型。由于依赖注入的主要体现是接口编程,所以本例中,我以接口和实现类的方式来举例。

首先声明一个接口itodorepository和实现类todorepository1,代码如下:

public interface itodorepository
{
 ienumerable<todoitem> allitems { get; }
 void add(todoitem item);
 todoitem getbyid(int id);
 bool trydelete(int id);
}

public class todoitem
{
 public int id { get; set; }
 public string name { get; set; }
}

public class todorepository : itodorepository
{
 readonly list<todoitem> _items = new list<todoitem>();

 public ienumerable<todoitem> allitems
 {
 get { return _items; }
 }

 public todoitem getbyid(int id)
 {
 return _items.firstordefault(x => x.id == id);
 }

 public void add(todoitem item)
 {
 item.id = 1 + _items.max(x => (int?)x.id) ?? 0;
 _items.add(item);
 }

 public bool trydelete(int id)
 {
 var item = getbyid(id);

 if (item == null) { return false; }

 _items.remove(item);

 return true;
 }
}

为了演示不同的声明周期类型,建议多实现几个类,比如todorepository2、todorepository3、todorepository4等,以便进行演示。

然后在configureservices方法内注册接口itodorepository类型和对应的实现类,本例中根据不同的生命周期注册了不同的实现类,具体示例如下:

//注册单例模式,整个应用程序周期内itodorepository接口的示例都是todorepository1的一个单例实例
services.addsingleton<itodorepository, todorepository1>();
services.addsingleton(typeof(itodorepository), typeof(todorepository1)); // 等价形式

//注册特定实例模型,整个应用程序周期内itodorepository接口的示例都是固定初始化好的一个单例实例

todorepository2
services.addinstance<itodorepository>(new todorepository2());
services.addinstance(typeof(itodorepository), new todorepository2()); // 等价形式

//注册作用域型的类型,在特定作用域内itodorepository的示例是todorepository3
services.addscoped<itodorepository, todorepository3>();
services.addscoped(typeof(itodorepository), typeof(todorepository3));// 等价形式

//获取该itodorepository实例时,每次都要实例化一次todorepository4类
services.addtransient<itodorepository, todorepository4>();
services.addtransient(typeof(itodorepository), typeof(todorepository));// 等价形式

//如果要注入的类没有接口,那你可以直接注入自身类型,比如:
services.addtransient<logginghelper>();

依赖注入的在mvc中的使用方式目前有三种,分别是controller的构造函数、属性以及view中的inject形式。其中构造函数注入和之前的mvc中的是一样的,示例代码如下:

public class todocontroller : controller
{
 private readonly itodorepository _repository;

 /// 依赖注入框架会自动找到itodorepository实现类的示例,赋值给该构造函数
 public todocontroller(itodorepository repository)
 {
 _repository = repository;
 }

 [httpget]
 public ienumerable<todoitem> getall()
 {
 return _repository.allitems; //这里就可以使用该对象了
 }
}

属性注入,则是通过在属性上加一个[fromservices]属性即可实现自动获取实例。

public class todocontroller : controller
{
 // 依赖注入框架会自动找到itodorepository实现类的示例,赋值给该属性
 [fromservices]
 public itodorepository repository { get; set; }

 [httpget]
 public ienumerable<todoitem> getall()
 {
 return repository.allitems;
 }
}

注意:这种方式,目前只适用于controller以及子类,不适用于普通类
同时:通过这种方式,你可以获取到更多的系统实例对象,如
actioncontexthttpcontexthttprequesthttpresponseviewdatadictionary以及actionbindingcontext

在视图中,则可以通过@inject关键字来实现注入类型的实例提取,示例如下:

@using webapplication1
@inject itodorepository repository
<div>
 @repository.allitems.count()
</div>

而最一般的使用方式,则是获取iserviceprovider的实例,获取该iserviceprovider实例的方式目前有如下几种(但范围不同):

var provider1 = this.request.httpcontext.applicationservices; 当前应用程序里注册的service
var provider2 = context.requestservices; // controller中,当前请求作用域内注册的service
var provider3 = resolver; //controller中

然后通过getservice和getrequiredservice方法来获取指定类型的实例,示例如下:

var _repository1 = provider1.getservice(typeof(itodorepository));
var _repository2 = provider1.getservice<logginghelper>();//等价形式
//上述2个对象可能为空

var _repository3 = provider1.getrequiredservice(typeof(itodorepository));
var _repository4 = provider1.getrequiredservice<logginghelper>();//等价形式
//上述2个对象肯定不为空,因为如果为空的话,会自动抛异常出来

普通类的依赖注入

在新版的asp.net5中,不仅支持上面我们所说的接口类的依赖注入,还支持普通的类型的依赖注入,比如我们生命一个普通类,示例如下:

public class appsettings
{
 public string sitetitle { get; set; }
}

上述普通类要保证有无参数构造函数,那么注册的用法,就应该像如下这样:

services.configure<appsettings>(app =>
{
 app.sitetitle = "111";
});

使用的时候,则需要获取ioptions<appsettings>类型的实例,然后其options属性即是appsettings的实例,代码如下:

var appsettings = app.applicationservices.getrequiredservice<ioptions<appsettings>>().options;

当然,我们也可以在视图中,使用@inject语法来获取实例,示例代码如下:

@inject ioptions<appsettings> appsettings

<title>@appsettings.options.sitetitle</title>

基于scope生命周期的依赖注入

普通的scope依赖注入

基于scope作用域的实例在创建的时候需要先创建作用域,然后在该作用域内再获取特定的实例,我们看看一个示例并对其进行验证。首先,注册依赖注入类型,代码如下:

services.addscoped<itodorepository, todorepository>();

然后创建作用域,并在该作用域内获取实例:

var serviceprovider = resolver;

var scopefactory = serviceprovider.getservice<iservicescopefactory>(); //获取scope工厂类
using (var scope = scopefactory.createscope()) // 创建一个scope作用域
{
 var containerscopedservice = serviceprovider.getservice<itodorepository>(); //获取普通的实例
 var scopedservice1 = scope.serviceprovider.getservice<itodorepository>(); //获取当前scope的实例
 thread.sleep(200);
 var scopedservice2 = scope.serviceprovider.getservice<itodorepository>(); //获取当前scope的实例

 console.writeline(containerscopedservice == scopedservice1); // 输出:false
 console.writeline(scopedservice1 == scopedservice2); //输出:true
}

另外,scope也可以进行嵌套,嵌套的内外作用域所获取的实例也是不相同的,实例代码如下:

var serviceprovider = resolver;

var outerscopefactory = serviceprovider.getservice<iservicescopefactory>();
using (var outerscope = outerscopefactory.createscope()) //外部scope作用域
{
 var innerscopefactory = outerscope.serviceprovider.getservice<iservicescopefactory>();
 using (var innerscope = innerscopefactory.createscope()) //内部scope作用域
 {
 var outerscopedservice = outerscope.serviceprovider.getservice<itodorepository>();
 var innerscopedservice = innerscope.serviceprovider.getservice<itodorepository>();

 console.writeline(outerscopedservice == innerscopedservice); // 输出:false
 }
}

基于http请求的scope依赖注入

在之前很多流行的di容器中,针对每个请求,在该请求作用域内保留一个单实例对象是很流行的,也就是在每次请求期间一个类型的对象实例只会创建一次,这样可以大大提高性能。

在asp.net5中,基于http请求的scope依赖注入是通过一个containermiddleware来实现的,调用该middleware时,会创建一个限定作用域的di容器,用于替换当前请求中已有的默认di容器。在该管线中,所有后续的middleware都会使用这个新的di容器,在请求走完整个pipeline管线以后,该containermiddleware的作用就结束了,此时作用域会被销毁,并且在该作用域内创建的实例对象也都会销毁释放。

containermiddleware的时序图如下所示:

具体的使用方式如下:

app.use(new func<requestdelegate, requestdelegate>(nextapp => new containermiddleware(nextapp, app.applicationservices).invoke));

普通类的依赖注入处理

目前普通类的依赖注入,只支持构造函数,比如我们定于一个testservice类,代码如下:

public class testservice
{
 private itodorepository _repository;
 public testservice(itodorepository r)
 {
 _repository = r;
 }

 public void show()
 {
 console.writeline(_repository.allitems);
 }
}

通过在构造函数里传入itodorepository类的参数来使用该实例,使用的时候需要先将该类注册到di容器中,代码如下:

services.addscoped<itodorepository, todorepository>();
services.addsingleton<testservice>();

然后调用如下语句即可使用:

var service = serviceprovider.getrequiredservice<testservice>();

另外,需要注意,在目前的情况下,不能使用[fromservices]来使用依赖注入功能,比如,如下代码在获取testservice2实例的过程中会出现错误:

public class testservice2
{
 [fromservices]
 public itodorepository repository { get; set; }
 public void show()
 {
 console.writeline(repository.allitems);
 }
}

普通类中获取httpcontext实例

在mvc6中,我们没办法通过httpcontent.current来获取上下文对象了,所以在普通类中使用的时候就会出问题,要想在普通类中使用该上下文对象,需要通过依赖注入来获取httpcontext实例,微软在asp.net5中,提供了ihttpcontextaccessor接口用于获取该上下文对象。也就是说,我们可以将该类型的参数放在构造函数中,以获取上下文实例,代码如下:

public class testservice3
{
 private ihttpcontextaccessor _httpcontextaccessor;
 public testservice3(ihttpcontextaccessor httpcontextaccessor)
 {
 _httpcontextaccessor = httpcontextaccessor;
 }

 public void show()
 {
 var httpcontext = _httpcontextaccessor.httpcontext;//获取上下文对象实例
 console.writeline(httpcontext.request.host.value);
 }
}

而使用的时候,则直接通过如下语句就可以了,代码如下:

var service = serviceprovider.getrequiredservice<testservice3>();
service.show();

提示:普通类的构造函数中,可以传入多个di容器支持的数据类似作为参数。

使用第三方di容器

目前,.netcore不支持,只能在全功能版的.net framework上才能使用,所以使用的时候需要注意一下。第三方di容器的替换通常是在startup.cs的configure方法中进行的,在方法的开始处进行替换,以便后续的middleware会使用相关的依赖注入功能。

首先要引入第三方的容器,以autofac为例,引入microsoft.framework.dependencyinjection.autofac,然后加入如下示例中的替换代码即可:

app.useservices(services =>
{
 services.addmvc();// addmvc要在这里注册
 var builder = new containerbuilder();// 构造容器构建类
 builder.populate(services);//将现有的services路由到autofac的管理集合中
 icontainer container = builder.build();
 return container.resolve<iserviceprovider>();//返回autofac实现的iserviceprovider
});

注意,使用上述方法的时候,要把mvc的注册代码services.addmvc();必须要从configureservices中挪到该表达式内,否则会报异常,等待微软解决。

另外,还有一个方式,微软目前的实例项目中还没有公开,通过分析一些代码,我们可以发现,在microsoft.aspnet.hosting程序中的startuploader.cs负责程序入口点的执行,在该文件中,我们知道首先是调用startup.cs中的configureservices方法,然后再调用configure方法;我们可以看到示例中的configureservices的返回值是void类型的,但在源码分析中发现,在根据约定解析configureservices方法的时候,其首先判断有没有返回类型是iserviceprovider的,如果有则执行该方法,用使用该返回中返回的新iserviceprovider实例;没有的话,再继续查找void类型的configureservices方法。所以,我们可以通过这种方式,来替换第三方的di容器,实例代码如下:

// 需要先删除void类型的configureservices方法
public iserviceprovider configureservices(iservicecollection services)
{
 var builder = new containerbuilder(); // 构造容器构建类
 builder.populate(services); //将现有的services路由到autofac的管理集合中
 icontainer container = builder.build();
 return container.resolve<iserviceprovider>(); //返回autofac实现的iserviceprovider
}

这样,你就可以像以往一样,使用autofac的方式进行依赖类型的管理了,示例如下:

public class autofacmodule : module
{
 protected override void load(containerbuilder builder)
 {
 builder.register(c => new logger())
  .as<ilogger>()
  .instanceperlifetimescope();

 builder.register(c => new valuesservice(c.resolve<ilogger>()))
  .as<ivaluesservice>()
  .instanceperlifetimescope();
 }
}

地址:https://github.com/aspnet/hosting/blob/dev/src/microsoft.aspnet.hosting/startup/startuploader.cs
另外一个关于autofac集成的案例:

最佳实践

在使用依赖注入的的时候,我们应该遵守如下最佳实践。

做任何事情之前,务必在程序入口点提前注册所有的依赖类型。避免直接使用iserviceprovider接口,相反,在构造函数里显式添加需要依赖的类型即可,让依赖注入引擎自己来解析实例,一旦依赖很难管理的话,就使用抽象工厂。基于接口进行编程,而不是基于实现进行编程。

参考1:
参考2:

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网