自定义IHttpHandlerFactory使用Unity对ASP.NET Webform页面进行依赖注入

背景

在日常的开发中,特别是使用了多层结构的程序,在视图层的页面逻辑中时常会用到业务逻辑的对象,此时就有可能产生如下的代码
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), trueas 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,本人示例中的配置文件如下

<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取对象),万分感谢

你可能感兴趣的:(asp.net)