Search code examples
swaggerblazorswashbuckle.aspnetcore

Swashbuckle.AspNetCore + Blazor - Dynamically Add/Remove custom .css file at runtime


I have Blazor Webassembly ASP.NET Core hosted and I installed Swashbuckle.AspNetCore to display endpoints that my Blazor app has (/swagger endpoint).
My Startup.Configure looks like this (only swagger part):

            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                foreach (var description in provider.ApiVersionDescriptions)
                {
                    c.SwaggerEndpoint($"{description.GroupName}/swagger.json", $"v{description.GroupName.ToUpperInvariant()}");
                }

                c.InjectStylesheet("/css/swaggerDark.css");
            });

As you can see, I inject custom .css file which works.

In my Blazor app, I inject swagger so my page looks like this (.razor page):

<iframe src="swagger"/>

Again, it works correctly, swagger documentation is displayed and it has dark theme.

I have noticed (to no suprise) that this iframe has a link to this .css file:

<link href="/css/swaggerDark.css" rel="stylesheet" media="screen" type="text/css">

Removing this link brings the default swagger look (light theme).

The user of my app can choose which theme he wants (light/dark) of the whole application. My question is, how do I dynamically inject/remove (or maybe enable/disable) this .css file so depending on which app theme the user chooses, the swagger will either display default (light) or dark theme (using that .css file)?

I couldn't find any relevant info on this issue so I decided to create this question. I appreciate any help. Thank you.


Solution

  • Ok, I figured it out. The answer is: use JsInterop.

    My .razor page looks like this at the moment:

    @page "/something"
    @inject IJSRuntime JS //needed to call InvokeVoidAsync
    
        //I made my own ThemeManager to control the Blazor app theme
        @if (ThemeManager.IsDefaultTheme)
        {
            <iframe id="myiframe" src="swagger" @onload="() => ToggleSwaggerTheme(true)" />
        }
        else
        {
            <iframe id="myiframe" src="swagger" @onload="() => ToggleSwaggerTheme(false)" />
        }
    
    @code {
        private async Task ToggleSwaggerTheme(bool isLight) => await JS.InvokeVoidAsync("toggleSwaggerTheme", isLight);
    }
    
    

    I made it so the iframe toggles depending on the app theme. The ToggleSwaggerTheme function is self-explanatory - I'm calling my JS function to toggle the theme of swagger.

    In index.html I added a script to load my helperFunctions.js (in body) in which toggleSwaggerTheme function can be found:

    <script src="/js/helperFunctions.js"></script>
    

    I added helperFunctions.js in my wwwroot:
    wwwroot -> js -> helperFunctions.js

    My helperFunctions.js looks like this:

    function toggleSwaggerTheme(isLight) {
        let myiframe = document.getElementById('myiframe');
        let styleSheets = myiframe.contentWindow.document.styleSheets;
        for (let i = 0; i < styleSheets.length; i++) {
            if (styleSheets[i].href == null) {
                continue;
            }
    
            if (styleSheets[i].href.includes("/css/swaggerDark.css")) {
                styleSheets[i].disabled = isLight;
                break;
            }
        }
    }
    

    The above function allowed me to toggle the swagger theme. Please note, I don't know if this is the best solution that can be created. Also, I'm not sure how it's going to behave in the production environment. I will update this answer if there are any problems in the production. I hope it can help somebody.

    UPDATE AFTER GOING LIVE:

    I've had some issues with displaying dark theme after going live. But the following seems to fix the issue: In Program.cs (Server side), in CreateHostBuilder method I added (not sure if it's needed):

        webBuilder.UseStaticWebAssets();
    

    So in my case, it looks like this:

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStaticWebAssets();
                    webBuilder.UseStartup<Startup>();
                });
    

    I also changed my swaggerDark.css file property: the Copy to Output Directory to Copy always. You do this by right-clicking the file in Solution Explorer -> Properties (at the end).
    After doing these new steps, It seems to be working fine. I hope it can help somebody.