我有一个装配解决问题让我疯狂。
所以我公司的产品可以有很多插件服务。我的团队致力于这些服务。我们在实用程序集(Utility.dll)中有一堆可重用的代码。
我们不时会对Utility.dll进行改进或添加新功能。我们有非常严格的向后兼容性测试,以确保该组件的新版本适用于所有旧的插件服务。
我遇到的问题是解决当前部署的Utility.dll版本。
让我举个例子。
Utility.dll目前版本为1.0.0.0
我们通过引用Utility.dll版本1.0.0.0
创建一个ServiceA服务然后我们更新Utility.dll并将版本更新为2.0.0.0
接下来,我们创建一个使用Utility.dll 2.0.0.0的服务ServiceB。
Utility.dll版本2.0.0.0与ServiceA和ServiceB兼容。
我启动了我们的应用程序。部署的Utility.dll版本已加载。
我启动了ServiceB。我的AppDomain.CurrentDomain.AssemblyResolve事件的实现被触发,并返回Utility.dll 2.0.0.0。
然后我启动了ServiceA。 AppDomain.CurrentDomain.AssemblyResolve永远不会被触发,但是我得到了一个针对Utility.dll 1.0.0.0的FileNotFoundException。此时我想用Utility 2.0.0.0解决。如果找不到版本1.0.0.0,为什么AssemblyResolve事件不会被触发?
此外,我可以通过首先启动ServiceA并以相反的顺序执行此操作,并且解决了AssemblyResolve并解决了ServiceA(最初使用Utilty.dll 1.0.0.0构建)的Utilty.dll 2.0.0.0。但是,如果我然后启动ServiceB(使用Utilty.dll 2.0.0.0构建),则AssemblyResolve事件永远不会被触发,并且对于Utility.dll版本1.0.0.0会抛出FileNotFoundException。
发生了什么事?我只是想将当前部署的Utility.dll版本用于所有服务。
****** ******修订
public class UtilityLoader
{
private IServiceContext context;
public UtilityLoader(IServiceContext context)
{
this.context = context;
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
private bool Loaded
{
get
{
context.Application.Log.WriteInfo("Checking for Utility...");
Assembly asmFound = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(asm => asm.FullName.Contains("Utility"));
context.Application.Log.WriteInfo(string.Format("Utility {0} loaded.{1}", asmFound == null ? "is not" : "is", asmFound == null ? string.Empty : string.Format(" Version: {0}", asmFound.GetName().Version)));
return asmFound != null;
}
}
public bool Load()
{
if (Loaded)
return true;
string utilityPath = Path.Combine(Session.DataDirectory, "Utility.dll");
if (File.Exists(utilityPath))
{
context.Application.Log.WriteInfo(string.Format("Utility.dll was found."));
FileStream stream = File.OpenRead(utilityPath);
byte[] assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
stream.Close();
try
{
Assembly.Load(assemblyData);
}
catch (Exception ex)
{
context.Application.Log.WriteInfo(string.Format("Could not load Utility: {0}", ex.Message));
throw;
}
return true;
}
return false;
}
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
AssemblyName asmName = new AssemblyName(args.Name);
if (asmName.Name == "Utility")
{
context.Application.Log.WriteInfo("Resolving Utility");
Assembly nuAsm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(asm => asm.FullName.Contains("Utility"));
context.Application.Log.WriteInfo(string.Format("Utility {0} already loaded.", nuAsm == null ? "is not" : "is"));
return nuAsm;
}
return null;
}
}
然后我在每个服务插件中都这样调用它。
public void Execute(IServiceContext context, ServiceParameters serviceParams)
{
UtilityLoader loader = new UtilityLoader(context);
if (!loader.Load())
{
MessageBox.Show("Utility not loaded.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
StartService(context, serviceParams);
}
第一个被调用的服务插件以及任何其他服务插件都是可以加载的,它引用了与加载的第一个服务插件相同的Utility.dll版本。如果调用的服务插件是使用不同版本的Utility.dll构建的,则会抛出FileLoadException。
另一个例子
使用Utility 1.0.0.0构建的ServiceA ServiceB使用Utility 1.0.0.0构建 ServiceB使用Utility 2.0.0.0构建
部署了Utility 2.0.0.0。
用户首先启动ServiceA。已加载Utility 2.0.0.0。 AssemblyResolve事件被触发。大会已经解决。服务启动。
用户在同一会话中启动ServiceB。服务B使用与ServiceA相同版本的Utility。服务B启动。
用户在同一会话中启动ServiceC。 ServiceC是使用不同版本的Utility构建的。 ServiceC无法加载。
用户重新启动应用程序并尝试首先加载ServiceC。 ServiceC加载正常。然后,在同一会话中,用户尝试贷款ServiceA或ServiceB,两者都失败。
由于某种原因,在初始服务之后加载服务时,即使无法实际解析程序集,它也不会再次触发AssemblyResolve。如果事件被触发,我的处理程序将返回当前加载的Utility版本。
答案 0 :(得分:1)
很难知道什么是没有更多细节(服务A和服务B独立.dll?,或者它们是在同一个.exe程序集中?)但是,我会以这种方式实现:
1)我将仅使用AppDomain.CurrentDomain.AssemblyResolve机制进行故障检测。但我会尝试先使用.Net AppDomain逻辑来获取它。
MyDomain.AssemblyResolve += new ResolveEventHandler(Loader_AssemblyResolve);
if (System.IO.File.Exists(pathToAssembly) == false)
{
System.IO.File.Copy(KnownPathToLastVersion, pathToAssembly)
}
_assembly = Assembly.Load(AssemblyName.GetAssemblyName(pathToAssembly));
2)在我的Loader_AssemblyResolve中,我会使用我曾经从网页上学到的最着名的实践,但是现在,我很遗憾地寻找URL(欢迎贡献!):
private Assembly Loader_AssemblyResolve(object sender, ResolveEventArgs args)
{
Assembly assembly = null;
//1. Disconnect the event AssemblyResolve
_Domain.AssemblyResolve -= new ResolveEventHandler(Loader_AssemblyResolve);
try
{
//2. Do not try to get the file without looking first
// in memory. AssemblyResolve could fire even when the
// Assembly is already loaded
assembly = System.Reflection.Assembly.Load(args.Name);
if (assembly != null)
{
_Domain.AssemblyResolve += new ResolveEventHandler(Loader_AssemblyResolve);
return assembly;
}
}
catch
{ // ignore load error
}
//3. Then try to get it from file
string FileName=GetFileName(args.Name);
try
{
assembly = System.Reflection.Assembly.LoadFrom(FileName);
if (assembly != null)
{
_Domain.AssemblyResolve += new ResolveEventHandler(Loader_AssemblyResolve);
return assembly;
}
}
catch
{ // ignore load error
}
//Be sure to reconnect the event
_Domain.AssemblyResolve += new ResolveEventHandler(Loader_AssemblyResolve);
return assembly;
}
我希望它可以帮到你。