Unity 实现依赖注入

【ASP.Net MVC3 】使用Unity 实现依赖注入

 

新项目还是要用MVC3,team 计划使用 Unity。看了一下网上的资料,都是比较老的了,官网也没什么好的指引。MVC也在更新,Unity也在更新。花了1天半时间去MSDN,P&P查资料,整理了一下分享给大家。言归正传:


什么是Unity?

Unity是一个轻量级的可扩展的依赖注入容器,支持构造函数,属性和方法调用注入。Unity可以处理那些从事基于组件的软件工程的开发人员所面对的问题。构建一个成功应用程序的关键是实现非常松散的耦合设计。松散耦合的应用程序更灵活,更易于维护。这样的程序也更容易在开发期间进行测试。你可以模拟对象,具有较强的具体依赖关系的垫片(轻量级模拟实现),如数据库连接,网络连接,ERP连接,和丰富的用户界面组件。例如,处理客户信息的对象可能依赖于其他对象访问的数据存储,验证信息,并检查该用户是否被授权执行更新。依赖注入技术,可确保客户类正确实例化和填充所有这些对象,尤其是在依赖可能是抽象的 。

 

如何得到Unity?

您可以访问http://unity.codeplex.com/releases得到最新版本的Unity现在。当然,如果您在您的visual studio 中安装了Nuget 包管理器,你可以直接在Nuget中获取到最新版本的Unity。

 

API

 

UnityContainer.RegisterType<ITFrom,TTO>();

UnityContainer.RegisterType< ITFrom, TTO >();

UnityContainer.RegisterType< ITFrom, TTO >("keyName");

IEnumerable<T> databases = UnityContainer.ResolveAll<T>();

IT instance = UnityContainer.Resolve<IT>();

T instance = UnityContainer.Resolve<T>("keyName");

UnitContainer.RegisterInstance<T>("keyName",new T());

UnityContainer.BuildUp(existingInstance);

IUnityContainer childContainer1 = parentContainer.CreateChildContainer();

 

 

代码举例

在开始之前我们要先做一些准备工作。首先创建一个控制台应用程序。使用Nuget 添加Unity到当前项目中。我们可以发现,dll引用中多了3个dll:Microsoft.Practices.ServiceLocation, Microsoft.Practices.Unity和Microsoft.Practices.Configuation。

示例1:根据接口依赖创建类

 

上边简单介绍了Unity的API。如果在没有注册的情况下Resolve一个类型会发生什么呢?

假设我们需要对日志进行处理。我们先声明一个接口ILogger:

 

  public interface ILogger
{
void Write(string log);

}

 

我们可以有多种方法实现这个接口,我们假设希望写日志到文件中:

复制代码
  public class FileLogger:ILogger
{

#region ILogger Members

public void Write(string log)
{
Console.WriteLine("Write log in file.");
}

#endregion
}
复制代码

我们在实际生活中对数据库的选择也是多种多样的。我们创建一个数据库的基类:

public class Database
{
}

创建一个派生类:

复制代码
public class CustomerDatabase : Database
{
private ILogger _logger;
public CustomerDatabase(ILogger logger)
{
_logger = logger;
}
}
复制代码

注意它的构造器的参数是ILogger类型。首先我们要创建一个Unity 容器:

UnityContainer container = new UnityContainer();

接下来我们需要在容器中注册一种类型,它是一个类型的映射,接口类型是ILogger,我希望返回的类型是FileLogger:

container.RegisterType<ILogger, FileLogger>();

然后我们使用Resolve 方法:

Database database = container.Resolve<CustomerDatabase>();

经过调试我们可以发现,如果在容器中没有注册的类型。执行Resolv方法后,Unity尝试创建该类型,会执行该类的构造器。最后database变量的类型就是CustomerDatabase,而且它的私有字段ILogger的当前实例也为FileLogger。

示例2:类型映射

 

我们希望返回一个日志类的实例,无论它是哪个实现类。我们可以直接在Resolve的类型中指定类型为接口ILogger:

UnityContainer container = new UnityContainer();
container.RegisterType<ILogger, FileLogger>();
ILogger logger = container.Resolve<ILogger>();

每一次 container都会给我们返回一个新的logger实例。

示例3:单例模式的注册

如果我们想告诉Unity,我们想控制生命周期,我们想用单例模式。RegisterType方法包含一个重载,将使用LifetimeManager。每次我们在想获得到database实例时,unity总是会返回第一次我创建的CustomerDatabase。

UnityContainer container = new UnityContainer();
container.RegisterType<Database, CustomerDatabase>
(new ContainerControlledLifetimeManager());

示例4:注册时附带key

在我们向容器里注册时,可以附带一个string 类型的Key值。

UnityContainer container = new UnityContainer();
container.RegisterType<Database, SQLDatabase>("SQL");
container.RegisterType<Database, ORACLEDatabase>("ORACLE");
IEnumerable<Database> databases = container.ResolveAll<Database>();
Database database = container.Resolve<Database>("SQL");

我们分别向容器中注册了名为“SQL“和”ORACLE“的Database。当我们使用ResolverAll方法是。容器会返回容器中所有类型为Database的类。

这时我们如果仅仅想回去SQL的实例,我们可以使用container.Resolve<Database>("SQL");

示例5:注册已经存在的实例

 

通过下边方式我们可以在container中注册一个实例:

  UnityContainer container = new UnityContainer();
container.RegisterInstance<Database>(new SQLDatabase());
container.RegisterInstance<Database>("Oracle", new ORACLEDatabase());
Database database = container.Resolve<Database>();
Database oracleDatabase = container.Resolve<Database>("Oracle");

看起来和上边的方式没什么不同。重要的是,当我们使用RegisterInstance方法时,Unity会注册一个单例。

我们还有一种方法可以把已经存在的实例注入到容器中。

             UnityContainer container = new UnityContainer();
container.RegisterType<ILogger, FileLogger>();
SQLDatabase existDatabase = new SQLDatabase();
container.BuildUp(existDatabase);
container.RegisterInstance<Database>(existDatabase);
Database database = container.Resolve<Database>();

就如上边代码中,我们已经存在一个database 是DB2Database。你希望Unity做的是把依赖注入到容器中。

我们用BuildUp方法告诉Unity我们的想法。这时候Unity回去DB2Database 类,如果他发现了[Dependency]这个特性。他就自动的把我们前边注册的FileLogger注入到DB2Database的Logger字段中。

以下是DB2Database类:

public class DB2Database:Database
{
[Dependency]
public ILogger Logger { get; set; }
}

使用配置文件来实现关系映射

 

我们也可以再web.config里配置 文件的依赖关系映射。首先我打开web.config文件。按照如下结构添加section。这里我只是简单的映射了ILogger 接口和FileLogger。并且指定了生命周期是单例。

复制代码
<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">
<container name="containerOne">
<types>
<type type="UnityDemo_ConsoleApplication.ILogger" mapTo="UnityDemo_ConsoleApplication.FileLogger"
lifeTime
="Singleton"/>
</types>

</container>

</unity>
...
...
</configuration>
复制代码

如果你想更详细的了解元素和属性的使用,可以看以下Unity xml中配置的结构图:



更详细了解,请参见:

http://msdn.microsoft.com/en-us/library/ff647848.aspx

http://msdn.microsoft.com/zh-cn/library/dd203230.aspx

 

如何读取配置 并加载

Unity同样支持我们在配置文件里写设定映射关系。

首先我们要引入命名空间: Microsoft.Practices.Unity.Configuration;

 

在Unity2.0以上版本,已经废弃了以前的方法。现在我们有2种方式可以读取配置。

第一种,我们使用configurationManager:

引用命名空间:System.Configuration

            IUnityContainer myContainer = new UnityContainer();

myContainer.LoadConfiguration("containerOne ");
UnityConfigurationSection section
= (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Configure(myContainer, "containerOne");

第二种,我们可以直接用容器读取配置信息:

IUnityContainer myContainer = new UnityContainer();
myContainer.LoadConfiguration("containerName");

 

通过 Injection API 指定依赖

 

假设我们有一个类GenericDatabase:



 

复制代码
public class GenericDatabase:Database
{
private string _connectionString;
public ILogger Logger { get; set; }

public GenericDatabase(string connectionString)
{
_connectionString = connectionString;
}
}
复制代码

在这里我们要通过Injection API 来为这个类注入connectionString 和 Logger。

首先我们还和前边的一样注册映射关系:

 

            IUnityContainer container = new UnityContainer();
container.RegisterType<ILogger, FileLogger>();
container.RegisterType<Database, GenericDatabase>();

 

然后通过Injection API 为GenericDatabase注入ConnectionStrings和Logger:

复制代码
container.Configure<InjectedMembers>()
.ConfigureInjectionFor<GenericDatabase>(
new InjectionConstructor(
ConfigurationManager.ConnectionStrings["ConnectionStrings"] == null
? "defaultConnectionString" : ConfigurationManager.ConnectionStrings["ConnectionStrings"].ConnectionString),
new InjectionProperty("Logger")
);
Database database = container.Resolve<Database>();
复制代码

这样我们最后获得的database 就包含了connection 和 Logger。

嵌套式容器

容器是可以嵌套的,获取实例时遵循的规则是,如果子容器里不包含需要的对象,则会去父容器获取。如果有,则从自己里获取。

一旦父容器销毁,子容器也随之销毁。

复制代码
UnityContainer parentContainer = new UnityContainer();
IUnityContainer childContainer1 = parentContainer.CreateChildContainer();
IUnityContainer childContainer2 = parentContainer.CreateChildContainer();
parentContainer.RegisterType<ILogger, FileLogger>(new ContainerControlledLifetimeManager());
childContainer1.RegisterType<ILogger, EventLogger>(new ContainerControlledLifetimeManager());
//应该从parentContainer得到FileLogger
ILogger logger = childContainer2.Resolve<ILogger>();
logger.Write("Test");
//应该从自己本身得到eventLogger

ILogger logger2 = childContainer1.Resolve<ILogger>();
复制代码

 

在MVC 中使用Unity注入Controller

 

在MVC2中我们会写一个controlleFactory 继承自DefaultControllerFactory。

并且override GetControllerInstance()这个方法。

MVC3对于依赖注入提供更好的支持。我们可以使用- IDependencyResolver 和 IControllerActivator 来实现对controller的注入。

具体实现如下:

创建一个MVC3项目。

我们要实现MVC3中新提供 的两个接口:IDependencyResolver和IControllerActivator

IDependencyResolver公开两个方法 - GetService的GetServices.The GetService方法解决了单独注册的服务,支持任意对象的创建,GetServices解决注册多个服务。IDependencyResolver接口的实现应该委托给底层的依赖注入容器提供注册服务请求的类型。当有没有注册的服务请求的类型,ASP.NET MVC框架预计这个接口的实现返回GetService为空,并从GetServices返回空集合。让我们以统一提供依赖注入工作IDependencyResolver intreface派生创建一个自定义的依赖解析器类。

我们定义一个类名为UnityDependencyResolver:

复制代码
  public class UnityDependencyResolver : IDependencyResolver
{
IUnityContainer container;
public UnityDependencyResolver(IUnityContainer container)
{
this.container = container;
}

public object GetService(Type serviceType)
{
try
{
return container.Resolve(serviceType);
}
catch
{
return null;
}
}

public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return container.ResolveAll(serviceType);
}
catch
{
return new List<object>();
}
}
}
复制代码

实现两个方法GetService和GetServices。使用Unity容器返回我们需要的Service或者ojbect。

实现两个方法GetService和GetServices。使用Unity容器返回我们需要的Service或者ojbect。

 

ASP.NET MVC 3已经推出了一个新的接口IControllerActivator,让您激活与自定义的行为控制器,并且可以使用依赖注入.让我们创建一个派生自IControllerActivator 接口的一个自定义的控制器

IController IControllerActivator.Create( System.Web.Routing.RequestContext requestContext,
Type controllerType)
{
return DependencyResolver.Current
.GetService(controllerType) as IController;
}

DependencyResolver.Current.GetService会执行我们自己定义的UnityDependencyResolver中的方法。

 

定义好这两个类,我们找到Global.asax.cs,并在其中中添加一个私有方法GetUnityContainer():

复制代码
        private IUnityContainer GetUnityContainer()
{
//Create UnityContainer
IUnityContainer container = new UnityContainer()
.RegisterType<IControllerActivator, CustomControllerActivator>()
.RegisterType<ILogger, FlatFileLogger>();
return container;
}
复制代码

这个方法定义了一个新的容器。并且注册了映射关系。我们要返回的container中包含:CustomControllerActivator和FlatFileLogger。

IUnityContainer container = GetUnityContainer();

DependencyResolver.SetResolver(new UnityDependencyResolver(container));

上边的都做好了。我们在Application_Start方法中添加如下代码:

 

protected void Application_Start()
{
...
IUnityContainer container = GetUnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}

 

首先我们通过GetUnityContainer方法获得container,并且设置当前的Resolver是我们自己实现的UnityDependencyResolver。

在Controller中我们只需要添加一个[Dependency]特性,就可以很方便的获取到我们注入的Logger。

 

复制代码
  public class HomeController : Controller
{
[Dependency]
public ILogger Logger { get; set; }

public ActionResult Index()
{

ViewBag.Message = "Welcome to ASP.NET MVC!";

Logger.GetType();

return View();
}

public ActionResult About()
{
return View();
}
}
复制代码

 

我们可以使用Logger.GetType()查看到,我们当前的Logger就是我们之前注册的FlatFileLogger。

 


依赖注入三种形式

  

具体依赖注入划分为三种形式,即构造器注入、属性(设置)注入和接口注入,习惯将其划分为一种(类型)匹配和三种注入:

  • 类型匹配(Type Matching):虽然我们通过接口(或者抽象类)来进行服务调用,但是服务本身还是实现在某个具体的服务类型中,这就需要某个类型注册机制来解决服务接口和服务类型之间的匹配关系;
  • 构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象;
  • 属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性;
  • 方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。

 

 

我们创建一个控制台程序,定义如下几个接口(IA、IB、IC和ID)和它们各自的实现类(A、B、C、D)。在类型A中定义了3个属性B、C和D,其类型分别为接口IB、IC和ID。其中属性B在构在函数中被初始化,以为着它会以构造器注入的方式被初始化;属性C上应用了DependencyAttribute特性,意味着这是一个需要以属性注入方式被初始化的依赖属性;属性D则通过方法Initialize初始化,该方法上应用了特性InjectionMethodAttribute,意味着这是一个注入方法在A对象被IoC容器创建的时候会被自动调用。

[csharp]  view plain copy
  1. public interface IA { }  
  2.     public interface IB { }  
  3.     public interface IC { }  
  4.     public interface ID { }  
  5.   
  6.     public class A : IA  
  7.     {  
  8.         public IB B { getset; }  
  9.         [Dependency]  
  10.         public IC C { getset; }  
  11.         public ID D { getset; }  
  12.   
  13.         public A(IB b)  
  14.         {  
  15.             this.B = b;  
  16.         }  
  17.         [InjectionMethod]  
  18.         public void Initalize(ID d)  
  19.         {  
  20.             this.D = d;  
  21.         }  
  22.     }  
  23.     public class B : IB { }  
  24.     public class C : IC { }  
  25.     public class D : ID { }  


然后我们为该应用添加一个配置文件,并定义如下一段关于Unity的配置。这段配置定义了一个名称为defaultContainer的Unity容器,并在其中完成了上面定义的接口和对应实现类之间映射的类型匹配。

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <configuration>  
  3.   <configSections>  
  4.     <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/>  
  5.   </configSections>  
  6.   <unity>  
  7.     <containers>  
  8.       <container name="defaultContainer">  
  9.         <register type="UnityDemo.IA,UnityDemo" mapTo="UnityDemo.A, UnityDemo"/>  
  10.         <register type="UnityDemo.IB,UnityDemo" mapTo="UnityDemo.B, UnityDemo"/>  
  11.         <register type="UnityDemo.IC,UnityDemo" mapTo="UnityDemo.C, UnityDemo"/>  
  12.         <register type="UnityDemo.ID,UnityDemo" mapTo="UnityDemo.D, UnityDemo"/>  
  13.       </container>  
  14.     </containers>  
  15.   </unity>  
  16. </configuration>  


最后在Main方法中创建一个代表IoC容器的UnityContainer对象,并加载配置信息对其进行初始化。然后调用它的泛型的Resolve方法创建一个实现了泛型接口IA的对象。最后将返回对象转变成类型A,并检验其B、C和D属性是否是空

[csharp]  view plain copy
  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             UnityContainer container = new UnityContainer();  
  6.             UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection;  
  7.             configuration.Configure(container, "defaultContainer");  
  8.             A a = container.Resolve<IA>() as A;  
  9.             if (null!=a)  
  10.             {  
  11.                 Console.WriteLine("a.B==null?{0}",a.B==null?"Yes":"No");  
  12.                 Console.WriteLine("a.C==null?{0}", a.C == null ? "Yes" : "No");  
  13.                 Console.WriteLine("a.D==null?{0}", a.D == null ? "Yes" : "No");  
  14.             }  
  15.         }  
  16.     }  


从如下给出的执行结果我们可以得到这样的结论:通过Resolve<IA>方法返回的是一个类型为A的对象,该对象的三个属性被进行了有效的初始化。这个简单的程序分别体现了接口注入(通过相应的接口根据配置解析出相应的实现类型)、构造器注入(属性B)、属性注入(属性C)和方法注入(属性D)

  a.B == null ? No
 a.C == null ? No
 a.D == null ? No