2016-09-01 12 views
0

Ich habe ein Montageproblem, das mich verrückt macht.Auflösen einer Baugruppe mit verschiedenen Versionen

So kann das Produkt meines Unternehmens viele Plug-in-Dienste haben. Mein Team arbeitet an diesen Diensten. Wir haben eine Menge wiederverwendbaren Code in einer Utility-Assembly (Utility.dll).

Von Zeit zu Zeit führen wir Verbesserungen oder neue Funktionen zu Utility.dll. Wir haben sehr strenge Abwärtskompatibilitätstests, um sicherzustellen, dass die neue Version dieser Assembly mit allen älteren Plug-in-Diensten funktioniert.

Das Problem, das ich habe, ist das Auflösen der aktuell bereitgestellten Version von Utility.dll.

Lassen Sie mich Ihnen ein Beispiel geben.

Utility.dll ist derzeit in der Version 1.0.0.0

wir einen Service ServiceA durch Bezugnahme auf Utility.dll Version 1.0.0.0

Wir aktualisieren dann Utility.dll und aktualisieren Sie die Version 2.0 erstellt erstellen. 0.0

Als nächstes erstellen wir einen Service ServiceB, der Utility.dll 2.0.0.0 verwendet.

Die Datei "Utility.dll" Version 2.0.0.0 ist sowohl mit ServiceA als auch mit ServiceB kompatibel.

Ich starte unsere Anwendung. Die bereitgestellte Version von Utility.dll wird geladen.

Ich starte ServiceB. Meine Implementierung des AppDomain.CurrentDomain.AssemblyResolve-Ereignisses wird ausgelöst, und Utility.dll 2.0.0.0 wird zurückgegeben.

Ich starte dann ServiceA. AppDomain.CurrentDomain.AssemblyResolve wird nie ausgelöst, aber ich erhalte eine FileNotFoundException für Utility.dll 1.0.0.0. An dieser Stelle möchte ich mit Utility 2.0.0.0 auflösen. Wenn Version 1.0.0.0 nicht gefunden wird, warum wird das AssemblyResolve-Ereignis nicht ausgelöst?

Auch kann ich es in umgekehrter Reihenfolge tun, indem Sie ServiceA zuerst und AssemblyResolve wird ausgelöst und Uttyty.dll 2.0.0.0 ist für ServiceA (das ursprünglich mit Utility.dll 1.0.0.0 erstellt wurde) gelöst. Wenn ich dann jedoch ServiceB starte (das mit "Utility.dll 2.0.0.0" erstellt wurde), wird das AssemblyResolve-Ereignis nie ausgelöst und eine FileNotFoundException wird für die "Utility.dll" -Version 1.0.0.0 ausgelöst.

Was ist los? Ich möchte nur die aktuell bereitgestellte Version von Utility.dll für alle Dienste verwenden.

****** AKTUALISIERT ******

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

} 

ich dann es so in jedem meiner Service-Plugins aufrufen.

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); 
} 

Die erste Service-Plugin, das fein sowie andere Service-Plugin namens Lasten bekommt, die die gleiche Version von Utility.dll als erster Service-Plugin gebaut wurde Referenzierung, die geladen wurde. Wenn ein Service-Plug-in aufgerufen wird, das mit einer anderen Version von Utility.dll erstellt wurde, wird eine FileLoadException ausgelöst.

Ein weiteres Beispiel

ServiceA mit 1.0.0.0 ServiceB Dienstprogramm mit Dienstprogramm 1.0.0 gebaut.0 ServiceB mit Utility 2.0.0.0

Das Dienstprogramm 2.0.0.0 wird bereitgestellt.

Der Benutzer startet ServiceA zuerst. Utility 2.0.0.0 ist geladen. AssemblyResolve-Ereignis wird ausgelöst. Die Montage ist gelöst. Service startet.

Der Benutzer startet ServiceB in derselben Sitzung. Dienst B verwendet dieselbe Version von Dienstprogramm wie ServiceA. Dienst B wird gestartet.

Der Benutzer startet ServiceC in derselben Sitzung. ServiceC wurde mit einer anderen Utility-Version erstellt. ServiceC wird nicht geladen.

Der Benutzer startet die Anwendung neu und versucht zunächst, ServiceC zu laden. ServiceC wird geladen. Dann versucht der Benutzer innerhalb derselben Sitzung ServiceA oder ServiceB auszuleihen, und beide Fehler schlagen fehl.

Aus irgendeinem Grund wird ein AssemblyResolve, wenn ein Service nach dem ersten geladen wird, nicht erneut ausgelöst, selbst wenn die Assembly nicht aufgelöst werden kann. Wenn das Ereignis ausgelöst würde, würde mein Handler die aktuell geladene Version von Utility zurückgeben.

+0

Ohne die genaue Konfiguration Ihrer Projekte zu sehen, ist es unmöglich, genau zu wissen, was vor sich geht. Es scheint, als ob Sie gerade die Dienste (oder zumindest ServiceA) kompiliert haben, wobei die "Spezifische Version" für die Referenz "Utility.dll" auf "true" gesetzt ist. Sie sollten in der Lage sein, die jeweils vorhandene Version zu verwenden, solange sie die gleiche oder eine neuere Version als die referenzierte ist. Sie könnten diese nützlich finden: https://stackoverflow.com/questions/37431249/whats-the-recommensed-way-to-run-plugins-with-dependency-dlls-tha-have-differ und https: // stackoverflow. com/questions/93455/assembly-names-and-versions –

+0

Die Einstellung "Spezifische Version" ist das erste, was ich angeschaut habe. Alle sind auf "falsch" eingestellt. – James

+0

Stellen Sie sicher, dass Ihre Assembly signiert ist (stark benannt) - dann können Sie mehrere Versionen laden (wahrscheinlich von Hand von verschiedenen Orten aus, wenn Sie nicht aus seltsamen Gründen alle in den GAC setzen) oder Assembly Redirect in der übergeordneten Anwendung auf die höchste Version. –

Antwort

0

schwierig zu wissen, was ohne weitere Details Ausgehen (sind Service A und B-Service unabhängige DLL ?, oder sie sind in der gleichen exe Montage?), Aber ich es auf diese Weise machen:

1) Ich verwende den AppDomain.CurrentDomain.AssemblyResolve-Mechanismus nur zur Fehlererkennung. Aber ich werde versuchen, es zuerst ohne .Net AppDomain Logik zu bekommen.

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) In meinem Loader_AssemblyResolve würde ich die bekanntesten Praktiken verwenden, die ich einige Zeit von einer Webseite lernen, aber im Moment bin ich beschämend faul für Look für die URL (Willkommen Beiträge):

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

Ich hoffe, es hilft Ihnen.

Verwandte Themen