OrchardCMS / OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
https://orchardcore.net
BSD 3-Clause "New" or "Revised" License
7.42k stars 2.39k forks source link

Serving static files from module without URL prefix #11407

Closed hieucd04 closed 2 years ago

hieucd04 commented 2 years ago

Describe the bug

To Reproduce

Steps to reproduce:

  1. Create a new tenant named Storybook
  2. Create a new module named Orchard.Storybook
  3. Copy Storybook build artifacts, which are Html, CSS, JS files, to wwwroot folder of Orchard.Storybook module
  4. Run the site and enable Orchard.Storybook module inside Storybook tenant

Actual behavior

I can access storybook via http://localhost:5002/Orchard.Storybook/index.html

Expected behavior

I want to access storybook via http://localhost:5002/ Is there any way to remove the prefix when serving static files from a module?

More info

ns8482e commented 2 years ago

@hieucd04 you can enable default files by adding app.UseDefaultFiles()

hieucd04 commented 2 years ago

@ns8482e I just tried it and to my surprise, it didn't work!

public class Startup : StartupBase
{
    public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
    {
        app.UseFileServer(new FileServerOptions
        {
            RequestPath = "/",
            FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "__dist__")),
            EnableDefaultFiles = true
        });
    }
}

I tried several options, including UseDefaultFiles() & UseStaticFiles() but the result is the same.


However, even if the /index.html can be removed it only solves half of my problem. The prefix /Orchard.Storybook still persists, though.

ns8482e commented 2 years ago

The static files for Module is served from module based on if request path contains the given module. The root static files are provided from Host. that's why it still needs Orchard.Storybook to locate the file

ns8482e commented 2 years ago

@hieucd04 if you need to serve static files ( css, js, images) from root, have your assets placed in wwwroot of your Host project.

However if you are concerned only about default documents (e.g. index.html to served as ~/) for SPA purpose, then I'd suggest instead of index.html to have _host.cshtml in Pages folder of your module with route @page '~/'

hieucd04 commented 2 years ago

@ns8482e

if you need to serve static files ( css, js, images) from root, have your assets placed in wwwroot of your Host project

Yeah, I did think about it. However, if I place it in the Host project (a.k.a Application) then it will be available to all tenants which is not what I want.

I want a dedicated tenant (with domain name: storybook.mydomain.com) just for my Storybook.

ns8482e commented 2 years ago

Suggestion:

Let your static file served from Orchard.Storybook e.g.

Have Pages/_Host.cshtml with home route ~/ and define static references in _Host.cshtml this way Hompage will be served from your module for tenant that has module enabled.

And, content will not be served if module is disabled for a given tenant.

hieucd04 commented 2 years ago

@ns8482e Yeah, to be honest, that's currently my temporary solution while waiting for help from GitHub. My approach is a bit different as I use a HomeController instead of a Razor Page. But still, I believe they produce the same result.

However, using MVC (or Razor Page) to serve static files ... it just feels wrong to me 😄 (just a feeling). I think I will leave this issue open for a couple more days and if there are no other comments I will accept the MVC / Razor Page solution as a permanent one and close this.

Thank you for your help! Appreciate it!

ns8482e commented 2 years ago

However, using MVC (or Razor Page) to serve static files

Use of MVC/Razor page is to serve the document with custom endpoint routing.

I don’t know if endpoint routing is possible for Staticfiles in asp.net

May be you can use app.Use to customize behavior for static files

hieucd04 commented 2 years ago

After digging through Orchard Core source code, I ended up writing my own FileProvider to serve static files. I'm leaving the code here for anyone who might need it:

Startup.cs

public class Startup : StartupBase
{
    public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
    {
        app.UseFileServer(new FileServerOptions
        {
            FileProvider = new StorybookFileProvider(serviceProvider.GetRequiredService<IApplicationContext>())
        });
    }
}

And my custom FileProvider:

internal class StorybookFileProvider : IFileProvider
{
    readonly Module _module;

    static string IndexFilePath => "index.html";

    public StorybookFileProvider(IApplicationContext applicationContext)
    {
        var application = applicationContext.Application;
        _module = application.GetModule(GetType().Assembly.GetName().Name);
    }

    public IDirectoryContents GetDirectoryContents(string subpath)
    {
        return string.IsNullOrEmpty(NormalizePath(subpath))
            ? new EmbeddedDirectoryContents(new[] { _module.GetFileInfo(IndexFilePath) })
            : NotFoundDirectoryContents.Singleton;
    }

    public IFileInfo GetFileInfo(string subpath)
    {
        var fileSubPath = Path.Combine(Module.WebRoot, NormalizePath(subpath) ?? IndexFilePath);
        return _module.GetFileInfo(fileSubPath);
    }

    public IChangeToken Watch(string filter) { return NullChangeToken.Singleton; }

    static string NormalizePath(string path) => path?.Replace('\\', '/').Trim('/').Replace("//", "/");
}