Bagaimana saya bisa mendapatkan aplikasi file tunggal .NET Core 3 saya untuk menemukan file appsettings.json?

11

Bagaimana seharusnya aplikasi file tunggal .Net Core 3.0 Web API dikonfigurasikan untuk mencari appsettings.jsonfile yang ada di direktori yang sama dengan aplikasi file tunggal dibangun?

Setelah berlari

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

Direktori terlihat seperti ini:

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

Namun, berusaha menjalankan APPNAME.exehasil dalam kesalahan berikut

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

Saya mencoba solusi dari pertanyaan yang serupa tetapi berbeda , serta pertanyaan Stack Overflow lainnya.

Saya mencoba meneruskan yang berikut ke SetBasePath()

  • Directory.GetCurrentDirectory()

  • environment.ContentRootPath

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

Masing-masing menyebabkan kesalahan yang sama.

Akar masalahnya adalah bahwa PublishSingleFilebiner tidak di-zip dan dijalankan dari tempdirektori.

Dalam hal aplikasi file tunggal ini, lokasi yang dicari appsettings.jsonadalah direktori berikut:

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

Semua metode di atas menunjuk ke tempat file itu membuka ritsleting, yang berbeda dari tempat itu dijalankan.

Jason Yandell
sumber

Jawaban:

14

Saya menemukan masalah pada GitHub di sini berjudul PublishSingleFile excluding appsettings not working as expected.

Itu menunjuk ke masalah lain di sini berjudulsingle file publish: AppContext.BaseDirectory doesn't point to apphost directory

Di dalamnya, solusinya adalah mencoba Process.GetCurrentProcess().MainModule.FileName

Kode berikut mengonfigurasi aplikasi untuk melihat direktori tempat aplikasi yang dapat dieksekusi tunggal dijalankan, alih-alih tempat binari diekstraksi.

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

The GetBasePath()implementasi:

private string GetBasePath()
{
    using var processModule = Process.GetCurrentProcess().MainModule;
    return Path.GetDirectoryName(processModule?.FileName);
}
Jason Yandell
sumber
Jawaban ini, plus @ ronald-swaine di bawah ini sempurna. Pengaturan aplikasi dikecualikan dari exe yang dibundel, dan tugas penerbitan menempatkan file aplikasi di samping exe yang dibundel.
Aaron Hudon
8

Jika Anda baik-baik saja dengan menggunakan file saat runtime di luar yang dapat dieksekusi, maka Anda bisa menandai file yang Anda inginkan di luar, di csproj. Metode ini memungkinkan untuk perubahan langsung dan semacamnya di lokasi yang diketahui.

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

Jika ini tidak dapat diterima, dan HANYA memiliki satu file, saya melewati jalur file tunggal yang diekstrak sebagai jalur root di pengaturan host saya. Ini memungkinkan konfigurasi, dan pisau cukur (yang saya tambahkan setelah), untuk menemukan file-nya seperti biasa.

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

Catatan, untuk benar-benar membuat satu file, dan tanpa PDB, Anda juga perlu:

<DebugType>None</DebugType>
Ronald Swaine
sumber
Bagaimana Views \ Test.cshtml dalam contoh Anda dapatkan di komputer target? Saya memiliki file gambar dan kamus yang perlu saya buka berdasarkan Culture,
Paul Cohen
@ PaulCohen Saya biasanya akan menyebarkan semua file dari lokasi output yang diterbitkan menggunakan SCP. Dengan hanya perubahan dalam csproj (contoh pertama), API apa pun yang menggunakan direktori konten root default harus memiliki file-file mereka tersedia di direktori kerja. Sepertinya Anda perlu menggunakan contoh ke-2, untuk mendapatkan path asli dari konten yang diekstraksi, sehingga Anda dapat mengakses gambar dari path lengkap, atau dengan mengatur root konten dengan benar sehingga ~ / ... paths untuk gambar bisa digunakan dalam silet.
Ronald Swaine
Latar belakang saya ada di aplikasi embedded sehingga biner tunggal biasanya dibakar ke rom. Apa yang saya sadari adalah ada Exe yang saya bagikan di semacam mengekstraksi sendiri file "zip like". Semua file data saya ada di sana dan ketika aplikasi dijalankan semuanya diekstraksi ke temp secara langsung. Jika saya menemukan bahwa saya dapat menemukan file data saya. Ini juga berarti saya perlu logika sehingga saya bisa Debug di VS di mana data berada di tempat lain.
Paul Cohen
1
Saya memiliki pengaturan ini saat ini, dan secara acak berhenti menemukan file.
Pergi
1

Aplikasi saya ada di .NET Core 3.1, diterbitkan sebagai file tunggal dan berjalan sebagai Layanan Windows (yang mungkin atau mungkin tidak berdampak pada masalah ini).

Solusi yang diajukan dengan Process.GetCurrentProcess().MainModule.FileNamesebagai root konten bekerja untuk saya, tetapi hanya jika saya mengatur root konten di tempat yang tepat:

Ini bekerja:

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

Ini tidak bekerja:

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