Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm trying to create a single file asp.net core 5 web app. Goal is to have a single .exe file, run the Kestrel server by executing this exe file and load the page in the browser.

I created an ASP.NET Core 5 template app in VS 2019. Then using cli I run this command:

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

This generates an exe file, which when I copy elsewhere, runs without a problem. But when I browse the page, none of the static files are loaded:

enter image description here

What would be the proper way of generating a single file asp.net core app, so it loads static content ?

EDIT

As requested, putting here the screenshot of the output after the publish

enter image description here

EDIT 2

To get a reproducible project:

Visual Studio 2019 -> New Solution -> ASP.NET Core Web App with the configuration below

enter image description here

EDIT 3

Thanks to the answer by @JHBonarius, I changed the Program.cs to set ContentRoot to a temp folder where wwwroot content is getting extracted.

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

While I appreciate that this seems like a hack (I couldn't find any official documentation for this path), I wanted to have some working code.

With these changes page now loads static resources, not all of them though.

This is the content of wwwroot folder in the solution explorer

enter image description here

And this is the content of extracted wwwroot folder on the temp path

enter image description here enter image description here

As can be seen js/css folders are missing altogether as well as jquery-validation & jquery-validation-unobtrusive folders.

Any clue what's going on ?

I created a github repo with latest changes.

like image 606
Mike Avatar asked Nov 10 '21 15:11

Mike


People also ask

Which static files can ASP.NET Core application serve?

Static files, such as HTML, CSS, images, and JavaScript, are assets an ASP.NET Core app serves directly to clients by default.

What is the default directory for static files in ASP.NET Core?

Static files are stored within the project's web root directory. The default directory is {content root}/wwwroot , but it can be changed with the UseWebRoot method. For more information, see Content root and Web root.

Can .NET Core HTTP pipeline be configured to server static files?

To serve static files from an ASP.NET Core app, you must configure static files middleware. With static files middleware configured, an ASP.NET Core app will serve all files located in a certain folder (typically /wwwroot).


2 Answers

I tried to reproduce your problem on new asp net core empty project and it works fine.

Perhaps Startup is missing some configuration.

Here's what I did.

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

Similar to your scenario, there is also bootstrap.

wwwroot content

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>

-- Update 29/11/2021

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.

Well, then you'd have to mark everything in wwwroot as embedded resource

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

And perform file extraction in Configure(IApplicationBuilder, IWebHostEnvironment) method

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

If you don't like this approach you might consider EmbeddedFileProvider

Startup.cs:Configure

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\**\*" />
like image 123
Józef Podlecki Avatar answered Oct 19 '22 18:10

Józef Podlecki


I found the solution here

Add the following to the csproj

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

Because what happens, is that the .exe contains an archive, which is extracted to a temporary directory.

However, this only works for .NET Core (3.1). They changed the behavior in .NET 5, where most dependencies are now directly loaded from the .exe or memory. So the content is now considered an external dependency, and the content path is set to the excuting directory, instead of the temporary directory.

The solution I mention will still include the wwwroot directory in the .exe and extract it to the temporary path, but since the content path is not pointing there, the content is not found.

You can change the content path using .UseWebRoot("[path]"). However, I haven't found a way to get the path of the temp directory.

edit: there's a whole other option: put the wwwroot files in a zip file, add the zip file as Embedded Resource, and serve it using a static file provider.

like image 3
JHBonarius Avatar answered Oct 19 '22 18:10

JHBonarius