Comment puis-je obtenir mon application de fichier unique .NET Core 3 pour trouver le fichier appsettings.json?

11

Comment une application d'API Web .Net Core 3.0 à fichier unique doit-elle être configurée pour rechercher le appsettings.jsonfichier se trouvant dans le même répertoire que l'application à fichier unique?

Après avoir couru

dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true

Le répertoire ressemble à ceci:

XX/XX/XXXX  XX:XX PM    <DIR>          .
XX/XX/XXXX  XX:XX PM    <DIR>          ..
XX/XX/XXXX  XX:XX PM               134 appsettings.json
XX/XX/XXXX  XX:XX PM        92,899,983 APPNAME.exe
XX/XX/XXXX  XX:XX PM               541 web.config
               3 File(s)     92,900,658 bytes

Cependant, la tentative d'exécution APPNAME.exeentraîne l'erreur suivante

An exception occurred, System.IO.FileNotFoundException: The configuration file 'appsettings.json' was not found and is not optional. The physical path is 'C:\Users\USERNAME\AppData\Local\Temp\.net\APPNAME\kyl3yc02.5zs\appsettings.json'.
   at Microsoft.Extensions.Configuration.FileConfigurationProvider.HandleException(ExceptionDispatchInfo info)
   at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load(Boolean reload)
   at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load()
   at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
   at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.BuildCommonServices(AggregateException& hostingStartupErrors)
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
...

J'ai essayé des solutions à partir d'une question similaire, mais distincte , ainsi que d'autres questions Stack Overflow.

J'ai tenté de transmettre ce qui suit à SetBasePath()

  • Directory.GetCurrentDirectory()

  • environment.ContentRootPath

  • Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)

Chacun a conduit à la même erreur.

La racine du problème est que le PublishSingleFilebinaire est décompressé et exécuté à partir d'un temprépertoire.

Dans le cas de cette application à fichier unique, l'emplacement qu'elle recherchait appsettings.jsonétait le répertoire suivant:

C:\Users\USERNAME\AppData\Local\Temp\.net\APPNAME\kyl3yc02.5zs

Toutes les méthodes ci-dessus pointent vers l'endroit où le fichier est décompressé, ce qui est différent de l'endroit à partir duquel il a été exécuté.

Jason Yandell
la source

Réponses:

14

J'ai trouvé un problème sur GitHub ici intitulé PublishSingleFile excluding appsettings not working as expected.

Cela a souligné un autre problème ici intitulésingle file publish: AppContext.BaseDirectory doesn't point to apphost directory

Dans ce document, une solution était d'essayer Process.GetCurrentProcess().MainModule.FileName

Le code suivant a configuré l'application pour qu'elle examine le répertoire à partir duquel l'application exécutable unique a été exécutée, plutôt que l'emplacement vers lequel les fichiers binaires ont été extraits.

config.SetBasePath(GetBasePath());
config.AddJsonFile("appsettings.json", false);

La GetBasePath()mise en œuvre:

private string GetBasePath()
{
    using var processModule = Process.GetCurrentProcess().MainModule;
    return Path.GetDirectoryName(processModule?.FileName);
}
Jason Yandell
la source
Cette réponse, plus @ ronald-swaine ci-dessous est parfaite. Les appsettings sont exclus de l'exe fourni et la tâche de publication place les fichiers appsettings à côté de l'exe fourni.
Aaron Hudon
8

Si vous êtes d'accord avec l'utilisation de fichiers à l'exécution en dehors de l'exécutable, vous pouvez simplement signaler les fichiers que vous souhaitez à l'extérieur, dans csproj. Cette méthode permet des changements en direct et autres dans un emplacement connu.

<ItemGroup>
    <None Include="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <CopyToPublishDirectory>Always</CopyToPublishDirectory>
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
    </None>
    <None Include="appsettings.Development.json;appsettings.QA.json;appsettings.Production.json;">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <CopyToPublishDirectory>Always</CopyToPublishDirectory>
      <DependentUpon>appsettings.json</DependentUpon>
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
    </None>
  </ItemGroup>

  <ItemGroup>
    <None Include="Views\Test.cshtml">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
    </None>
  </ItemGroup>

Si cela n'est pas acceptable et ne doit avoir qu'un seul fichier, je passe le chemin extrait d'un seul fichier comme chemin racine dans ma configuration d'hôte. Cela permet à la configuration et au rasoir (que j'ajoute après) de retrouver ses fichiers normalement.

// when using single file exe, the hosts config loader defaults to GetCurrentDirectory
            // which is where the exe is, not where the bundle (with appsettings) has been extracted.
            // when running in debug (from output folder) there is effectively no difference
            var realPath = Directory.GetParent(System.Reflection.Assembly.GetExecutingAssembly().Location).FullName;

            var host = Host.CreateDefaultBuilder(args).UseContentRoot(realPath);

Remarque, pour vraiment créer un seul fichier, et pas de PDB, vous aurez également besoin de:

<DebugType>None</DebugType>
Ronald Swaine
la source
Comment Views \ Test.cshtml dans votre exemple obtient-il sur l'ordinateur cible? J'ai des fichiers image et dictionnaire que je dois ouvrir en fonction de la culture,
Paul Cohen
@PaulCohen Je déploierais généralement tous les fichiers de l'emplacement de sortie publié à l'aide de SCP. Avec seulement les changements dans csproj (premier exemple), toutes les API qui utilisent le répertoire de contenu racine par défaut devront avoir leurs fichiers disponibles dans le répertoire de travail. Il semble que vous ayez besoin d'utiliser le 2ème exemple, pour obtenir le vrai chemin du contenu extrait, afin que vous puissiez accéder aux images à partir d'un chemin complet, ou en ayant la racine de contenu définie correctement afin que les chemins ~ / ... aux images peuvent être utilisées dans le rasoir.
Ronald Swaine
Mon expérience est dans les applications intégrées, donc un seul binaire est généralement gravé dans rom. Ce que je me rends compte, c'est qu'il y a l'Exe que je partage dans une sorte de fichier "zip-like" auto-extractible. Tous mes fichiers de données s'y trouvent et lorsque l'application est exécutée, tout est extrait directement à un temp. Si je trouve que je peux trouver mes fichiers de données. Cela signifie également que j'ai besoin d'un peu de logique pour pouvoir déboguer dans VS où les données sont ailleurs.
Paul Cohen
1
J'ai cette configuration actuellement et elle a cessé de trouver les fichiers au hasard.
Disparu le
1

Mon application est sur .NET Core 3.1, est publiée en tant que fichier unique et s'exécute en tant que service Windows (ce qui peut ou non avoir un impact sur le problème).

La solution proposée avec Process.GetCurrentProcess().MainModule.FileNamecomme racine de contenu fonctionne pour moi, mais seulement si je mets la racine de contenu au bon endroit:

Cela marche:

Host.CreateDefaultBuilder(args)
    .UseWindowsService()
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseContentRoot(...);
        webBuilder.UseStartup<Startup>();
    });

Cela ne fonctionne pas:

Host.CreateDefaultBuilder(args)
    .UseWindowsService()
    .UseContentRoot(...)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
    });
Wolfgang Gallo
la source