Como dizer ao Aplicativo para ler <runtime> do meu arquivo app.config personalizado em vez do padrão

8

Digamos que estou criando um aplicativo chamado ConsoleApp2 .

Devido a algumas bibliotecas de terceiros que estou usando, meu arquivo app.config padrão está gerando código como

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

Isso é porque minhas referências solução versões diferentes de uma biblioteca, por isso precisa de dizer a todos: " Ei, se você olhar para qualquer OldVersion desta biblioteca, basta usar NewVersion ". E está tudo bem.

O problema é que eu quero definir um arquivo de configuração separado "test.exe.config", onde tenho algumas configurações e me livrar da gerada automaticamente.

Para informar meu aplicativo sobre o novo arquivo de configuração, estou usando um código como

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

E isso funciona (quase) perfeitamente. E eu escrevi lá " quase ", pois, embora a <appSettings>seção esteja sendo lida corretamente, a <runtime>seção não está sendo analisada no meu arquivo de configuração personalizado, mas o App o procura no arquivo de configuração padrão, o que é um problema, já que eu quero poder excluí-lo mais tarde.

Então, como posso dizer ao meu aplicativo para ler também as <runtime>informações do meu arquivo de configuração personalizado?


Como reproduzir o problema

Um exemplo simples para reproduzir meu problema é o seguinte:

Crie uma biblioteca chamada ClassLibrary2 ( .Net Framework v4.6 ) com uma única classe da seguinte maneira

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

Observe a referência ao pacote Newtonsoft . O instalado na biblioteca é a v10.0.2 .

Agora crie um aplicativo de console chamado ConsoleApp2 ( .Net Framework v4.6 ) com uma classe chamada Program cujo conteúdo é simplesmente o seguinte:

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

        }
    }
}

Este aplicativo deve ter instalado também o Newtonsoft , mas em uma versão diferente v12.0.3 .

Crie o aplicativo no modo de depuração. Em seguida, na pasta ConsoleApp2 / ConsoleApp2 / bin / Debug, crie um arquivo chamado test.exe.config com o seguinte conteúdo

<?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>

e observe que nessa mesma pasta Debug também há o arquivo de configuração padrão ConsoleApp2.exe.config com um conteúdo como

<?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>

Se nesse ponto você executar o aplicativo, ele será compilado sem problemas e você deverá ver um console como

insira a descrição da imagem aqui

Observe que as (3) configurações foram lidas corretamente no meu arquivo de configuração personalizado. Por enquanto, tudo bem...

Agora renomeie o arquivo de configuração padrão para algo como _ConsoleApp2.exe.config e execute novamente o aplicativo. Agora você deve obter uma FileLoadException .

insira a descrição da imagem aqui

Então, novamente, como posso dizer ao meu aplicativo para ler as <runtime>informações do meu arquivo de configuração personalizado?


Fundamentação

A razão pela qual estou procurando uma resposta para esta pergunta é a seguinte:

Quando lançamos nosso aplicativo, colocamos todos os arquivos .exe e .dll em uma pasta e nosso arquivo de configuração personalizado (com configurações, etc) em outra, onde nossos clientes têm arquivos semelhantes.

Na pasta com os arquivos .exe e .dll, tentamos manter o mínimo possível, por isso me pediram para encontrar uma maneira de se livrar desse ConsoleApp2.exe.config, se possível. Agora, desde que as ligações mencionadas acima foram escritas nesse arquivo de configuração, tentei mover essas informações para nosso arquivo de configuração personalizado ... mas até agora não consegui: os redirecionamentos de ligação sempre tentam ser lidos a partir desse ConsoleApp2.exe .config , assim que removê-lo, recebo exceções ...

Deczaloth
fonte
1
Isso não é possível. O .config é aplicado antes do domínio do aplicativo principal ser criado pelo host do CLR. Muito cedo, mesmo antes de o CLR ser iniciado e qualquer outra coisa ser feita, após o qual a configuração é bloqueada. Tecnicamente, você pode criar seu próprio AppDomain, exceto que há pouco futuro para essa solução, já que o .NETCore e o próximo .NET 5 não os suportam mais. A hospedagem personalizada também não é exatamente ideal. Não é óbvio por que esse hack é necessário, os redirecionamentos de ligação são um mero detalhe de implantação.
Hans Passant
Olá @HansPassant, obrigado pela sua informação. Você conhece algum lugar no documento MSDN onde isso está documentado? Além disso, se você publicá-lo como resposta, ficarei feliz em aceitá-lo e dar-lhe a recompensa.
Deczaloth
2
Livros, Steven Pratschner escreveu um que chega ao âmago da questão. Difícil de recomendar, o CLR mudou muito desde então. As respostas "Isso não é possível" não são consideradas úteis por quem visita o SO. Não há uma maneira óbvia de levá-lo a algum lugar, pois você não explicou por que precisa fazer isso.
Hans Passant
@HansPassant, editei minha pergunta adicionando a "justificativa" no final.
Deczaloth
2
@ Deczaloth Tenho a sensação de que temos um problema XY - você está tentando encontrar uma solução para aplicar a <runtime>seção de outra configuração, mas o problema ocorre devido à maneira como você gerencia a configuração comum. Se você gerenciar configurações comuns de uma maneira diferente, esse problema não será mais relevante. Por favor, verifique minha resposta e considere usar transformações de configuração em vez de ajustar o tempo de execução.
Fenixil 14/12/19

Respostas:

6

Você provavelmente está procurando por transformações de configuração :

A idéia por trás disso é que você crie várias configurações no Visual Studio, como Depuração, Lançamento, Produção, Teste ... no gerenciador de configuração e um arquivo de configuração padrão, além das chamadas transformações.

Observe que você pode criar quantas configurações desejar no gerenciador de configuração. Para adicionar novos, clique em Configurações da solução (no menu suspenso "Debug" ou "Release") e selecione "Configuration Manager ...". Abra-o e você verá uma lista de todas as configurações existentes no momento. Desça a caixa de combinação "Configuração da solução ativa" e selecione " <New...>" para adicionar mais.

Essas transformações especificam o que torna a configuração específica diferente da padrão - portanto, você não precisa repetir o que já especificou na configuração padrão. Em vez disso, basta mencionar as diferenças, por exemplo:

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

que localiza a configuração relevante por sua chave ClientSessionTimeoute define seu valor 100substituindo o valor original no arquivo de configuração (é isso que os atributos adicionais da transformação xdt:Transform="SetAttributes" xdt:Locator="Match(key)"significam). Você também pode especificar para remover as configurações existentes (especificando em xdt:Transform="Remove"vez disso), por exemplo

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

removeria um ID de usuário que deveria estar lá apenas para depuração, não para o lançamento (Para saber mais sobre as opções disponíveis, consulte aqui - descrito para Web.config, mas também aplicável para App.config).

Além do App.Configarquivo que você tem um arquivo por configuração, ou seja, App.Debug.Configpara a depuração, App.Release.Configpara lançamento etc. Visual Studio ajuda a criá-los.

Já criei respostas no StackOverflow aqui e ali , que o descrevem em detalhes, por favor, dê uma olhada.

Se você está tendo problemas para exibi-los no Visual Studio, dê uma olhada aqui .


Em relação à sua justificativa :

As transformações estão criando um arquivo de configuração completo aplicando o arquivo de transformação ao arquivo de configuração padrão. O arquivo resultante é compilado e colocado na pasta "bin" - junto com os outros arquivos compilados. Portanto, se você tiver uma configuração "Release" selecionada, todos os arquivos, incluindo o arquivo de configuração transformado, serão compilados em "bin \ Release".

E o arquivo de configuração é nomeado exatamente como o arquivo exe mais ".config" anexado no final (em outras palavras, não há ".Release.config" na pasta binária, mas um "MySuperCoolApp.exe.config" foi criado - para o aplicativo "MySuperCoolApp.exe").

Da mesma forma, o mesmo acontece com a outra configuração - cada configuração cria uma subpasta dentro de "bin" - se você estiver usando scripts, essa subpasta pode ser referenciada como $(TargetDir)em um evento pós-compilação.

Matt
fonte
Oi Matt, obrigado por dedicar seu tempo para tentar me ajudar. Como você pode ver na resposta abaixo, o @fenixil sugeriu também transformações de configuração. No entanto, e apesar de achar uma ferramenta fascinante (definitivamente vou experimentá-la em nossos projetos!), Não consigo ver como isso resolve minha pergunta. Você poderia estender minha amostra super simples para implementar transformações de configuração de uma maneira que resolva meu problema? (lembre-se da minha "justificativa" no final da minha pergunta)
Deczaloth
@ Deczaloth - adicionei algumas dicas para o seu Rationale, espero que isso torne mais claro.
Matt
4

Transformação de configuração

Dado o problema ocorrer ao tentar usar outro arquivo de configuração (não nativo), você está tentando encontrar uma solução para substituí-lo 'adequadamente'. Na minha resposta, quero recuar um pouco e focar na razão pela qual você deseja substituí-lo. Com base no que você descreveu na pergunta, você pode definir configurações personalizadas do aplicativo. Se entendi corretamente, você planeja vinculá-lo ao projeto de destino, defina a propriedade 'Copy to output' como 'Always' e a aproximará do aplicativo.

Em vez de copiar o novo arquivo de configuração, existe uma maneira de transformar um existente (nativo), no seu caso - ConsoleApp2.exe.configusando transformações Xdt . Para isso, você cria o arquivo de transformação e declara apenas as seções que deseja transformar, por exemplo:

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

Os benefícios dessa abordagem são:

  • flexibilidade: as transformações são muito flexíveis , você pode substituir seções, mesclá-las, definir / remover atributos, etc. Você pode ter transformações específicas do ambiente (DEV / UAT / PROD) ou de construção (Debug / Release) específicas.
  • reutilização: defina a transformação uma vez e reutilize-a em todos os projetos necessários.
  • granularidade: você declara apenas o que precisa, sem necessidade de copiar e colar toda a configuração.
  • safety: você permite que nuget e msbuild gerenciem o arquivo de configuração 'nativo' (adicione redirecionamentos de ligação, etc.)

A única desvantagem dessa abordagem é a curva de aprendizado: você precisa aprender sintaxe e saber como colar transformações nas suas configurações no MSBuild.

O .NET Core tem suporte para transformação, aqui está um exemplo de como criar transformações para web.config, mas você pode aplicar transformações a qualquer configuração.

Se você desenvolver aplicativos .NET (não o .NET Core), recomendo consultar o Slowcheetah .

Existem muitos recursos e benefícios úteis de blog sobre transformação, é bastante utilizado. Entre em contato comigo se tiver dificuldades.

Do meu ponto de vista, as transformações de configuração são uma solução certa para atingir seu objetivo, portanto, recomendo fortemente considerá-lo, em vez de ajustar o tempo de execução.

Externalizar seções de configuração

Se você ainda deseja manter o appSettings no local comum, pode externalizar as seções de configuração com o atributo ConfigSource . Verifique este e este tópico para obter detalhes:

// 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>

A seção AppSettings contém o atributo File que permite mesclar parâmetros de outro arquivo.

Esta opção permite substituir determinadas seções da configuração, mas não todo o conteúdo. Portanto, se você precisar apenas de appSettings, é totalmente aplicável - basta colocar o arquivo de configuração com o appSettings no local comum compartilhado com o arquivo de configuração do usuário e do patch (adicionar fileou configSourceatributo) para originar esta seção a partir desse local. Se você precisar de mais seções, precisará extraí-las como arquivos separados.

fenixil
fonte
Ei! obrigado por dedicar seu tempo para ler / responder à minha pergunta. Vou tentar a sua proposta logo que eu voltar no meu escritório :)
Deczaloth
Dei uma olhada nas transformações de configuração. É uma ferramenta muito interessante, devo admitir. No entanto, não vejo como isso responde à minha pergunta. Se entendi direito, você propõe que eu adicione ainda um arquivo extra (a saber, o arquivo de transformação de configuração). E, mesmo deixando isso de lado, se uma gravação de um arquivo de transformação de configuração for criada automaticamente na seção <runtime> no meu arquivo de configuração personalizado, ainda não sei como dizer ao aplicativo para ler os redirecionamentos de ligação de lá em vez de arquivo de configuração padrão. Se houver algo que pareço não ter entendido, entre em contato.
Deczaloth
1
A idéia por trás da transformação de configuração é que você não possui mais nenhum arquivo de configuração personalizado e isso elimina a necessidade de ler a configuração de tempo de execução de outro arquivo. Basta colocar as configurações do seu aplicativo (ou qualquer outra coisa) com transformações no arquivo de configuração nativo. Isso faz sentido?
Fenixil
Lamento muito dizer isso, mas ainda não entendi o motivo. Você poderia usar minha amostra super simples acima para produzir um exemplo completo de sua proposta? Então eu (espero eu) vai ver cristalinas como configuração Transform pode resolver o meu problema :)
Deczaloth
depende totalmente do que considerar um problema: da sua perspectiva, é incapaz de substituir o arquivo de configuração no tempo de execução, para que a seção <runtime> seja consumida a partir de outro arquivo. Estou tentando reformular sua preocupação - se você não precisar substituir o arquivo de configuração em tempo de execução, não terá esse problema. Então, o problema da minha perspectiva é como você gerencia a configuração para que você precise executar esses hacks com substituição em tempo de execução.
Fenixil 18/12/19
3

Para funcionar corretamente com .configarquivos diferentes , você pode manter o padrão para gerenciar redirecionamentos de espera e outro para os parâmetros do aplicativo. Para fazer isso, mude o app.config padrão em tempo de execução .

Você também pode desligar a geração automática de redirecionamento de ligação e usar apenas um arquivo app.config criado manualmente. Há um exemplo aqui: Precisa de uma maneira de fazer referência a 2 versões diferentes da mesma DLL de terceiros

Editar Tendo em conta a justificativa: Se eu entendi, você não deseja o arquivo app.exe.config. Você já consegue colocar e ler o conteúdo personalizado em outro lugar.

Permanece apenas o redirecionamento de ligação.

Você pode se livrar dele gerenciando o redirecionamento de ligação em tempo de execução, como é feito aqui: https://stackoverflow.com/a/32698357/361177 Você também pode recriar um resolvedor de ligação configurável fazendo seu código olhar para o arquivo de configuração.

Meus dois centavos aqui: é viável, mas não acho que valha a pena.

Editar 2 Esta solução parece promissora https://stackoverflow.com/a/28500477/361177

Orace
fonte
Ei, obrigado pela sua resposta! Eu votei por causa da referência à resposta de @BryanSlatner, no entanto, que a solução alternativa não é exatamente o que eu estava pedindo. E pode ser que o que eu quero alcançar, a saber: ser capaz de dizer a minha candidatura para ler os redirecionamentos de ligação do meu arquivo de configuração personalizada, não é mesmo possível ...
Deczaloth
1
Você pode reconstruir o processo de redirecionamento de ligação configurável. Registre um método no AppDomain.CurrentDomain.AssemblyResolveevento e faça com que este método obtenha as regras de ligação do arquivo de configuração.
Orace 14/12/19
1
Reli sua lógica. Parece que seu objetivo (exe e config separados) o levará a codificar onde o programa encontrará sua configuração. Desculpe, mas acho isso idiota. Se o caminho [relativo] do arquivo de configuração for alterado, você terá que recompilar seu código. Entendo os benefícios de uma divisão, e para mim a melhor solução é um arquivo app.config com a configuração de tempo de execução e o caminho [relativo] para outro arquivo de configuração; esse outro arquivo conterá o restante da configuração em que o cliente pode ajustar a aplicação.
Orace 14/12/19
Eu entendi o seu ponto. No entanto, a estrutura de pastas onde o arquivo de configuração deve ser localizado permanece inalterada há quase mais de uma década, portanto isso não seria um problema. Enfim, acho que no final aplicaríamos essa abordagem: "arquivo app.config com a configuração do tempo de execução e o caminho [relativo] para outro arquivo de configuração".
Deczaloth
@Deczaloth dê uma olhada aqui stackoverflow.com/a/28500477/361177
Orace