网上有不少对Unity和ASP.NET MVC Framework结合的示例,但是Webform也不能因为MVC的出现而失去了生命力,此文使用自定义的IHttpHandlerFactory使ASP.NET Webform的页面支持Unity的依赖注入
背景
在日常的开发中,特别是使用了多层结构的程序,在视图层的页面逻辑中时常会用到业务逻辑的对象,此时就有可能产生如下的代码
public partial class Default : System.Web.UI.Page


{
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方法来对已经生成的对象进行注入
当然其间还是有不少需要注意的问题,在实现环节中一一说明
实现
首先自定义一个HttpHandlerFactory,在此称之为
UnityHttpHandlerFactory,其GetHandler方法和ReleaseHandler方法均调用PageHandlerFactory的相应方法,具体如下
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)


{
IHttpHandlerFactory pageFactory = CreatePageFactory();
IHttpHandler page = pageFactory.GetHandler(context, requestType, url, pathTranslated);
page = Build(page);
return page;
}

public void ReleaseHandler(IHttpHandler handler)


{
IHttpHandlerFactory pageFactory = CreatePageFactory();
pageFactory.ReleaseHandler(handler);
}
其中的CreatePageFactory方法如下,简单地使用Activator构造一个PageHandlerFactory的实例
private static IHttpHandlerFactory CreatePageFactory()


{
IHttpHandlerFactory pageFactory =
Activator.CreateInstance(typeof(PageHandlerFactory), true) as IHttpHandlerFactory;
if (pageFactory == null)

{
throw new ApplicationException(
"无法初始化PageHandlerFactory
");
}
return pageFactory;
}
在GetHandler方法中,取得具体的Page对象后,使用了一个Build方法对其进行包装,此方法如下
private static IHttpHandler Build(IHttpHandler page)


{
try

{
return unityContainer.BuildUp(page.GetType().BaseType, page) as IHttpHandler;
}
catch (Exception)

{
return page;
}
}
Build方法使用UnityContainer的BuildUp方法对Page对象进行注入,这里有2点需要注意
注意1.因为并不能保证每一个Page都在UnityContainer中有注册,所以此处的BuildUp并不保证成功,需要用catch捕获BuildUp过程中抛出的异常,如果BuildUp失败,则返回原有的实例即可
注意2.在ASP.NET运行期间,PageHandlerFactory生成的是编程时相应Page的子类,所以在UnityContainer中寻找注册的类型的时候,需要使用page.GetType().BaseType才可
当然在此之前,需要初始化一个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);
}
为了保持灵活性,使用了配置文件的方式读取UnityContainer,默认配置在web.config中,窗口名为HttpHandlerContainer,本人示例中的配置文件如下

Code
<unity>
<typeAliases>
<typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity"/>
</typeAliases>
<containers>
<container name="HttpHandlerContainer">
<types>
<!-- Default页面的依赖 -->
<type type="GDev.Test.WebApp.Default, GDev.Test.WebApp" mapTo="GDev.Test.WebApp.Default, GDev.Test.WebApp">
<lifetime type="singleton" />
<typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration">
<property name="UserService" propertyType="GDev.Test.WebApp.IUser, GDev.Test.WebApp">
<dependency />
</property>
</typeConfig>
</type>
<type type="GDev.Test.WebApp.IUser, GDev.Test.WebApp" mapTo="GDev.Test.WebApp.MyUser, GDev.Test.WebApp">
<lifetime type="singleton" />
</type>
</types>
</container>
</containers>
</unity>
最后的工作当然是将此HttpHandlerFactory加入运行环境,在web.config中system.web配置组下的
httpHandlers段加入以下2行即可
<remove verb="*" path="*.aspx"/>
<add verb="*" path="*.aspx" type="Cst.Core.Web.Factory.UnityHttpHandlerFactory, Cst.Core.Web"/>
这2行将原有的默认HttpHandlerFactory(即PageHandlerFactory)取消,代替以新的UnityHttpHandlerFactory
为了演示具体的效果,在Default的Page_Load方法中加入代码,使方法最终如下
protected void Page_Load(object sender, EventArgs e)


{
if (UserService != null)

{
Response.Write("注入成功!");
}
}
结果当然是如预期地显示出了"注入成功"字样,也就不放图了~
问题
1.因为在Unity中注册的页面其实只是用来作为BuildUp的参考,所以其生命周期管理是Transient好还是Singleton好依旧是个问题,有待更详细的测试
2.Unity只提供对public的属性的注入,需要对protected属性注入还要自己写扩展
3.只能用属性注入,因为Page的构造是由PageHandlerFactory完成的,Unity无力拦截构造函数的注入
总结
本文提供了一种思路,使用HttpHandlerFactory对ASP.NET Webform的页面进行依赖注入,不仅仅是Unity,使用同样的思路也可以用Spring.NET或者Castle进行依赖注入,同样,也可以配合使用PIAB完成更多的功能
最后题外话:哪位老师有Unity的扩展方面的相关教程的请告诉我一下,特别是Inject方面的扩展(比如通过从Session或者QueryString取对象),万分感谢
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!