question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Blazor UseBlazorFrameworkFiles and IFileProvider

See original GitHub issue

Is your feature request related to a problem? Please describe.

I have multiple blazor SPA’s referenced from the aspnet core host. I need to conditionally serve up the correct blazor SPA on the root request path “/” based on who the tenant is (determined by the URL).

I could do this in 3.2 preview 1 by configuring the tenant specific fork of the middleware pipeline to serve that tenants blazor spa files only:

if(isTenantA)
{
     app.UseClientSideBlazorFiles<ClientA.Program>() 
    /// stuff 
    endpoints.MapFallbackToClientSideBlazor<ClientA.Program>("index.html") 
    
}
else
{
     app.UseClientSideBlazorFiles<ClientB.Program>() 
    /// stuff 
    endpoints.MapFallbackToClientSideBlazor<ClientB.Program>("index.html") 
}

However with 3.2 preview 2 and the move to static files this has proved to be not so easy. Firstly - all referenced SPA’s are being served on their static asset base paths automatically - you can’t pick and chose to only serve one of the other based on the tenant. To resolve this first problem i have already raised “#20605 - don’t serve static assets in directories prefixed with dot”. However even with that in place, I then need the ability to set up middleware to serve just a particular SPA - so that I can fork the middleware pipeline per tenant, and each tenant can have this middleware serve them there SPA.

The first issue I hit was that UseBlazorFrameworkFiles doesn’t allow me to pass in a particular IFileProvider - instead UseBlazorFrameworkFiles is coupled to the Environment.WebRootFileProvider - so it basically means all SPA’s must be resolvable by the Environment.WebRootFileProvider - in my case I want only some SPA’s to be visible to particular tenants, so having them all resolvable via the Environment.WebRootFileProvider doesn’t really work.

Here is the code I needed to serve up just a particular SPA on a particular path “/” in an isolated fashion (i.e IWebHostEnvironment.WebRootFileProvider cannot resolve these files as they are published to hidden" directories)

                IFileProvider staticFileProvider = spa.FileProvider; // I built this to point to SPA content in the "hidden" folder.
                var basePathPrefix = "/"; // the path you want to serve the SPA static content on. This may be different than the StaticAssetBasePath because multiple different SPA's must have different StaticAssetBasePath but you might want to serve any of them up on the root "/" based on who the tenant is.
               
                    // We have to serve blazor _framework static content up with special options.
                    c.MapWhen(ctx => IsBlazorFrameworkFileRequest(basePathPrefix, ctx), subBuilder =>
                    {                        
                        StaticFileOptions options = GetBlazorFrameworkStaticFileOptions(tenantDefaultSpa);
                        subBuilder.UseMiddleware<ContentEncodingNegotiator>(staticFileProvider);
                        subBuilder.UseStaticFiles(options);
                    });              

                // now serve non blazor framework specific content using ordinary options.
                var spaFileOptions = new StaticFileOptions() { FileProvider = staticFileProvider };
                c.UseStaticFiles(spaFileOptions);

Note: The entire MapWhen() portion was copied from the internals of UseBlazorFrameworkFiles() but modified to allow me to pass the IFileProvider around instead of using Environment.WebRootFileProvider. everywhere.

Note also that I am serving “_framework” files for _framework requests, and if not a “_framework” request, i’m serving the SPA’s file using ordinary static options - this handles serving the whole of a blazor application. It would be helpful in my view if this logic was just wrapped up into a single higher level UseBlazorSpa() method that combines all the necessaries to just serve a particular blazor SPA.

Describe the solution you’d like

I’d like to see at minimum:

  • UseBlazorFrameworkFiles accept an optional IFileProvider as a parameter (that it also passes to ContentEncodingNegotiator etc) to allow using a specific IFileProvider to serve specific SPA content, as opposed to what it does now which is only use IWebHostEnvironment.WebRootFileProvider.

However for ease of use, it would also be good to have:

  • UseBlazorSpa(baseRequestPath, staticAssetsBasePath)
  • UseBlazorSpa(baseRequestPath, fileProvider)

which would do the following for you:

  1. Construct an IFileProvider (if one not provided) to serve only the SPA with the specified static asset base path
    • In development environments, uses the static assets mappings xml file, in non development, resolves relative to the webroot directory.
  2. Does what UseBlazorFrameworkFiles() does, but uses the specified file provider to only serve “_framework” files from that SPA using MapWhen to detect _framework requests and configuring StaticFileMiddleware appropriately.
  3. Does UseStaticFiles() as I have shown above to serve the “oridinary” (non _framework) files for the SPA using the specified IFileProvider.

This would make serving a blazor SPA up as simple as:

appBuilder.UseBlazorSpa("/", "./apps/welcome-app");

  • The first argument is the base request path to serve the SPA on - this allows it to be different from the static asset path which is something I don’t think the current API’s enable but is super useful.
  • The second argument corresponds to the <StaticWebAssetBasePath>./apps/welcome-app</StaticWebAssetBasePath> in the client csproj.

it would be easy to fork the middleware pipleline to serve different apps to different tenants. In conjunction with #20605 which allows referenced SPA projects to be hidden from being served by default this would be quite a cool feature and gets us back to what we could achieve in preview 1 but I think in a nicer way.

Additional context

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
dazinatorcommented, Apr 8, 2020

Just a note to say that in my enhancements branch I’ve made some more changes and now all that is required is this:

               app.MapWhen(ctx => ctx.Request.Host.Host.Equals("foo.localhost"),
               (app) =>
               {                   
                   app.UseBlazorSpa("/app2", ".private/spa2", Configuration, "/index.html");
               });

That’s it… that lets you serve any blazor spa (static asset path), on any request path (in this case /app2 but it could also be the root), and optionally enforces the fallback file route in one go. If you don’t want the fallback file to be enforced there and then (i.e becuase your app doesnt fully own that URL space and you need to give the server the chance to handle requests in that URL space first, then you don’t need to provide the last argument “fallbackFilePath”, and you can use endpoint routing as before.

app.MapWhen((ctx) =>
            {
                return ctx.Request.Host.Host.Equals("localhost");
            },
                (app) =>
                {
                    var files = app.UseBlazorSpa("/", ".private/spa1", Configuration);

                    app.UseRouting();
                    app.UseEndpoints(endpoints =>
                    {
                        endpoints.MapControllers();
                        endpoints.MapFallbackToFile("index.html",
                            new StaticFileOptions() { FileProvider = files });
                    });
                });

I’m so pleased with this that I might package it up into a nuget package. Next on my agenda is to also try the path re-write approach.

1reaction
javiercncommented, Apr 8, 2020

How do I restore your project though as it seems to require preview4 packages which aren’t available on my nuget feeds - assume I need to add a nuget feed source?

It should work with preview3 stuff.

I didn’t update the base paths of the apps. Those should be “/” on their respectives “index.html” files.

The path rewriting should be fine, if you just undo it after the static files middleware. Routing, controllers, etc would use the original path from “/”. I haven’t tried E2E the host version, since I don’t want to mess with my host files, but I’m pretty confident it should work.

Read more comments on GitHub >

github_iconTop Results From Across the Web

ComponentsWebAssemblyAppli...
UseBlazorFrameworkFiles (IApplicationBuilder, PathString). Configures the application to serve Blazor WebAssembly framework files from the path pathPrefix .
Read more >
c# - Upgrade from .NET 6.0.0-preview.3.21201.13 to ...
UseBlazorFrameworkFiles (); causes error ... AddSingleton<IFileProvider>(new PhysicalFileProvider(Path.Combine(Directory.
Read more >
Create a Blazor WebAssembly Dashboard Application
View Example: Get Started - Dashboard Component in Blazor WebAssembly Application ... CreateBuilder(args); IFileProvider fileProvider = builder.Environment.
Read more >
How do I add a Blazor WebAssembly project to an existing ...
This FAQ explains the topic "How do I add a Blazor WebAssembly project to an existing ASP.NET Core application?"
Read more >
Hosting Blazor WebAssembly on ASP.Net Core WebAPI
Simple steps to host an already created Blazor Web Assembly project in ASP.Net core wen project. ... UseBlazorFrameworkFiles();.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found