So weisen Sie Application an, <runtime> aus meiner benutzerdefinierten Datei app.config anstatt aus der Standarddatei zu lesen

8

Angenommen, ich erstelle eine App namens ConsoleApp2 .

Aufgrund einiger Bibliotheken von Drittanbietern, die ich verwende, generiert meine Standarddatei app.config Code wie

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
      <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

Das liegt daran, dass meine Lösung auf verschiedene Versionen einer Bibliothek verweist. Daher muss sie allen mitteilen: " Hey, wenn Sie nach einer alten Version dieser Bibliothek suchen , verwenden Sie einfach newVersion. " Und das ist in Ordnung.

Das Problem ist, dass ich eine separate Konfigurationsdatei "test.exe.config" definieren möchte, in der ich einige Einstellungen habe und die automatisch generierte entfernen möchte.

Um meine App über die neue Konfigurationsdatei zu informieren, verwende ich Code wie

AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", "test.exe.config");

Und das funktioniert (fast) perfekt. Und ich habe dort " fast " geschrieben, da der <appSettings>Abschnitt zwar korrekt gelesen wird, der <runtime>Abschnitt jedoch nicht in meiner benutzerdefinierten Konfigurationsdatei angezeigt wird, sondern die App ihn stattdessen in der Standardkonfigurationsdatei sucht, was ein Problem ist, da ich dies möchte in der Lage sein, diese später zu löschen.

Wie kann ich meine Anwendung anweisen, auch die <runtime>Informationen aus meiner benutzerdefinierten Konfigurationsdatei zu lesen ?


So reproduzieren Sie das Problem

Ein einfaches Beispiel zur Reproduktion meines Problems lautet wie folgt:

Erstellen Sie eine Bibliothek mit dem Namen ClassLibrary2 ( .Net Framework v4.6 ) mit einer einzelnen Klasse wie folgt

using Newtonsoft.Json.Linq;
using System;

namespace ClassLibrary2
{
    public class Class1
    {
        public Class1()
        {
            var json = new JObject();
            json.Add("Succeed?", true);

            Reash = json.ToString();
        }

        public String Reash { get; set; }
    }
}

Beachten Sie den Verweis auf das Newtonsoft- Paket. Die in der Bibliothek installierte Version ist 10.0.2 .

Erstellen Sie nun eine Konsolenanwendung mit dem Namen ConsoleApp2 ( .Net Framework v4.6 ) mit einer Klasse namens Program, deren Inhalt einfach wie folgt lautet:

using System;
using System.Configuration;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {

            AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", "test.exe.config");

            var AppSettings = ConfigurationManager.AppSettings;

            Console.WriteLine($"{AppSettings.Count} settings found");
            Console.WriteLine($"Calling ClassLibrary2: {Environment.NewLine}{new ClassLibrary2.Class1().Reash}");
            Console.ReadLine();

        }
    }
}

Diese Anwendung sollte auch Newtonsoft installiert haben , jedoch in einer anderen Version v12.0.3 .

Erstellen Sie die Anwendung im Debug-Modus. Erstellen Sie dann im Ordner ConsoleApp2 / ConsoleApp2 / bin / Debug eine Datei mit dem Namen test.exe.config mit folgendem Inhalt

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
  </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <appSettings>
    <add key="A" value="1"/>
    <add key="B" value="1"/>
    <add key="C" value="1"/>
  </appSettings>
</configuration>

und beachten Sie, dass sich in demselben Debug- Ordner auch die Standardkonfigurationsdatei ConsoleApp2.exe.config mit einem Inhalt wie befindet

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
  </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Wenn Sie die Anwendung an diesem Punkt ausführen, wird sie problemlos kompiliert, und Sie sollten eine Konsole wie diese sehen

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass die (3) Einstellungen korrekt aus meiner benutzerdefinierten Konfigurationsdatei gelesen wurden. So weit, ist es gut...

Benennen Sie nun die Standardkonfigurationsdatei in _ConsoleApp2.exe.config um und führen Sie die Anwendung erneut aus. Sie sollten jetzt eine FileLoadException erhalten .

Geben Sie hier die Bildbeschreibung ein

Wie kann ich meine Anwendung anweisen, die <runtime>Informationen aus meiner benutzerdefinierten Konfigurationsdatei zu lesen ?


Begründung

Der Grund, warum ich eine Antwort auf diese Frage suche, ist folgender:

Wenn wir unsere Anwendung freigeben, legen wir alle EXE- und DLL-Dateien in einem Ordner und unsere benutzerdefinierte Konfigurationsdatei (mit Einstellungen usw.) in einem anderen Ordner ab, in dem unsere Clients ähnliche Dateien haben.

In dem Ordner mit den EXE- und DLL-Dateien versuchen wir, so wenig wie möglich zu behalten, daher wurde ich gebeten, einen Weg zu finden, um diese ConsoleApp2.exe.config nach Möglichkeit zu entfernen . Da die oben genannten Bindungen in diese Konfigurationsdatei geschrieben wurden, habe ich nur versucht, diese Informationen in unsere benutzerdefinierte Konfigurationsdatei zu verschieben ... aber bisher habe ich es nicht geschafft: Die Bindungsumleitungen werden immer versucht, von dieser ConsoleApp2.exe gelesen zu werden .config , sobald ich es entferne, bekomme ich Ausnahmen ...

Deczaloth
quelle
1
Das ist nicht möglich. Die .config wird angewendet, bevor die primäre Appdomain vom CLR-Host erstellt wird. Sehr früh, noch bevor die CLR gestartet und alles andere erledigt wird, wonach die Konfiguration gesperrt wird. Technisch gesehen könnten Sie Ihre eigene AppDomain erstellen, außer dass eine solche Lösung wenig Zukunft hat, da .NETCore und das kommende .NET 5 sie nicht mehr unterstützen. Custom-Hosting ist auch nicht gerade ideal. Es ist nicht offensichtlich, warum ein solcher Hack notwendig ist. Bindungsumleitungen sind lediglich ein Bereitstellungsdetail.
Hans Passant
Hallo @HansPassant, danke für deine Information. Kennen Sie irgendwo im MSDN-Dokument, wo dies dokumentiert ist? Auch wenn Sie es als Antwort posten, werde ich es gerne annehmen und Ihnen das Kopfgeld geben.
Deczaloth
2
Bücher, Steven Pratschner hat eines geschrieben, das auf den Punkt kommt. Schwer zu empfehlen, die CLR hat sich seitdem zu stark verändert. "Das ist nicht möglich" -Antworten werden von niemandem, der SO besucht, als nützlich angesehen. Es gibt keinen offensichtlichen Weg, Sie irgendwohin zu bringen, da Sie nicht erklärt haben, warum Sie dies tun müssen.
Hans Passant
@ HansPassant, ich habe meine Frage bearbeitet und am Ende die "Begründung" hinzugefügt.
Deczaloth
2
@Deczaloth Ich habe das Gefühl, dass wir ein XY-Problem haben - Sie versuchen, eine Lösung zu finden, um einen <runtime>Abschnitt aus einer anderen Konfiguration anzuwenden , aber das Problem tritt aufgrund der Art und Weise auf, wie Sie die allgemeine Konfiguration verwalten. Wenn Sie die allgemeine Konfiguration auf andere Weise verwalten, ist dieses Problem nicht mehr relevant. Bitte überprüfen Sie meine Antwort und ziehen Sie in Betracht, Konfigurationstransformationen zu verwenden, anstatt die Laufzeit zu optimieren.
Fenixil

Antworten:

6

Sie suchen wahrscheinlich nach Konfigurationsumwandlungen :

Die Idee dahinter ist, dass Sie in Visual Studio mehrere Konfigurationen wie Debug, Release, Production, Test ... im Konfigurationsmanager und eine Standardkonfigurationsdatei sowie sogenannte Transformationen erstellen.

Beachten Sie, dass Sie im Konfigurationsmanager so viele Konfigurationen erstellen können, wie Sie möchten. Um neue hinzuzufügen, klicken Sie auf Lösungskonfigurationen (die Dropdown-Liste zeigt "Debug" oder "Release") und wählen Sie "Configuration Manager ...". Öffnen Sie es und Sie sehen eine Liste aller derzeit vorhandenen Konfigurationen. Lassen Sie das Kombinationsfeld "Active Solution-Konfiguration" fallen und wählen Sie " <New...>", um weitere hinzuzufügen.

Diese Transformationen geben an, was die spezifische Konfiguration von der Standardkonfiguration unterscheidet. Sie müssen also nicht wiederholen, was Sie bereits in der Standardkonfiguration angegeben haben. Stattdessen erwähnen Sie nur die Unterschiede, zum Beispiel:

<configuration>
    <appSettings>
        <add key="ClientSessionTimeout" value="100"
            xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
    </appSettings>
</configuration>

Hiermit wird die relevante Einstellung anhand des Schlüssels ermittelt ClientSessionTimeoutund der Wert 100durch Ersetzen des ursprünglichen Werts in der Konfigurationsdatei festgelegt (dies xdt:Transform="SetAttributes" xdt:Locator="Match(key)"bedeuten die zusätzlichen Transformationsattribute ). Sie können auch festlegen, dass vorhandene Einstellungen entfernt werden sollen (indem Sie xdt:Transform="Remove"stattdessen angeben ), z

<add key="UserIdForDebugging" xdt:Transform="Remove" xdt:Locator="Match(key)"/>

würde eine Benutzer-ID entfernen, die nur zum Debuggen und nicht für die Version vorhanden sein sollte (Weitere Informationen zu verfügbaren Optionen finden Sie hier - beschrieben für Web.config, aber auch für App.config).

Zusätzlich zu der App.ConfigDatei haben Sie eine Datei pro Konfiguration, dh App.Debug.Configfür Debug, App.Release.ConfigRelease usw. Visual Studio hilft Ihnen beim Erstellen.

Ich habe hier und da bereits Antworten in StackOverflow erstellt , die es ausführlich beschreiben. Bitte schauen Sie.

Wenn Sie Probleme beim Anzeigen in Visual Studio haben, sehen Sie hier nach .


In Bezug auf Ihre Begründung :

Transformationen erstellen eine vollständige Konfigurationsdatei, indem die Transformationsdatei auf die Standardkonfigurationsdatei angewendet wird. Die resultierende Datei wird kompiliert und zusammen mit den anderen kompilierten Dateien in den Ordner "bin" gestellt. Wenn Sie also die Konfiguration "Release" ausgewählt haben, werden alle Dateien einschließlich der transformierten Konfigurationsdatei in "bin \ Release" kompiliert.

Und die Konfigurationsdatei wird genauso benannt wie die exe-Datei plus ".config" am Ende angehängt (mit anderen Worten, es gibt keine ".Release.config" im Binärordner, sondern eine "MySuperCoolApp.exe.config" erstellt - für die Anwendung "MySuperCoolApp.exe").

Gleiches gilt auch für die andere Konfiguration - jede Konfiguration erstellt einen Unterordner innerhalb von "bin" -. Wenn Sie Skripts verwenden, kann auf diesen Unterordner wie $(TargetDir)in einem Post-Build-Ereignis verwiesen werden .

Matt
quelle
Hallo Matt, danke, dass du dir die Zeit genommen hast, mir zu helfen. Wie Sie in einer Antwort unten sehen können, schlug @fenixil auch Konfigurationstransformationen vor. Trotzdem und obwohl ich es als faszinierendes Werkzeug finde (ich werde es definitiv in unseren Projekten ausprobieren!), Kann ich nicht sehen, wie dies meine Frage löst. Könnten Sie mein supereinfaches Beispiel erweitern, um Konfigurationsumwandlungen so zu implementieren, dass mein Problem gelöst wird? (Denken Sie an meine "Begründung" am Ende meiner Frage)
Deczaloth
@Deczaloth - hat einige Hinweise für Ihre Begründung hinzugefügt, hoffe, dies macht es klarer.
Matt
4

Konfiguration konfigurieren

Angesichts des Problems, das auftritt, wenn Sie versuchen, eine andere (nicht native) Konfigurationsdatei zu verwenden, versuchen Sie, eine Lösung zu finden, um diese ordnungsgemäß zu ersetzen. In meiner Antwort möchte ich ein wenig zurücktreten und mich auf den Grund konzentrieren, warum Sie ihn ersetzen möchten. Basierend auf dem, was Sie in der Frage beschrieben haben, müssen Sie benutzerdefinierte Anwendungseinstellungen definieren. Wenn ich es richtig verstanden habe, planen Sie, es mit dem Zielprojekt zu verknüpfen. Setzen Sie die Eigenschaft "In Ausgabe kopieren" auf "Immer" und Sie erhalten es in der Nähe der Anwendung.

Anstatt die neue Konfigurationsdatei zu kopieren, gibt es in Ihrem Fall eine Möglichkeit, vorhandene (native) zu transformieren - ConsoleApp2.exe.configmithilfe von Xdt-Transformationen . Um dies zu erreichen, erstellen Sie eine Transformationsdatei und deklarieren dort nur die Abschnitte, die Sie transformieren möchten, zum Beispiel:

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings xdt:Transform="Replace">
    <add key="A" value="1"/>
    <add key="B" value="1"/>
    <add key="C" value="1"/>
  </appSettings>
</configuration>

Vorteile eines solchen Ansatzes sind:

  • Flexibilität: Transformationen sind sehr flexibel . Sie können Abschnitte ersetzen, zusammenführen, Attribute festlegen / entfernen usw. Möglicherweise verfügen Sie über umgebungsspezifische (DEV / UAT / PROD) oder erstellungsspezifische (Debug / Release) Transformationen.
  • Wiederverwendbarkeit: Definieren Sie die Transformation einmal und verwenden Sie sie in allen benötigten Projekten wieder.
  • Granularität: Sie deklarieren nur das, was Sie benötigen, und müssen nicht die gesamte Konfiguration kopieren und einfügen.
  • Sicherheit: Sie lassen nuget und msbuild die native Konfigurationsdatei verwalten (fügen Sie Bindungsumleitungen usw. hinzu).

Der einzige Nachteil dieses Ansatzes ist die Lernkurve: Sie müssen die Syntax lernen und wissen, wie Sie Transformationen in Ihre Konfigurationen in MSBuild kleben.

.NET Core unterstützt die Transformation. Hier ist ein Beispiel zum Erstellen von Transformationen für web.config. Sie können jedoch Transformationen auf alle Konfigurationen anwenden.

Wenn Sie .NET-Anwendungen (nicht .NET Core) entwickeln, würde ich empfehlen, sich Slowcheetah anzusehen .

Es gibt viele Ressourcen und nützliche Blogbosts über Transformation, es ist ziemlich weit verbreitet. Bitte kontaktieren Sie mich, wenn Sie Schwierigkeiten haben.

Aus meiner Sicht ist config transforms eine richtige Lösung, um Ihr Ziel zu erreichen. Ich würde daher dringend empfehlen, dies in Betracht zu ziehen, anstatt die Laufzeit zu optimieren.

Externalisieren Sie Konfigurationsabschnitte

Wenn Sie appSettings weiterhin am gemeinsamen Speicherort behalten möchten, können Sie Konfigurationsabschnitte mit dem ConfigSource- Attribut externalisieren . Überprüfen Sie dies und diesen Thread für Details:

// ConsoleApp2.exe.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings configSource="../commonConfig/connections.config"/>
</configuration>

// connections.config:
<?xml version="1.0" encoding="utf-8"?>
<connectionStrings>
<add name="MovieDBContext" 
   connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=aspnet-MvcMovie;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\Movies.mdf" 
   providerName="System.Data.SqlClient" 
/>
</connectionStrings>

Der Abschnitt AppSettings enthält das Attribut Datei , mit dem Sie Parameter aus einer anderen Datei zusammenführen können.

Mit dieser Option können Sie bestimmte Abschnitte der Konfiguration ersetzen, jedoch nicht den gesamten Inhalt. Wenn Sie also nur appSettings benötigen, ist dies völlig zutreffend. Sie legen einfach die Konfigurationsdatei mit appSettings an einem gemeinsamen Speicherort ab, der für Benutzer und Patch-Konfigurationsdatei (Hinzufügen fileoder configSourceAttribut) freigegeben ist, um diesen Abschnitt von diesem Speicherort aus zu beziehen . Wenn Sie weitere Abschnitte benötigen, müssen Sie diese als separate Dateien extrahieren.

Fenixil
quelle
Hallo! Vielen Dank, dass Sie sich die Zeit genommen haben, meine Frage zu lesen / zu beantworten. Ich werde Ihren Vorschlag versuchen, sobald ich wieder in meinem Büro bin :)
Deczaloth
Ich habe mir Konfigurationsumwandlungen angesehen. Es ist ein sehr interessantes Werkzeug, sollte ich zugeben. Trotzdem sehe ich nicht, wie dies meine Frage beantwortet. Wenn ich richtig verstanden habe, schlagen Sie vor, dass ich noch eine zusätzliche Datei hinzufüge (nämlich die Konfigurationsumwandlungsdatei). Und selbst wenn ich eine Konfigurationsumwandlungsdatei so schreibe, dass der Abschnitt <runtime> automatisch in meine benutzerdefinierte Konfigurationsdatei geschrieben wird, weiß ich immer noch nicht, wie ich die App anweisen soll, die Bindungsumleitungen von dort anstatt von der zu lesen Standardkonfigurationsdatei. Wenn es etwas gibt, das ich anscheinend nicht verstanden habe, lassen Sie es mich bitte wissen.
Deczaloth
1
Die Idee hinter der Konfigurationstransformation ist, dass Sie keine benutzerdefinierte Konfigurationsdatei mehr haben und die Laufzeitkonfiguration nicht mehr aus einer anderen Datei gelesen werden muss. Fügen Sie einfach Ihre App-Einstellungen (oder irgendetwas anderes) mit trasforms in die native Konfigurationsdatei ein. Macht das Sinn?
Fenixil
Es tut mir wirklich leid, das zu sagen, aber ich verstehe den Punkt immer noch nicht. Könnten Sie vielleicht mein supereinfaches Beispiel oben verwenden, um ein vollständiges Beispiel Ihres Vorschlags zu erstellen? Dann werde ich (hoffe ich) kristallklar sehen, wie Config Transform mein Problem lösen kann :)
Deczaloth
Es hängt ganz davon ab, was als Problem zu betrachten ist: Aus Ihrer Sicht ist es nicht möglich, die Konfigurationsdatei zur Laufzeit zu ersetzen, sodass der Abschnitt <runtime> aus einer anderen Datei verwendet wird. Ich versuche, Ihr Problem neu zu formulieren. Wenn Sie die Konfigurationsdatei zur Laufzeit nicht ersetzen müssen, tritt dieses Problem nicht auf. Aus meiner Sicht besteht das Problem darin, wie Sie die Konfiguration verwalten, sodass Sie diese Hacks mit Laufzeitersetzung durchführen müssen.
fenixil
3

Um ordnungsgemäß mit anderen .configDateien zu arbeiten, können Sie die Standarddatei zum Verwalten von Biding-Weiterleitungen und eine andere für Ihre Anwendungsparameter beibehalten. Um dies zu tun Standard ändern app.config zur Laufzeit sieht gut aus .

Sie können auch die automatische Generierung der Bindungsumleitung herunterfahren und nur eine handgefertigte Datei app.config verwenden. Hier ist ein Beispiel: Benötigen Sie eine Möglichkeit, auf zwei verschiedene Versionen derselben DLL eines Drittanbieters zu verweisen

Bearbeiten Unter Berücksichtigung der Gründe: Wenn ich es verstehe, möchten Sie die Datei app.exe.config überhaupt nicht. Sie schaffen es bereits, benutzerdefinierte Contants an einer anderen Stelle abzulegen und zu lesen.

Es bleibt nur die verbindliche Weiterleitung.

Sie können es entfernen, indem Sie die Bindungsumleitung zur Laufzeit wie hier beschrieben verwalten: https://stackoverflow.com/a/32698357/361177 Sie können auch einen konfigurierbaren Bindungsauflöser neu erstellen, indem Sie Ihren Code in der Konfigurationsdatei anzeigen lassen.

Meine zwei Cent hier: Es ist machbar, aber ich denke nicht, dass es das wert ist.

Bearbeiten 2 Diese Lösung sieht vielversprechend aus https://stackoverflow.com/a/28500477/361177

Orace
quelle
Hey, danke für deine Antwort! Ich habe wegen des Verweises auf die Antwort von @BryanSlatner gestimmt, trotzdem ist diese Problemumgehung nicht genau das, wonach ich gefragt habe. Und es könnte sein, dass das, was ich erreichen möchte, nämlich: meiner Anwendung mitteilen zu können, dass sie die Bindungsumleitungen aus meiner benutzerdefinierten Konfigurationsdatei lesen soll, nicht einmal möglich ist ...
Deczaloth
1
Möglicherweise können Sie den konfigurierbaren Bindungsumleitungsprozess neu erstellen. Indem Sie eine Methode für das AppDomain.CurrentDomain.AssemblyResolveEreignis registrieren und diese Methode veranlassen, die Bindungsregeln aus der Konfigurationsdatei abzurufen.
Orace
1
Ich habe Ihre Begründung noch einmal gelesen. Es sieht so aus, als ob Ihr Ziel (getrennte exe und config) Sie dazu bringt, hart zu codieren, wo das Programm seine Konfiguration findet. Entschuldigung, aber ich finde es dumm. Wenn sich der [relative] Pfad der Konfigurationsdatei ändert, müssen Sie Ihren Code neu kompilieren. Ich verstehe die Vorteile eines Split und für mich ist die beste Lösung eine app.config-Datei mit der Laufzeitkonfiguration und dem [relativen] Pfad zu einer anderen Konfigurationsdatei. Diese andere Datei enthält den Rest der Konfiguration, wo der Kunde kann Optimieren Sie die Anwendung.
Orace
Ich verstehe was sie meinen. Die Ordnerstruktur, in der sich die Konfigurationsdatei befinden soll, ist jedoch seit fast einem Jahrzehnt unverändert, sodass dies kein Problem darstellt. Wie auch immer, ich denke am Ende würden wir tatsächlich einen solchen Ansatz anwenden: "app.config-Datei mit der Laufzeitkonfiguration und dem [relativen] Pfad zu einer anderen Konfigurationsdatei".
Deczaloth
@ Deczaloth werfen Sie einen Blick hier stackoverflow.com/a/28500477/361177
Orace