之前的文章介绍了MVC如何通过ControllerFactory及ControllerActivator创建Controller,而Controller又是如何通过ControllerBase这个模板完成了功能的拓展及业务的执行。这一系列MVC类型的设计处处都体现了IoC的设计原则,所以本章将从以下几点对ASP.NET MVC中的IoC进行介绍:
● 什么是IoC
● ASP.NET MVC中的IoC
● 什么是DI
● IoC容器与依赖解析器(Dependency Resolver)
● ASP.NET MVC中使用DI
什么是IoC
IoC(控制反转,Inversion of Control)它是软件开发中的一种设计原则,意思就是把控制权从自身(这里的自身大部分指代的是业务代码)转给其它对象来控制。IoC的意图是将任务的执行从实现中解耦,可以专注于模块任务的设计(如:操作数据库的组件,可以有操作SQL Server和My SQL的),系统本身不会关注这个模块做了什么,同时当系统替换了这个模块后不会对系统造成影响。在软件开发中有几种常见的基于IoC思想的实现:
● 工厂模式
● 模板模式
● 策略模式
● 依赖注入
以上模式都是将实现解耦,便于拓展。特别是依赖注入(DI,Dependency Injection),看到IoC更多想到的就依赖注入。
依赖倒置原则(DIP):上面说了那么多的实现,但有一个根本的原则就是依赖倒置原则“高层模块不依赖于低层模块,它们都应该依赖于抽象,抽象不应该依赖于实现,实现应该依赖于抽象”。在.Net中就是使用接口和抽象类对业务和对象进行抽象,抽象与实现分开,如果放弃了这一原则实现依赖于实现,那么就反转不了了(lll¬ω¬)。
ASP.NET MVC中的IoC
之前的文章中分析了ASP.NET MVC中Controller的创建过程和执行中主要的参与对象分别有:
● DefaultControllerFactory:工厂模式。
● DefaultControllerActivator:依赖注入(注:创建Controller时首先从依赖解析器中获取Controller实例,无法获取才自己创建)。
● ControllerBase:模板模式(注:ControllerBase定义了Execute模板方法调用抽象方法ExecuteCore,ExecuteCore的实现在子类中)。
这些对象的设计思想就是IoC。
什么是DI
DI(Dependency Injection,依赖注入),它其实是两个部分,第一个是“依赖”,然后才是“注入”。
1. 依赖:简单来说就是需要,在面向对象的一个类里面它“需要”其它类型来完成工作。如在MyBlog中的逻辑类需要仓储类来操作数据库,这种情况下就是逻辑类依赖仓储类:
2. 注入:其实就是对类的依赖赋值,常用的注入的方式是:构造注入、属性注入、方法注入,说白了就是通过构造方法、属性(Setter方法)以及方法将依赖的对象传入类的实例中并赋值。但要注意的是这个“注”字,为什么不直接用赋值?
举个栗子:制作一个水球,要么留一个注水口,要么在制作的时候把水灌入然后封死。
对于有注水口的水球,在使用时如果注入红色的水,就变成了红色的水球,注入黄色的就是黄色的,而被封死的水球在制作时灌了什么颜色的水就是什么颜色的。
而在程序开发中更是如此,以MyBlog为例,业务逻辑类通过仓储类来获取数据,业务类可以“封死”只用SQL Server的操作类,也可以开放,在运行时将数据操作这个实例“注入”,那么注入SQL Server操作类就使用SQL Server,注入MySQL的就使用My SQL。
对水球注水首先要有水,其次是水球要有注水口,那么在程序里的“水”和“注水口”是什么?
IoC容器与依赖解析器(DependencyResolver)
在程序中所谓的“水”应该是那些抽象的实现,或者说实现了抽象的类型。比如操作SQL Server的仓储类和操作MySQL的仓储操作类。那么这些“水”要放在哪里?“水”当然要放在容器里,所以就有了IoC容器。
那IoC容器是什么?它可以简单到只是一个实例的数组或字典,通过实例的类型(或实现的父类型、接口等)从这个字典或数组中获取对应的实例,也可以是像Autofac、Ninject这些复杂成熟的IoC容器组件。
以下代码分别是AutoFac和NinJect官方文档中为容器注“水”的过程:
Ninject:https://github.com/ninject/ninject
AutoFac:https://autofac.org/
有了装满“水”的容器,想要把“水”注入到程序中,那就需要“注水口”依赖解析器(Denpendency Resolver)的支持。
以下代码就是DefaultControllerActivator创建Controller的代码:
想象一下,把上面的“注水口”连接到装满“水”的容器中,不是就能够把需要的东西注入到需要的位置吗?
ASP.NET MVC中使用DI
DI的使用有两个必要条件就依赖容器和依赖解析器,ASP.NET中建议使用成熟的依赖容器,如Autofac等,它们提供了强大的功能并支持多种组件注册到容器以及注入方式。而关于依赖解析器在ASP.NET MVC中是有默认解析器的,以下是ASP.NET MVC的解析器定义:
它实际的作用是通过Current属性获取一个IDependencyResolver的对象。
DependencyResolver中内置了一些实现该接口的对象,但是几乎是无用的如下图:
所以一般情况下如果在ASP.NET中都是实现IDependencyResolver接口然后通过DependencyResolver.SetResolver方法修改默认的依赖解析器。
在ASP.NET MVC中,使用依赖注入最频繁的就是Controller,Controller作为业务逻辑执行的入口,它依赖业务逻辑的组件,而业务逻辑组件又依赖数据操作组件等等。所以在ASP.NET MVC中使用依赖注入实际上就是把Controller及其依赖都放入容器里,然后创建Controller时从容器中获取即可。根据Controller的创建流程将使用方法分为以下几种:
1. 实现IDependencyResolver接口然后通过DependencyResolver.SetResolver方法修改默认的依赖解析器(替换默认的“注水口”)。
2. 实现IControllerActivator接口,然后在创建DefaultControllerFactory时将其以参数的形式传入到DefaultControllerFactory中(在ControllerActivator中添加自己的“注水口”,并替换掉原有的ControllerActivator)。
3. 继承DefaultControllerFactory使用从容器中获取Controller的方法将原有的GetControllerInstance方法替换掉(添加“注水口”放置在ControllerFactory中,放弃ControllerActivator的使用)。
注:由于在Controller类型中包含一个IDependencyResolver属性,所以方法2和方法3没有替换默认依赖解析器会导致Controller中使用该属性无法访问到真实的容器,如果需要在其基础上使用方法1将默认的依赖解析器替换掉。
小结
IoC容器和依赖注入在软件开发中是一个非常中要的概念,现在主流的一些开发框架的核心都是基于依赖注入的,即框架中的所有组件如日志、缓存、队列等都会通过容器将其注入到使用的地方。本章主要以文字的形式介绍了IoC、DI的概念及其在ASP.NET MVC中的使用的三种方法,在下一篇文章中将会用代码的形式介绍如何使用这三种方法在ASP.NET MVC中实现依赖注入。
参考:
https://en.wikipedia.org/wiki/Inversion_of_control