In this post we use Sassy CSS to style our Blazor components and we will integrate Web Optimizer to compile these files to a single file site.css.

Web Optimizer is an ASP.Net Core middleware for bundling and minification of CSS and JavaScript files at runtime. With full server-side and client-side caching to ensure high performance. No complicated build process and no hassle.

In our server side .Net Core 3.0 Blazor project we got the following project structure

  • BlazorStyling.WebOptimizer
    • wwwroot/
      • css/
    • Components/
      • Progress.razor
      • Progress.scss
    • Pages/
      • _Host.cshtml
      • Index.razor
    • Shared/
      • Helper.scss
      • MainLayout.razor
    • _Imports.razor
    • App.razor
    • App.scss
    • appsettings.json
    • Program.cs
    • Startup.cs

We use SCSS to style a progress bar Blazor component Progress.razor

<div class="progress">
    <div class="progress-bar @BackgroundClass"
         role="progressbar"
         style="width: @Width"
         aria-valuenow="@ValueNow"
         aria-valuemin="0"
         aria-valuemax="100">
        @if (!string.IsNullOrWhiteSpace(Label))
        {
            @string.Format(Label, ValueNow)
        }
    </div>
</div>

@code {
    public enum BackgroundColor { Red, Yellow, Green }

    [Parameter]
    public int ValueNow { get; set; }
    [Parameter]
    public string Label { get; set; }
    [Parameter]
    public BackgroundColor BgColor { get; set; } = BackgroundColor.Green;

    private string BackgroundClass => $"bg-{BgColor.ToString().ToLower()}";
    private string Width => $"{ValueNow}%";
}

with its style Progress.scss.

.progress {
    display: flex;
    height: 1rem;
    overflow: hidden;
    font-size: .75rem;
    background-color: #e9ecef;
    border-radius: .25rem;
}

.progress-bar {
    display: flex;
    flex-direction: column;
    justify-content: center;
    color: #fff;
    text-align: center;
    white-space: nowrap;
    background-color: #007bff;
    transition: width .6s ease;
}

$bg-green: #28a745;
$bg-yellow: #ffc107;
$bg-red: #dc3545;

.bg {
    &-green {
        background-color: $bg-green;
    }

    &-yellow {
        background-color: $bg-yellow;
    }

    &-red {
        background-color: $bg-red;
    }
}

The index page Index.razor just renders the progress bar components

@page "/"

<div class="mt-1">
    <Progress ValueNow="ValueNow" BgColor="Progress.BackgroundColor.Green" />
</div>
<div class="mt-1">
    <Progress ValueNow="ValueNow" BgColor="Progress.BackgroundColor.Red" />
</div>
<div class="mt-1">
    <Progress ValueNow="ValueNow" BgColor="Progress.BackgroundColor.Yellow" />
</div>
<div class="mt-1">
    <Progress ValueNow="ValueNow" BgColor="Progress.BackgroundColor.Green" Label="we got {0}%" />
</div>
<div class="mt-1">
    <Progress ValueNow="ValueNow" BgColor="Progress.BackgroundColor.Red" Label="finally {0}%" />
</div>
<div class="mt-1">
    <Progress ValueNow="ValueNow" BgColor="Progress.BackgroundColor.Yellow" Label="{0}%" />
</div>

@code {
    const int From = 0, To = 100;
    int ValueNow { get; set; } = 50;

    protected override Task OnAfterRenderAsync(bool firstRender)
        => InvokeAsync(async () =>
        {
            while (true)
            {
                await System.Threading.Tasks.Task.Delay(2000);
                ValueNow = new Random().Next(From, To);
                StateHasChanged();
            }
        });
}

and we need a root SCSS file App.scss to combine all other SCSS files.

@import 'Components/Progress';
@import 'Shared/Helper';

The MainLayout.razor is nearly empty

@inherits LayoutComponentBase

@Body

and our Host.cshtml just renders the app and includes the compiled css.

@page "/"
@namespace BlazorStyling.WebCompiler.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>BlazorStyling.WebCompiler</title>
    <base href="~/" />
    <environment include="Development">
        <link href="css/site.css" rel="stylesheet" />
    </environment>
    <environment exclude="Development">
        <link href="css/site.min.css" rel="stylesheet" />
    </environment>
</head>
<body>
    <app>
        @(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
    </app>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

With this set up, we can install the NuGet packages LigerShark.WebOptimizer.Core and LigerShark.WebOptimizer.Sass.

PM> Install-Package LigerShark.WebOptimizer.Core
PM> Install-Package LigerShark.WebOptimizer.Sass

To add the middleware we have to add the Web Compiler services to our service collection in Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddWebOptimizer(pipeline =>
    {
        // UseContentRoot only sets the root of the source files
        pipeline.AddScssBundle("/css/site.css", "App.scss").UseContentRoot();
    });
}

and add Web Optimizer to the request execution pipeline also in Startup.cs.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
        app.UseDeveloperExceptionPage();
    else
        app.UseHsts();

    app.UseHttpsRedirection();
    app.UseWebOptimizer();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
    });
}

This project is available on GitHub.