解析具有不同版本

时间:2016-09-01 23:21:50

标签: c# .net

我有一个装配解决问题让我疯狂。

所以我公司的产品可以有很多插件服务。我的团队致力于这些服务。我们在实用程序集(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版本。

1 个答案:

答案 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;
    }

我希望它可以帮到你。