Blazor UseBlazorFrameworkFiles and IFileProvider
See original GitHub issueIs 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:
- 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.
- 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. - 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:
- Created 3 years ago
- Comments:8 (3 by maintainers)
Top GitHub Comments
Just a note to say that in my enhancements branch I’ve made some more changes and now all that is required is this:
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.
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.
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.