Single File ASP.NET Core 5 web app 不加载静态资源

Single File ASP.NET Core 5 web app does not load static resources

我正在尝试创建单个文件 asp.net core 5 网络应用程序。目标是拥有一个 .exe 文件,运行 Kestrel 服务器通过执行此 exe 文件并在浏览器中加载页面。

我在 VS 2019 中创建了一个 ASP.NET Core 5 模板应用程序。然后使用 cli 我 运行 这个命令:

dotnet publish -c Release -r win-x64 /p:PublishSingleFile=true \n 
/p:IncludeNativeLibrariesForSelfExtract=true --self-contained true

这会生成一个 exe 文件,当我将其复制到其他地方时,运行s 没有问题。 但是当我浏览页面时,加载了 none 个静态文件:

生成单个文件 asp.net 核心应用程序的正确方法是什么,以便它加载静态内容?

编辑

根据要求,将发布后的输出截图放在这里

编辑 2

要获得可复制的项目:

Visual Studio 2019 -> 新解决方案 -> ASP.NET 具有以下配置的核心 Web 应用程序

编辑 3

感谢@JHBonarius 的回答,我更改了 Program.cs 以将 ContentRoot 设置为提取 wwwroot 内容的临时文件夹。

public class Program
    {
        public static void Main(string[] args)
        {
            var path = Path.Combine(Path.GetTempPath(), ".net", typeof(Program).Assembly.GetName().Name);

            var directory = 
                Directory
                    .GetDirectories(path)
                    .Select(path => new DirectoryInfo(path))
                    .OrderByDescending(di => di.LastWriteTime)
                    .First();

            CreateHostBuilder(args)
                .UseContentRoot(directory.FullName)
                .Build()
                .Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)            
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

虽然我很欣赏这看起来像是一个 hack(我找不到任何关于此路径的官方文档),但我想要一些工作代码。

通过这些更改,页面现在加载静态资源,但不是全部。

这是解决方案资源管理器中 wwwroot 文件夹的内容

这是临时路径wwwroot文件夹中提取的内容

可以看出 js/css 文件夹以及 jquery-validationjquery-validation-unobtrusive 文件夹完全丢失。

有什么线索吗?

我创建了一个包含最新更改的 github repo

我试图在新的 asp net core 空项目上重现你的问题,它工作正常。

可能 启动 缺少一些配置。

这是我所做的。

csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
</Project>

Startup.cs

public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseDefaultFiles();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints => {});
        }
    }

wwwroot

与你的场景类似,还有bootstrap。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>TEST</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <script src="js/bootstrap.min.js"></script>
</body>
</html>

-- 2021 年 11 月 29 日更新

My expectation was that .exe will be a self-contained bundle and upon execution it would extract static files and be able to resolve references to it. That doesn't seem to be the case.

那么你必须将 wwwroot 中的所有内容都标记为嵌入资源

<PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
   <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>

<ItemGroup>
    <Compile Remove="wwwroot\**" />
    <Content Remove="wwwroot\**" />
     <EmbeddedResource Include="wwwroot\**\*">
      <Link>wwwroot\%(RecursiveDir)%(Filename)%(Extension)</Link>
      <LogicalName>wwwroot\%(RecursiveDir)%(Filename)%(Extension) 
      </LogicalName>
    </EmbeddedResource>
    <None Remove="wwwroot\**" />
</ItemGroup>

并在Configure(IApplicationBuilder, IWebHostEnvironment)方法中执行文件提取

var basePath = AppDomain.CurrentDomain.BaseDirectory;
var wwwrootPath = Path.Combine(basePath, "wwwroot");

if (!Directory.Exists(wwwrootPath))
{
    var assembly = typeof(Startup).Assembly;
    Directory.CreateDirectory(wwwrootPath);

    var resourcePaths = assembly.GetManifestResourceNames()
        .Where(rnn => rnn.Contains("wwwroot"))
        .ToList();

    foreach (var resourcePath in resourcePaths)
    {
        var fileName = resourcePath;
        var filePath = Path.Combine(basePath, fileName);
        var fileInfo = new System.IO.FileInfo(filePath);
        fileInfo.Directory.Create();
        using var stream = File.Create(filePath);
        using var resourceStream = assembly.GetManifestResourceStream(resourcePath);

        resourceStream.CopyTo(stream);
    }
};

如果您不喜欢这种方法,您可以考虑 EmbeddedFileProvider

Startup.cs:配置

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new EmbeddedFileProvider(
        assembly: typeof(Startup).Assembly, 
        baseNamespace: "TestApp.wwwroot"),
});

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/", async http => { http.Response.Redirect("/index.html"); });
});

csproj

<EmbeddedResource Include="wwwroot\**\*" />

我可能会转载你的问题。

我发布了项目,并在一个不相关的文件夹打开命令行,用完整路径打开exe。那么静态资源就不会加载了。

您可以尝试双击exe,看看是否可以运行。

解决问题:

在Program.cs

中添加Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);

我找到了解决方案here

将以下内容添加到 csproj

<ItemGroup>
    <Content Update="wwwroot\**" ExcludeFromSingleFile="false">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
</ItemGroup>

因为发生的事情是 .exe 包含一个存档,它被提取到一个临时目录。

但是,这仅适用于 .NET Core (3.1)。 They changed the behavior in .NET 5, where most dependencies are now directly loaded from the .exe or memory。所以内容现在被认为是外部依赖,内容路径设置为执行目录,而不是临时目录。

我说的方案还是会在.exe中包含wwwroot目录,然后解压到临时路径,但是由于内容路径没有指向那里,所以找不到内容

您可以使用 .UseWebRoot("[path]") 更改内容路径。但是,我还没有找到获取临时目录路径的方法。

编辑:还有一个完整的其他选项:将 wwwroot 文件放入一个 zip 文件中,将 zip 文件添加为嵌入式资源,并使用静态文件提供程序提供它。

根据请求,所有静态文件都在没有 wwwroot 路径的情况下请求,因此要么配置您的 html & js 并在路径 中添加 wwwroot,要么显式映射 wwwroot 内容,如下所示:

app.UseStaticFiles(new StaticFileOptions()
{
    FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot")),
    RequestPath = new PathString("/wwwroot")  // OR use "/" to map to base URI
});