使用Fusion解析未引用的部分类型名称
最近,我的任务是编写一个数据提取/报告框架,该框架将使客户端应用程序能够定义要执行的存储过程,并将结果通过电子邮件发送给感兴趣的各方。使该项目变得有趣的是允许用户从一组查找表,Web服务数据和应用程序派生数据中指定参数值的要求。在没有任何外部引用的情况下,检索Web服务或应用程序派生数据的要求需要一种机制来执行未引用程序集中的方法。
本文概述了我的方法,该方法允许框架代码通过部分类型名解析来执行未引用程序集中的方法。
问题
查找表数据的预填充非常简单-从表中选择Id和Value字段-这是通过一个简单的ParameterLookup表实现的。但是,当有时间用Web服务数据或特定于应用程序的数据预先填充查找时,我们需要某种机制来执行客户端应用程序中的方法。在没有对客户端应用程序的任何引用的情况下,我们需要一种机制来解析和执行客户端应用程序代码。我们实现的方法是LookupClass在ParameterLookup表中包含一个字段,并让框架代码解析类型并执行该search方法。
有许多常用的方法允许.NET代码解析和执行未引用类型的方法,尽管并非所有方法都适用于所有环境。当宿主进程是COM+进程时,Type.GetType()可以将调用与部分限定的类型名称(即MyCompany.MyApplication.Search.Lookup.SomeLookup,MyCompany.MyApplication)一起使用。但是,当主机进程是IIS时,必须指定完全限定的类型名称(“类型名称”,“程序集名称”,“版本”,“区域性”,“公钥”),或者该程序集必须存在于Web程序包的/bin目录中。
不希望ParameterLookup每次执行新版本的应用程序时都强制客户端应用程序更新表,我们需要一种仅使用Type和Assembly名称部分的机制。
解决方案
如果您没有完全限定的类型名称(类型名称,程序集类型,版本,区域性和公共密钥),则在未引用的程序集中定义的类型可以是正确的PITA。
例如,类型Microsoft.Build.BuildEngine.Engine类型的完全限定名称为Microsoft.Build.BuildEngine.Engine,Microsoft.Build.Engine,Culture=neutral,Version=4.0.0.0,PublicKeyToken=b03f5f7f11d50a3a(真是令人讨厌!)。对于相对稳定的组件,可以在配置表/文件中指定完全限定的类型名称,但是对于版本号更改为相对不稳定的不稳定组件(例如,来自客户端应用程序的组件),您该怎么做?每个版本?
Type.GetType()
该Type.GetType()功能可用于加载在项目中引用或在程序集搜索路径中的程序集。
Type t = Type.GetType("MyCompany.MyApplication.Lookup.SomeLookup, MyCompany.MyApplication "); 1复制代码类型:[html]
由于框架代码不会引用该程序集,因此我们唯一的希望是该程序集位于程序集搜索路径中。尽管这种情况是可能的,但肯定不能保证。
该解决方案必须丢弃。
如果我们为Type.GetType()函数提供完全限定的类型名称该怎么办?
Type t = Type.GetType("MyCompany.MyApplication.Lookup.SomeLookup, MyCompany.MyApplication, Culture=neutral, Version=1.0.0.3245, PublicKey= 1e9a8d893e3afa78");12复制代码类型:[html]
此调用肯定有效,并且类型引用已成功返回。所需要做的就是将此完全合格的类型传递给框架代码-相对简单的练习。las,当重新编译程序集时,将使用不同的内部版本号(假定您使用的是内部版本号)创建该组件的新版本,该调用将返回原始程序集;否则,将返回原始程序集。不是更新的程序集。这是不理想的。
该解决方案必须丢弃。
我需要的是一种无需指定版本号或无需依靠程序集搜索路径中的程序集即可解决类型的方法。
Assembly.LoadFrom()
该Assembly.LoadFrom()方法看起来很有希望,因为我可以使用此函数来加载程序集而无论其位置如何,并遍历该程序集中定义的每种类型。
internal static Type GetTypeFromAssembly(string assemblyName, string typeName) { Assembly assembly = Assembly.LoadFrom(gacPath); Type[] types = assembly.GetTypes(); foreach (Type type in types) { if (type.FullName == typeName)) return type; } return null; }123456789101112复制代码类型:[html]
条件if(type.FullName==typeName)使我们不必指定完全限定类型的版本,区域性或公钥属性。现在,我们可以加载程序集并提取适当的类型以供使用。
但是,这导致了代码和安装实现之间的依赖性。如果支持操作员决定修改应用程序安装参数,则可以将软件安装到未知位置。可以想象,我们可能要求将软件安装到特定位置(例如ProgramFiles),但这会导致32位/64位问题(ProgramFiles是指“ProgramFiles”还是“ProgramFiles(x86)”?)。
但我想我快到了。
融合
如果将拥有我们所要类型的程序集安装到全局程序集缓存中,则我们可以利用CLR程序集管理系统为我们加载并找到我们的程序集。
使用Fusion.dll程序集(有关详细信息,请参阅此链接),我们可以创建对适当的GAC(32位或64位)的引用,并使用该Assembly.LoadFrom()方法安全地加载我们的程序集。
[DllImport("fusion.dll")] internal static extern int CreateAssemblyCache(out IAssemblyCache ppAsmCache, int reserved);12复制代码类型:[html]
private static string GetAssemblyPath(string name) { if (name == null) throw new ArgumentNullException("name"); string finalName = name; AssemblyInfo aInfo = new AssemblyInfo(); aInfo.cchBuf = 1024; // should be fine... aInfo.currentAssemblyPath = new String('\0', aInfo.cchBuf); IAssemblyCache ac; int hr = CreateAssemblyCache(out ac, 0); if (hr >= 0) { hr = ac.QueryAssemblyInfo(0, finalName, ref aInfo); if (hr < 0) return null; } return aInfo.currentAssemblyPath; }123456789101112131415161718192021复制代码类型:[html]
public static Type ResolveType(string assemblyName, string typeName) { string gacPath = GetAssemblyPath(assemblyName); return GetTypeFromAssembly(assemblyName, typeName); }12345复制代码类型:[html]
仅使用类型和程序集的名称,代码将搜索适合于应用程序环境(x86/x64)的GAC目录,并返回对该类型的引用。引用正确的类型后,对的调用Activator.CreateInstance()将创建要使用的可用类型。
使用代码
框架定义了要解析的类型必须实现的接口。
namespace FrameworkApp { public interface ILookup { string Search(); } }1234567复制代码类型:[html]
...当类实现接口时。
public class SearchType1 : ILookup { public string Search() { return string.Format("SearchType1.Search() from version {0}", Assembly.GetExecutingAssembly().GetName().Version); } }12345678复制代码类型:[html]
现在,我们只需要调用TypeResolver.ResolveType(),创建一个返回类型的实例(强制进入选择的接口),最后执行所需的方法。
Type type = TypeResolver.ResolveType("ClientApp.SearchType1,ClientApp"); ILookup lookup = Activator.CreateInstance(type) as ILookup; lookup.Search();123复制代码类型:[html]
局限性
程序集必须在GAC中注册才能使此解决方案生效
不会加载不兼容的.NET版本程序集
将解析为最高版本,而不是最新编译的版本