ASP.NET Web Froms开发模式中实现程序集的延迟加载
延迟加载是一个很大的诱惑,可以达到一些比较好的效果,比如:
1、在实体框架中,由于关联数据的数量和使用时机是不确定的,通过延迟加载,仅在使用的时候去执行关联数据的查询操作,减少无谓的数据查询操作,可以降低单次数据查询执行的时间,提升系统的性能。
2、在一个插件平台中启动平台时只加载必需的程序集,当执行到具体插件时再加载插件相关的程序集,仅在需要的时候加载资源,可以减少插件平台的启动时间,使内存的占用更合理些。
延迟加载可以使资源的占用更加合理,并提升一定的性能,当然也有一些例子来说明延迟加载的坏处,这就需要根据实际的情况去考量,不是这篇文章的目的。
言归正传,在ASP.NET Web Forms开发模式中,程序集一般都放到bin目录下,或者在web.config中通过配置codebase或者probing节点指定程序集目录,应用程序启动时会从这些位置自动加载程序集。我们要使用延迟加载,就不能将程序集放到这些地方,将需要延迟加载的程序集放到一些有规则可循的目录是一种比较好的方式。比如:
root
|–bin
|–lazyload
| |–bin1
| |–bin2
将这些程序集都放到一个lazyload的目录中,然后在其中根据程序集的划分建立不同的子目录,根据需要去不同的目录中加载程序集。
那么使用什么方法加载程序集呢?
Assembly类提供了几个静态方法:Load、LoadFile、LoadFrom,可以通过这几个方法将dll文件加载到当前应用程序域的程序集中。
关于这几个方法如何选择,网上有一些总结,这里不做讨论。以下是一些总结:
http://www.cnblogs.com/xuqingfeng/archive/2012/05/22/assembly-load-loadfrom-loadfile-details.html
http://msdn.microsoft.com/zh-cn/library/dd153782(v=vs.110).aspx
实现程序集的延迟加载需要扩展两个地方:
1、依赖程序集的延迟加载
通过订阅当前应用程序域的AssemblyResolve事件,应用程序域在加载依赖程序集时如果找不到就会触发这个事件。
在这个事件中我们可以通过一些规则找到需要加载的程序集文件,然后通过Assembly的加载方法加载到内存,并返回。
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; |
private Assembly CurrentDomain_AssemblyResolve( object sender, ResolveEventArgs args) |
{ |
Assembly assembly = null ; |
//加载程序集部分省略 |
return assembly; |
} |
2、页面动态编译所需程序集的延迟加载
aspx页面在首次访问时会进行编译,编译时需要页面绑定的类所在的程序集。默认情况下这些程序集是在程序启动的时候自动加载的,从.net4开始,微软提供了一个应用程序启动的扩展支持System.Web.PreApplicationStartMethod,也可以在这里通过程序加载程序集,但还是达不到延迟加载的效果。
aspx页面的编译是通过BuildManager实现的,调用BuildManager.AddReferencedAssembly方法可以添加程序集,但是这个方法只能在上边提到的扩展支持中调用,程序启动后再调用就会抛出异常(可能是.net4.0还有些东西没协调好),此路不通。既然不能通过方法添加,那是不是可以直接加入到BuildManager的程序集集合中,很不幸全是私有的,有兴趣的可以自己反编译看看。
私有的其实也不是没有办法可以获取,使用反射,还好BuildManager有一个静态的属性TheBuildManager,通过反射获取这个属性的值就可以得到内部的BuildManager实例,修改程序集的集合就不成问题了。
// 获取BuildManager的实例 |
PropertyInfo buildmanagerProperty = Type.GetTypeFromHandle( typeof (BuildManager).TypeHandle).GetProperty( "TheBuildManager" , BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetProperty); |
BuildManager buildmanager = buildmanagerProperty.GetValue( null , null ) as BuildManager; |
// 获取TopLevelReferencedAssemblies |
PropertyInfo topLevelReferencedAssembliesProperty = Type.GetTypeFromHandle( typeof (BuildManager).TypeHandle).GetProperty( "TopLevelReferencedAssemblies" , BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty); |
IList assemblies = topLevelReferencedAssembliesProperty.GetValue(buildmanager, null ) as IList; |
// 添加程序集 |
Assembly assembly = null ; |
//加载程序集部分省略 |
assemblies.Add(assembly); |
这段程序要在页面编译之前调用,比如PageHandlerFactory的GetHandler方法中。
通过这两个扩展基本上就可以实现程序集的延迟加载了,能用来干什么就要看自己了。博客园有个人搞了个OSGI.NET,就用到文中的两个方法。
当然上边只是初步给出了解决问题的方法,如果要实际使用,可能要考虑更多的问题,比如多线程同步问题、程序集多版本问题等等,有兴趣的可以写写看。
本人独立博客地址:http://blog.bossma.cn/dotnet/asp-net-how-to-lazy-load-assembly/
转载请注明出处。