Using .NET 9 and a Blazor web app (with server interactive rendering mode), I'm struggling to get static assets (or CSS isolation bundles) from a Razor class library to work with my Blazor web app.
I have followed Microsoft's instructions and my .razor
file has a .razor.css
as a child. In addition I've created a wwwroot
with a styles2.css
file in it.
The RCL is dynamically loaded through
app.MapRazorComponents<App>().AddAdditionalAssemblies()
The RCL is definitely loaded as a page defined in Components/Pages/MyPage.razor
in the RCL, using @page /myurl
, is accessible from /myurl
.
When running the app I can find neither the bundle nor the CSS file. I've tried various version of the supposed syntax, but none work:
_content/MyRclAssemblyName/MyRclAssemblyName.bundle.scp.css
_content/MyRclAssemblyName/styles2.css
MyRclAssemblyName/MyRclAssemblyName.bundle.scp.css
MyRclAssemblyName/styles2.css
Adding this to the WebApplicationBuilder
has no effect:
builder.WebHost.UseWebRoot("wwwroot");
builder.WebHost.UseStaticWebAssets();
Though this is not really surprising as per the documentation
running the consuming app from build output (dotnet run), static web assets are enabled by default in the Development environment.
My suspicion is that because this is added through .AddAdditionalAssemblies()
that this creates some weird scenario where you need additional configuration that is not documented or hidden in a way that I've overlooked it.
How can I use static assets, or CSS bundles, from a Razor class library that is dynamically loaded through .AddAdditionAssembles()
and not referenced directly?
What I'm asking for doesn't seem possible for dynamically loaded RCLs according to this issue: https://github.com/dotnet/aspnetcore/issues/33284 It is a few years old, but seems to still hold true: Feel free to add answers if this changes.
What I ended up doing was marking all content in my wwwroot
folder as "Embedded resource", and creating a middleware to serve the content.
Making files an "Embedded resource" looks like this in the .csproj
file:
<ItemGroup>
<Content Remove="wwwroot\styles.css" />
<EmbeddedResource Include="wwwroot\styles.css" />
</ItemGroup>
This is the middleware (code is shortened for brevity. Add null
checks and other validation as needed!):
public class EmbeddedResourceMiddleware(RequestDelegate next)
{
private readonly string _prefix = "_embeddedResource";
public async Task Invoke(HttpContext context)
{
if (!context.Request.Path.Value.StartsWith($"/{_prefix}/")))
{
await next(context);
return;
}
string[] pathSegments = context.Request.Path.Value.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
string assemblyName = pathSegments[1];
Assembly assembly = GetYourAssemblyHere(assemblyName);
string filePath = string.Join('/', pathSegments.Skip(2));
string resourceName = "wwwroot." + filePath.Replace('/', '.');
string resourceFullName = assembly.GetManifestResourceNames()
.FirstOrDefault(name => name.EndsWith(resourceName));
await using Stream stream = assembly.GetManifestResourceStream(resourceFullName);
string contentType = GetContentType(resourceName);
context.Response.ContentType = contentType;
await stream.CopyToAsync(context.Response.Body);
}
private static string GetContentType(string fileName)
{
var provider = new FileExtensionContentTypeProvider();
if (!provider.TryGetContentType(fileName, out string contentType))
{
contentType = "application/octet-stream";
}
return contentType;
}
}
And inserted it into the middleware pipeline. Inserted right after UseStaticFiles()
should hopefully allow it to behave just like any other static file:
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
...
app.UseStaticFiles()
app.UseMiddleware<EmbeddedResourceMiddleware>();
...
Now I can make a request to /_embeddedResource/MyRclAssemblyName/styles.css
and get the stylesheet from the wwwroot
folder in my RCL.