簡介:使用HttpHandlerFactory對ASP.NET Webform的頁面進行依賴注入,不僅僅是Unity,使用同樣的思路也可以用Spring.NET
背景
在日常的開發中,特別是使用了多層結構的程序,在視圖層的頁面邏輯中時常會用到業務邏輯的對象,此時就有可能產生如下的代碼
{
public IUser UserService
{
get;
set;
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
此代碼的問題在於,沒有一個適當的地方初始化User對象,雖然可以藉助工廠在Page_Load中加入初始化的邏輯,但是每一個頁面都需要手動地初始化對象顯然並不合適
而在MVC模式下,在Controller中經常會用到依賴注入的方式將User對象注入,例如JAVA中的Structs和Webworks都可以配合Spring進行注入,而新出的ASP.NET MVC Framework也有相關的配合Unity進行依賴注入的技術文章
但是對於Webform,因爲整個頁面的執行相對封閉,沒有很好的擴展環節,使注入顯得不是那麼容易
此文將使用自定義的IHttpHandlerFactory,在頁面的生成時期使用Unity進行依賴注入,在不影響系統的運行的前提下,無侵入性地完成此項工作
原理
在ASP.NET的執行週期中,在經過了HttpModule之後,會由HttpHandlerFactory生成具體的Handler,隨後進行Handler的生命週期,因此如果需要對同是HttpHandler的Page進行依賴注入,就需要使用HttpHandlerFactory的GetHandler方法生成完成注入後的Page對象
原本ASP.NET中用於生成Page對象的HttpHandlerFactory叫PageHandlerFactory,此類的構造函數被隱藏了,因此需要使用Activator來生成對象,再從PageHandlerFactory的對象中生成原有的Page對象
在獲取了Page對象之後,我們就可以使用Unity爲其進行依賴注入,所幸的是Unity提供了BuildUp方法來對已經生成的對象進行注入
當然其間還是有不少需要注意的問題,在實現環節中一一說明
using System;
using System.Web;
using System.Web.UI;
using System.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
namespace MyDataUtilyTest
{
//此類實現原理
/// <summary>
/// 在ASP.NET的執行週期中,在經過了HttpModule之後,會由HttpHandlerFactory生成具體的Handler,
/// 隨後進行Handler的生命週期,因此如果需要對同是HttpHandler的Page進行依賴注入,
/// 就需要使用HttpHandlerFactory的GetHandler方法生成完成注入後的Page對象
/// 原本ASP.NET中用於生成Page對象的HttpHandlerFactory叫PageHandlerFactory,
/// 此類的構造函數被隱藏了,因此需要使用Activator來生成對象,
/// 再從PageHandlerFactory的對象中生成原有的Page對象
/// 在獲取了Page對象之後,我們就可以使用Unity爲其進行依賴注入,
/// 所幸的是Unity提供了BuildUp方法來對已經生成的對象進行注入
/// </summary>
public class UnityHttpHandlerFactory : IHttpHandlerFactory
{
//需要初始化一個UnityContainer,這裏使用靜態變量
private static readonly IUnityContainer unityContainer;
static UnityHttpHandlerFactory()
{
//初始化UnityContainer
string containerName = ConfigurationManager.AppSettings["HttpHandlerUnityContainerName"];
if (String.IsNullOrEmpty(containerName))
{
containerName = "HttpHandlerContainer";
}
unityContainer = new UnityContainer();
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers[containerName].Configure(unityContainer);
}
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
IHttpHandlerFactory pageFactory = CreatePageFactory();
IHttpHandler pageHandle = pageFactory.GetHandler(context,requestType,url,pathTranslated);
pageHandle = Build(pageHandle);
return pageHandle;
}
public void ReleaseHandler(IHttpHandler handler)
{
IHttpHandlerFactory pageFactory = CreatePageFactory();
pageFactory.ReleaseHandler(handler);
}
/// <summary>
/// 在GetHandler方法中,取得具體的Page對象後,使用了一個Build方法對其進行包裝,
/// </summary>
/// <param name="httpHandler"></param>
/// <returns></returns>
private static IHttpHandler Build(IHttpHandler httpHandler)
{
//Build方法使用UnityContainer的BuildUp方法對Page對象進行注入,這裏有2點需要注意
//注意1.因爲並不能保證每一個Page都在UnityContainer中有註冊,所以此處的BuildUp並不保證成功,
//需要用catch捕獲BuildUp過程中拋出的異常,如果BuildUp失敗,則返回原有的實例即可
//注意2.在ASP.NET運行期間,PageHandlerFactory生成的是編程時相應Page的子類,所以在
//UnityContainer中尋找註冊的類型的時候,需要使用page.GetType().BaseType纔可
//
try
{
return unityContainer.BuildUp(httpHandler.GetType().BaseType,httpHandler) as IHttpHandler;
}
catch
{
return httpHandler;
}
}
private static IHttpHandlerFactory CreatePageFactory()
{
//簡單地使用Activator構造一個PageHandlerFactory的實例
//在這裏CreateInstance方法給了兩個參數,第二個參數是這樣子的,
//如果公共或非公共默認構造函數可以匹配,則爲 true;如果只有公共默認構造函數可以匹配,則爲 false。
IHttpHandlerFactory pageFactory =
Activator.CreateInstance(typeof(PageHandlerFactory), true) as IHttpHandlerFactory;
if (pageFactory == null)
{
throw new ApplicationException("Unable to initialize 'PageHandlerFactory'");
}
return pageFactory;
}
}
}
爲了保持靈活性,使用了配置文件的方式讀取UnityContainer,默認配置在web.config中
最後的工作當然是將此HttpHandlerFactory加入運行環境,在web.config中system.web配置組下的httpHandlers段加入以下2行即可
<add verb="*" path="*.aspx" type="Cst.Core.Web.Factory.UnityHttpHandlerFactory, Cst.Core.Web"/>
這2行將原有的默認HttpHandlerFactory(即PageHandlerFactory)取消,代替以新的UnityHttpHandlerFactory
問題
1.因爲在Unity中註冊的頁面其實只是用來作爲BuildUp的參考,所以其生命週期管理是Transient好還是Singleton好依舊是個問題,有待更詳細的測試
2.Unity只提供對public的屬性的注入,需要對protected屬性注入還要自己寫擴展
3.只能用屬性注入,因爲Page的構造是由PageHandlerFactory完成的,Unity無力攔截構造函數的注入