本文演示Enterprise Library – Unity Application Block依赖注入模块的使用。Unity是微软Pattern & Practices团队设计和开发的一个轻量级的、可扩展的依赖注入(Dependency Injection)容器,提供了构造器、属性和方法调用的注入。
(1)简化了对象建立,特别是对于层次化的对象结构和依赖,简化了代码。
(2)支持需求的抽象;允许开发者在运行时或者配置文件中指定依赖,简化了软件开发中所关注问题的管理。
(3)通过使用容器延缓组件的配置,提高了灵活性。
(4)具有服务定位功能,使得客户端可以对容器进行存储或缓存。这个特性在ASP.NET网站中特别实用,开发者可以将整个容器存储在Session或Application中。
本文练习使用Unity Container创建应用程序对象,并连接在一起。将调用类的构造函数和设置属性值常用方法替换为调用正确配置的Unity Container。本文由http://blog.entlib.com 开源ASP.NET博客平台小组根据EntLib HOL手册编译提供,欢迎交流。
练习一:使用Unity Container
首先,打开Labs\Lab01\begin\StocksTicker 目录下的StockTicker.sln Solution项目文件。
运行范例程序,在应用程序界面中,输入股票代码,如GM,然后点击Subscribe按钮,并且选择Refresh复选框,在窗口中将显示所订阅股票代码的最新信息,并且在每次接收到股票信息,刷新界面。如下图所示:
应用程序的默认配置是从MoneyCentralRemote获取股票信息,该站点提供在线的、实时的股票市场数据。Console界面显示获取更新股票信息的日志,ui.log文件(在StocksTicker\bin\Debug目录下)在包含了用户界面的操作信息。
1. 分析范例程序
范例程序基于Model-View-Presenter(MVP)模式创建,关于MVP的更新信息,可以参考MSDN文章。下图显示了范例程序涉及到的类及其相互关系。
StocksTickerForm 实现了用户界面,IStockQuoteService 定义了检索当前股票价格的服务接口,MoneyCentralStockService则是该接口的具体实现。ILogger接口及其具体实现用来记录操作日志,最后StocksTickerPresenter 完成显示的角色。
在启动用户界面之前,静态方法Program.Main() 按顺序创建所有涉及的对象,并相互连接。本文的目标是使用Unity Container代替显式地创建这些对象。
2. 添加对必要程序集的引用
对StockTicker项目,添加对Microsoft.Practices.Unity和Microsoft.Practices.ObjectBuilder2程序集的引用。
3. 更新Startup代码,使用Unity Container
打开Program.cs文件,添加对Unity 命名空间的引用。
using Microsoft.Practices.Unity;
使用 UnityContainer对象的Resolve方法创建StocksTickerPresenter对象实例,来代替创建view、presenter、service、logger对象实例的代码,代码如下所示。
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// TODO use a container to create the objects here
using (IUnityContainer container = new UnityContainer())
{
StocksTickerPresenter presenter
= container.Resolve<StocksTickerPresenter>();
Application.Run((Form)presenter.View);
}
Application.Run((Form)presenter.View);
}
Unity Container实现了IDisposable接口,因此上述代码利用using语句来释放container对象。
如果你现在运行范例程序,将抛出ResolutionFailedException异常,这表示试图解析StocksTickerPresenter失败。
下面使用REgisterType方法增加container解析接口所需要的类型映射,代码如下所示。
using (IUnityContainer container = new UnityContainer())
{
container
.RegisterType<IStocksTickerView, StocksTickerForm>()
.RegisterType<IStockQuoteService, MoneyCentralStockQuoteService>();
StocksTickerPresenter presenter
= container.Resolve<StocksTickerPresenter>();
Application.Run((Form)presenter.View);
}
RegisterType方法是用来设置container的主要机制,可用来映射抽象类型到具体的类,也可以用来重载默认的注入规则和指定生命周期管理器(lifetime manager)。
上述代码中没有ILogger接口的映射,目前还不是必须的,因为属性还没有注入,除非显式配置。现在,范例程序可以正常运行了,因为涉及的对象都可以由container解析出来,但是没有记录日志,因为logger没有注入到待解析的对象中。
现在运行范例程序,范例程序可以正常工作,除了日志信息没有出现在console窗口和ui.log文件中。
4. 使用Attributes控制注入
Attributes可以用来重载默认的注入规则。在某些情况下,Attributes可以用来选择按照默认规则会忽略的成员注入。
使用Attributes进行属性注入
通常情况下,我们希望使用container设置属性,此外,通过构造器传入依赖。
为MoneyCentralStockQuoteService类的Logger属性设置注入
(1)打开StockQuoteServices\MoneyCentralStockQuoteService.cs文件
(2)添加对Unity命名空间的引用
using Microsoft.Practices.Unity;
(3)为Logger属性添加Dependency attribute,代码如下所示。
private ILogger logger;
[Dependency]
public ILogger Logger
{
get { return logger; }
set { logger = value; }
}
Dependency attribute 表示该属性类型应该注入,在container中解析类型为ILogger。
(4)添加ILogger类型注册,实现ILogger接口解析
打开Program.cs文件,使用container类的RegisterType方法,实现ILogger接口到ConsoleLogger类的映射,代码如下所示。
using (IUnityContainer container = new UnityContainer())
{
container
.RegisterType<IStocksTickerView, StocksTickerForm>()
.RegisterType<IStockQuoteService, MoneyCentralStockQuoteService>()
.RegisterType<ILogger, ConsoleLogger>();
StocksTickerPresenter presenter
= container.Resolve<StocksTickerPresenter>();
Application.Run((Form)presenter.View);
}
5. 运行范例程序
现在运行范例程序,服务调用的操作日志将记录到console窗口,但是用户界面的操作则没有记录到console窗口,因为presenter类的Logger属性仍就没有注入。
设置注入StocksTickerPresenter类的Logger属性
与前面的操作一样,对presenter类同样适用Dependency attribute,然后因为注入一个不同的logger实例,Unity container支持多次注册相同的类型,但每一次有不同的名称。当解析依赖时,根据指定的不同名称,返回不同的实例。
(1)打开UI\ StocksTickerPresenter.cs文件,添加对Unity命名空间的引用。
(2)对Logger属性添加Dependency attribute,命名为UI,代码如下所示。
(3)使用UI名称添加类型注册,解析ILogger接口。
打开Program.cs 文件,使用RegisterType方法映射ILogger接口到TraceSourceLogger类,传入UI名称,代码如下所示。
using (IUnityContainer container = new UnityContainer())
{
container
.RegisterType<IStocksTickerView, StocksTickerForm>()
.RegisterType<IStockQuoteService, MoneyCentralStockQuoteService>()
.RegisterType<ILogger, ConsoleLogger>()
.RegisterType<ILogger, TraceSourceLogger>("UI");
StocksTickerPresenter presenter
= container.Resolve<StocksTickerPresenter>();
Application.Run((Form)presenter.View);
}
如果现在运行范例程序,在解析presenter的Logger属性为TraceSourceLogger实例是,将抛出异常。沿着InnerExpceptions链,找到最终的异常信息为InvalidOperationExpceiton - The type TraceSourceLogger has multiple constructors of length 1. Unable to disambiguate。这是因为Unity默认的注入规则不能识别如何构建对象实例,因此container必须显式配置。
(4)使用InjectionConstructor attribute显式指定创建TraceSourceLogger对象实例使用的构造函数
打开Loggers\TraceSourceLogger.cs文件,添加对Unity命名空间的引用。
using Microsoft.Practices.Unity;
添加InjectionConstructor attribute到TraceSourceLogger其中之一的构造函数上,该构造函数接收唯一参数TraceSource,代码如下所示。
[InjectionConstructor]
public TraceSourceLogger(TraceSource traceSource)
{
this.traceSource = traceSource;
}
5. 添加实例注册,来解析TraceSource类型
在上一步指定构造函数,Unity默认的注入规则将使用该构造函数,且解析.NET Framework TraceSource对象实例,并作为参数传入构造函数。因为TraceSource是.NET Framework内置的类,不能添加attrubite,因此将通过代码自行创建对象实例,并提供给container。
(1)打开Program.cs文件,使用RegisterInstance方法,指示当解析TraceSource类时,将返回的对象实例,示例代码如下所示。
using (IUnityContainer container = new UnityContainer())
{
container
.RegisterType<IStocksTickerView, StocksTickerForm>()
.RegisterType<IStockQuoteService, MoneyCentralStockQuoteService>()
.RegisterType<ILogger, ConsoleLogger>()
.RegisterType<ILogger, TraceSourceLogger>("UI")
.RegisterInstance(new TraceSource("UI", SourceLevels.All));
StocksTickerPresenter presenter
= container.Resolve<StocksTickerPresenter>();
Application.Run((Form)presenter.View);
}
现在运行范例程序,可以正常工作了,UI操作日志将记录到ui.log文件中,同时来自service的消息显示在console窗口中。
Attributes是一个方便的机制来重载或消除歧义container的默认注入规则,但是会导致脆弱的依赖,特别是涉及到Dependency名称,因为这样需要在源代码中进行硬编码。下一节将演示其他可选的机制,将container的设置外部化,不必牵扯到所需创建的对象。
http://www.entlib.com专业ASP.NET电子商务平台小组,欢迎你继续访问Unity Application Block学习手册。
参考文档:
Unity Application Block Hands-On Labs for Enterprise Library