ASP.NET MVC IOC之Unity攻略
一、你知道IOC与DI吗?
1、IOC(Inversion of Control )——控制反转
即依赖对象不在被依赖模块的类中直接通过new来获取
先看看下面这段代码的问题~
public class SqlServerDal { public void Delete() { Console.WriteLine("删除表中某个订单信息!"); } } public class Order { private readonly SqlServerDal dal = new SqlServerDal(); public void Delete() { dal.Delete(); }
} using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DIPTest { class Program { static void Main(string[] args) { Order order = new Order(); order.Delete(); Console.Read(); } }
}
关于以上例子的说明:
(1)在Order类中,它依赖于具体的对象SqlServerDal,违反了依赖倒置的原则,即不论是高层还是底层,都应该依赖于抽象而不应该依赖于具体
(2)如果需求有变:数据访问层换为OracleDal,那么这个时候,就要修改Order类的代码;如果数据访问层再次换为MySqlDal,那么还要继续修改Order类的代码......如果无休止的变下去,将会是一个噩梦,而且你不但要修改 Order里边的代码,可能你还要修改Product、Users等类里边的代码,因为它们也可能跟Order类是同样的情况
怎么办呢?IOC啊~
那如何IOC啊?使用DI啊~
2、DI(Dependency Injection)——依赖注入
DI是IoC的一种实现方式,就是将依赖对象的创建和绑定转移到被依赖对象类的外面来实现
依赖注入分为:构造函数注入、属性注入和接口注入
(1)构造函数注入
首先,我们为数据访问类SqlServerDal定义一个抽象接口IDataAccess,并在IDataAccess接口中声明GetAll方法:
public interface IDataAccess { void Delete(); }
然后在SqlServerDal类中,实现IDataAccess接口:
public class SqlServerDal:IDataAccess { public void Delete() { Console.WriteLine("删除表中某个订单信息!"); } }
接下来,我们还需要修改Order类:
public class Order { private IDataAccess da; //构造函数注入 public Order(IDataAccess ida) { da = ida;
} public void Delete() { da.Delete(); } }
下面是控制台程序调用的代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text;
namespace IOCDemo
{ class Program { static void Main(string[] args) { SqlServerDal dal = new SqlServerDal();//在Order类外部创建依赖对象 Order order = new Order(dal);//通过构造函数注入依赖 order.Delete(); Console.Read(); } }
}
(2)属性注入
属性注入就是通过属性来传递依赖。因此,我们首先需要在依赖类Order中定义一个属性:
public class Order { private IDataAccess _da;
//属性,接受依赖 public IDataAccess da { set { _da = value; } get { return _da; } } public void Delete() {
_da.Delete(); } }
下面是控制台程序调用的代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text;
namespace IOCDemo
{ class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在外部创建依赖对象 Order order = new Order(); order.da = dal;//给属性赋值 order.Delete(); Console.Read(); } }
}
(3)接口注入
相比构造函数注入和属性注入,用起来没有它们方便。首先定义一个接口,包含一个设置依赖的方法。
public interface IDependent { void SetDependence(IDataAccess ida);//设置依赖项 }
用依赖类实现这个接口:
public class Order : IDependent { private IDataAccess _ida;
public void SetDependence(IDataAccess ida) { _ida = ida; } public void Delete() { _ida.Delete(); } }
下面是控制台程序调用的代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IOCDemo { class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在Order外部创建依赖对象 Order order = new Order(); order.SetDependence(dal);//传递依赖 order.Delete(); Console.Read(); } }
}
3、IoC容器
前面所有的栗子中,我们都是通过手动的方式来创建依赖对象,并将引用传递给被依赖模块。比如:
SqlServerDal dal = new SqlServerDal();//在Order外部创建依赖对象 Order order = new Order(dal);//通过构造函数注入依赖
对于大型项目来说,相互依赖的组件比较多。如果还用手动的方式,自己来创建和注入依赖的话,显然效率很低,而且往往还会出现不可控的场面。因此,IoC容器就诞生了。IoC容器实际上是一个DI框架,它能简化我们的工作量。它包含以下几个功能:
- 动态创建、注入依赖对象。
- 管理对象生命周期。
- 映射依赖关系。
本篇我们使用微软框架组给提供的Unity来实现依赖注入,它是最流行的IOC容器之一
二、Unity的使用
1、Unity是个什么东东?
Unit是微软patterns& practices组用C#实现的轻量级、可扩展的依赖注入容器,我们可以通过代码或者XML配置文件的形式来配置对象与对象之间的关系,在运行时直接调用Unity容器即可获取我们所需的对象,以便建立松散耦合的应用程序。
对于小型项目:用代码的方式实现即可
对于中大型项目:使用配置文件比较好
2、Unity入门
您可以访问http://unity.codeplex.com/releases得到最新版本的Unity,也可以直接在Nuget中获取到最新版本的Unity,或者下载微软的企业库,然后在项目中添加Microsoft.Practices.Unity.dll和Microsoft.Practices.Unity.Configuration.dll的引用
这里用到的最重要的东东就是IUnityContainer 接口,它本身定义了很多方法,当然还有一些扩展方法,具体的接口定义这里就不说了,我们会经常用到IUnityContainer 接口的RegisterInstance、RegisterType、Resolve等方法。
这里我举个栗子,首先定义如下接口,并用两个类来进行实现:
/// <summary> /// 班级接口 /// </summary> public interface IClass { string ClassName { get; set; } void ShowInfo(); } /// <summary> /// 计科班 /// </summary> public class CbClass : IClass { public string ClassName { get; set; } public void ShowInfo() { Console.WriteLine("计科班:{0}", ClassName); } } /// <summary> /// 电商班 /// </summary> public class EcClass : IClass { public string ClassName { get; set; } public void ShowInfo() { Console.WriteLine("电商班:{0}", ClassName); } }
(1)用编程方式实现注入
使用Unity来管理对象与对象之间的关系可以分为以下几步:
A、创建一个UnityContainer对象
B、通过UnityContainer对象的RegisterType方法来注册对象与对象之间的关系
C、通过UnityContainer对象的Resolve方法来获取指定对象关联的对象
注入代码如下:
public static void ContainerCodeTest() { IUnityContainer container = new UnityContainer(); //默认注册(无命名),如果后面还有默认注册会覆盖前面的 container.RegisterType<IClass, CbClass>(); //命名注册 container.RegisterType<IClass, EcClass>("ec"); //解析默认对象 IClass cbClass = container.Resolve<IClass>(); cbClass.ShowInfo(); //指定命名解析对象 IClass ecClass = container.Resolve<IClass>("ec"); ecClass.ShowInfo(); //获取容器中所有IClass的注册的已命名对象 IEnumerable<IClass> classList = container.ResolveAll<IClass>(); foreach (var item in classList) { item.ShowInfo(); } }
(2)配置文件方式
通过配置文件配置Unity信息需要有以下几个步骤:
A、在配置文件<configSections> 配置节下注册名为unity的section
B、在<configuration> 配置节下添加Unity配置信息
C、在代码中读取配置信息,并将配置载入到UnityContainer中
配置文件内容如下:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> </configSections> <unity xmlns=http://schemas.microsoft.com/practices/2010/unity> <!--定义类型别名--> <aliases> <add alias="IClass" type="ConsoleApplication1.UnityDemo.IClass,ConsoleApplication1" /> <add alias="CbClass" type="ConsoleApplication1.UnityDemo.CbClass,ConsoleApplication1" /> <add alias="EcClass" type="ConsoleApplication1.UnityDemo.EcClass,ConsoleApplication1" /> </aliases> <!--容器--> <container name="FirstClass"> <!--映射关系--> <register type="IClass" mapTo="CbClass"></register> <register type="IClass" mapTo="EcClass" name="ec"></register> </container> </unity> </configuration>
注入代码如下:
public static void ContainerConfiguration() { IUnityContainer container = new UnityContainer();//获取指定名称的配置节
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity"); container.LoadConfiguration(section, "FirstClass");//获取特定配置节下已命名的配置节<container name="FirstClass">下的配置信息
IClass classInfo = container.Resolve<IClass>("ec"); classInfo. ShowInfo(); }
注意:
如果系统比较庞大,那么对象之间的依赖关系可能就会很复杂,最终导致配置文件变得很大,所以我们需要将Unity的配置信息从App.config或web.config中分离出来到某一个单独的配置文件中,比如Unity.config,然后将其作为参数传递给下面的方法,依然可以实现依赖注入:
public static void ContainerConfigurationFromFile(string configFile) { //根据文件名获取指定config文件 var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = configFile }; //从config文件中读取配置信息 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); var unitySection = (UnityConfigurationSection)configuration.GetSection("unity"); var container = new UnityContainer().LoadConfiguration(unitySection, "FirstClass"); IClass classInfo = container.Resolve<IClass>("ec"); classInfo.ShowInfo(); }
3、使用Unity为已存在的对象注册关系
在日常开发的过程中我们有时候会自己创建好一个对象,但是你又想对这个已经创建好的对象的生命周期进行管理,这个时候你可以使用Unity提供的RegisterInstance方法(有很多重载),由于RegisterInstance是对已存在的实例进行注册,所以无法通过配置文件来进行配置。
代码示例如下:
public static void RegisterInstance() { IClass myClass = new MyClass(); IClass yourClass = new YourClass(); //为myClass实例注册默认实例 container.RegisterInstance<IClass>(myClass); //为yourClass实例注册命名实例,同RegisterType container.RegisterInstance<IClass>("yourInstance", yourClass); container.Resolve<IClass>().ShowInfo(); container.Resolve<IClass>("yourInstance").ShowInfo(); }
这段代码很简单,就是使用RegisterInstance方法将已存在的实例myClass、yourClass等注册到UnityContainer中,默认情况下其实用的是ContainerControlledLifetimeManager,这个生命周期是由UnityContainer来进行管理,UnityContainer会维护一个对象实例的强引用,当你将已存在的实例注册到UnityContainer后,每次通过Resolve方法获取对象都是同一对象,也就是单件实例(singleton instance),具体有关生命周期相关信息在下面进行介绍。
注意是单实例哦~
4、Unity中生命周期管理
我们在系统中引入Unity主要就是想通过Unity来解除对象之间的依赖关系,方便我们根据配置调用到所需的对象,而Unity默认情况下会自动帮我们维护好这些对象的生命周期,可能Unity自动维护的生命周期并不总是我们想要的,这时我们就要根据具体的需求来更改这些对象的生命周期,下面就简单介绍一下Unity中内置的两个常用生命周期管理器,其他的生命周期管理器如果需要可以自己上网查看其详细说明。
(1)TransientLifetimeManager,瞬态生命周期,默认情况下,在使用RegisterType进行对象关系注册时如果没有指定生命周期管理器则默认使用这个生命周期管理器,这个生命周期管理器就如同其名字一样,当使用这种管理器的时候,每次通过Resolve或ResolveAll调用对象的时候都会重新创建一个新的对象。
代码如下:
public static void TransientLifetimeManagerCode() { //以下2种注册效果是一样的 container.RegisterType<IClass, MyClass>(); container.RegisterType<IClass, MyClass>(new TransientLifetimeManager()); Console.WriteLine("-------TransientLifetimeManager Begin------"); Console.WriteLine("第一次调用RegisterType注册的对象HashCode:" + container.Resolve<IClass>().GetHashCode()); Console.WriteLine("第二次调用RegisterType注册的对象HashCode:" + container.Resolve<IClass>().GetHashCode()); Console.WriteLine("-------TransientLifetimeManager End------"); }
如果是使用配置的方式,则需要在配置文件中注册关系的时候在<register>配置节下新增<lifetime>既可(如果不新增则默认使用TransientLifetimeManager),如果想使用其他的生命周期管理器,则更改此配置节即可!
其中<lifetime>有3个参数:
- type,生命期周期管理器的类型,这边可以选择Unity内置的,也可以使用自定义的,其中内置的生命周期管理器会有智能提示
- typeConverter,生命周期管理器转换类,用户自定义一个生命周期管理器的时候所创建一个转换器
- value,初始化生命周期管理器的值
如果用此生命周期管理器,则要在配置文件中新增的节点如下:
<register type="IClass" mapTo="MyClass"> <lifetime type="transient" /> </register>
注入代码如下:
public static void TransientLifetimeManagerConfiguration() { //获取指定名称的配置节 UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity"); container.LoadConfiguration(section, "FirstClass"); Console.WriteLine("-------TransientLifetimeManager Begin------"); Console.WriteLine("第一次调用RegisterType注册的对象HashCode:" + container.Resolve<IClass>("transient").GetHashCode()); Console.WriteLine("第二次调用RegisterType注册的对象HashCode:" + container.Resolve<IClass>("transient").GetHashCode()); Console.WriteLine("-------TransientLifetimeManager End------"); }
以上无论是代码还是配置的方式,运行之后都会发现实例的哈希码是不一样的,说明每次调用都是重新生成一个对象实例!
(2)ContainerControlledLifetimeManager,容器控制生命周期管理,这个生命周期管理器是RegisterInstance默认使用的生命周期管理器,也就是单件实例,UnityContainer会维护一个对象实例的强引用,每次调用的时候都会返回同一对象,示例代码如下:
public static void ContainerControlledLifetimeManagerCode() { IClass myClass = new MyClass(); //以下2种注册效果是一样的 container.RegisterInstance<IClass>("ccl", myClass); container.RegisterInstance<IClass>("ccl", myClass, new ContainerControlledLifetimeManager()); container.RegisterType<IClass, MyClass>(new ContainerControlledLifetimeManager()); Console.WriteLine("-------ContainerControlledLifetimeManager Begin------"); Console.WriteLine("第一次调用RegisterType注册的对象HashCode:" + container.Resolve<IClass>().GetHashCode()); Console.WriteLine("第二次调用RegisterType注册的对象HashCode:" + container.Resolve<IClass>().GetHashCode()); Console.WriteLine("第一次调用RegisterInstance注册的对象HashCode:" + container.Resolve<IClass>("ccl").GetHashCode()); Console.WriteLine("第二次调用RegisterInstance注册的对象HashCode:" + container.Resolve<IClass>("ccl").GetHashCode()); Console.WriteLine("-------ContainerControlledLifetimeManager End------"); }
运行之后都会发现实例的哈希码是一样的,说明是单实例的
如果用此生命周期管理器,则要在配置文件中新增的节点如下:
<register type="IClass" mapTo="MyClass" name="ccl"> <lifetime type="singleton" /> </register>
注入代码与上例类似,这里不再列出
三、ASP.NET MVC与Unity
说了这么多Unity,主要还是想将其用到ASP.NET MVC的IOC中,其实很简单,大概就几个步骤搞定:
1. 实现IDependencyResolver接口并通过DependencyResolver.SetResolver告知MVC,将部分类型实例解析工作交由IoC容器Unity来处理
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Microsoft.Practices.Unity; namespace UnityOfMVC.IOC { public class UnityDependencyResolver : IDependencyResolver { IUnityContainer container; public UnityDependencyResolver(IUnityContainer container) { this.container = container; } public object GetService(Type serviceType) { if (!this.container.IsRegistered(serviceType)) { return null; } return container.Resolve(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { return container.ResolveAll(serviceType); } } }
2、继承DefaultControllerFactory,重载GetControllerInstance方法,实现自己的UnityControllerFactory类,并通过IoC容器将之注册为IControllerFactory的实现
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; using System.Web.SessionState; namespace UnityOfMVC.IOC { public class UnityControllerFactory : DefaultControllerFactory { IUnityContainer container; public UnityControllerFactory(IUnityContainer container) { this.container = container; } protected override IController GetControllerInstance(RequestContext reqContext, Type controllerType) { return container.Resolve(controllerType) as IController; } } }
3、让我们开始弄一下配置文件
<configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" /> </configSections> <unity> <containers> <container name="defaultContainer"> <register type="UnityOfMVC.Models.IStudentRepository, UnityOfMVC" mapTo="UnityOfMVC.Models.StudentRepository, UnityOfMVC"/> <register type="System.Web.Mvc.IControllerFactory, System.Web.Mvc" mapTo="UnityOfMVC.IOC.UnityControllerFactory, UnityOfMVC"/> </container> </containers> </unity>
4、用引导类Bootstrapper进行初始化工作
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Configuration; using System.Web.Mvc; using Microsoft.Practices.Unity; using Microsoft.Practices.Unity.Configuration; using UnityOfMVC.IOC; namespace UnityOfMVC.BootStrapper { public class Bootstrapper { public static IUnityContainer Init() { var container = BuildUnityContainer(); DependencyResolver.SetResolver(new UnityDependencyResolver(container)); return container; } private static IUnityContainer BuildUnityContainer() { var container = new UnityContainer(); UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName); configuration.Configure(container, "defaultContainer"); return container; } } }
5、在函数Application_Start() 中进行真正的初始化工作
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth();
BootStrapper.Bootstrapper.Init(); //就是这个东东
} }
6、现在在你的MVC程序中注入依赖代码就ok了
(1)首先声明一个Student学生类
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace UnityOfMVC.Models { public class Student { public int Id { get; set; } public string Name { get; set; } public string Graduation { get; set; } public string School { get; set; } public string Major { get; set; } } }
(2)然后声明仓储接口和其实现
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace UnityOfMVC.Models { public interface IStudentRepository { IEnumerable<Student> GetAll(); Student Get(int id); Student Add(Student item); bool Update(Student item); bool Delete(int id); } }
(3)最后添加控制器StudentController,并注入依赖代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using UnityOfMVC.Models; namespace UnityOfMVC.Controllers { public class StudentController : Controller { readonly IStudentRepository repository; //构造器注入 public StudentController(IStudentRepository repository) { this.repository = repository; } public ActionResult Index() { var data = repository.GetAll(); return View(data); } } }
(4)最后为控制器StudentController的Index方法添加视图即可,这里不再详述,运行效果如下: