Bootstrap+Blazor essentials

Bootstrap+Blazor essentials

·

11 min read

When you start a new Blazor project it will quickstart you with a default Bootstrap template. But when you spent several weeks or months on the project you starting to notice that there is a lot of duplicate HTML code that can be refactored and moved into components.

In this post, I want to give you some advice on how and which Bootstrap components you can wrap into .razor components to make your code simpler, easier to maintain, and expandable.

📥 Sources to this article

All these components are alterations of what I use every day in my projects. Why not share the same? In real projects, there is usually some CSS template used that could alter the component significantly.

These examples use clean Bootstrap and can be the foundation that could be altered for your CSS template. That's why I usually just copy-paste them across projects and tune to fit the current CSS template, instead of moving them to separate NuGet package;

Bootstrap has more than 20 components, but for this article, I keep only the ones that used very often and make a great example for you where to go from hereby.

  • Button
  • Button Group
  • Card
  • Progress
  • Spinner
  • Toast
  • Modals

Next time - Form and Form Components in Blazor with MVVM

Button

The most basic component is a button. Because I use MVVM my button is based on the ICommand interface

@using System.Windows.Input

<button type="button" class="@($"btn {Class}")" @onclick="Callback" @attributes="InputAttributes">@ChildContent</button>

@code {
    [Parameter]
    public ICommand Command { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public string Class { get; set; } = "btn-primary";

    [Parameter]
    public object Parameter { get; set; }

    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; } = new Dictionary<string, object>();

    private void Callback()
    {
        Command?.Execute(Parameter);
    }
}

When you add action to button's ICommand AddCommand = new Command<int>(DoAdd); take notice of the action

private void DoAdd(int p)
{
    Value += p;
    StateHasChanged(); // <--- not gonna work without it
}

I found out that onaction will re-render only the component it belongs to. Even if actual action changes something from another (parent) component. But when you use ViewModel there is no need in StateHasChanged call here because the View should be subscribed to INotifyPropertyChanged anyway.

Button Group

Button group wraps several command buttons in a single neat component. I usually use it for data table editing Alt Text

@using System.Windows.Input

<div class="btn-group" role="group" aria-label="Basic example">
    <CommandButton class="btn-sm btn-primary" Command="EditCommand" Parameter="Parameter">Edit</CommandButton>
    <CommandButton class="btn-sm btn-danger" Command="DeleteCommand" Parameter="Parameter">Delete</CommandButton>
</div>

@code {
    [Parameter]
    public ICommand EditCommand { get; set; }
    [Parameter]
    public ICommand DeleteCommand { get; set; }
    [Parameter]
    public object Parameter { get; set; }
}

The component is rather simple, you can use icons instead of text for a better look, but the main thing that it nicely fits Blazor's way of building tables. You could pass commands and current items to ViewModel for processing. If you have a lot of tables in your app that saves tons of space also, you get consistency across tables and can easily adapt styles in the future.

<tbody>
    @foreach (var item in Items)
    {
        <tr>
            <td>@item</td>
            <td><RowButtonGroup 
                    EditCommand="EditItemCommand" 
                    DeleteCommand="DeleteItemCommand" 
                    Parameter="item"/></td>
        </tr>
    }
</tbody>

Card

For me, the card is the primary component of any web app. This component wrap most of the basic HTML card into several optional RenderFragments. You can expand it further to add an optional card-image or list-group block.

I noticed that some folks missing the fact that you can use several RenderFragment inside your component. Well, you can and it allows you to use your components like that

<Card Title="Special title treatment">
    <Body>
        <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
        <a href="#" class="btn btn-primary">Go somewhere</a>
    </Body>
    <Footer>
        2 days ago
    </Footer>
</Card>

Please note

[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; } 
    = new Dictionary<string, object>();

This parameter is used on the card to catch-all unmatched attributes that you want to assign to it <div class="card @Class" @attributes="InputAttributes">. It's usually a good practice to include such catch-all property to every custom component. Saves some time on unmatched exceptions later. But be aware where you put it, for some components, it might be wise to apply attributes to the child or not apply it at all.

Progress Bar

Small component but I want to show it here as an example of the power of Blazor components and proper MVVM usage.

<div class="progress @Class" style="@(Height == null ? "" : $"height: {Height}px")">
  <div class="progress-bar" role="progressbar" style="@($"width: {Progress}%")" aria-valuenow="@Progress" aria-valuemin="0" aria-valuemax="100">@ChildContent</div>
</div>

@code {
    [Parameter]
    public string Class {get;set;}
    [Parameter]
    public int Progress {get;set;}
    [Parameter]
    public int? Height {get;set;}
    [Parameter]
    public RenderFragment ChildContent { get; set; }
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; } = new Dictionary<string, object>();
}

Progress Bar with MVVM Example

Let's dig into the example of usage of ProgressBar with backing it ViewModel.

View

<h2 class="mt-3">Interactive (with VM)</h2>
<ProgressBar Progress="@DataContext.Progress" Class="mt-2 mb-2">@((DataContext.Progress/100f).ToString("P0"))</ProgressBar>
<CommandButton class="btn-primary" Command="@DataContext.Add">+</CommandButton>
<CommandButton class="btn-secondary" Command="@DataContext.Subtract">-</CommandButton>

As you can see View is quite simple: we have progress which is transformed like that @((DataContext.Progress/100f).ToString("P0")) for display only and couple buttons with commands Add and Subtract. These actions are calculated in ViewModel. To ensure that ViewModel created, initialized, and linked I use ContextComponentBase as a base class for all my pages.

Page code-behind looks like this

    public partial class Progresses
    {
        [Inject]
        protected new ProgressViewModel DataContext
        {
            get => base.DataContext as ProgressViewModel;
            set => base.DataContext = value;
        }
    }

Ensure that the .razor page itself is inherited from the proper class

@page "/progress"
@inherits Xakpc.BlazorBits.Bootstrap.ViewModels.ContextComponentBase;

That's all we need for View, let's switch to ViewModel!

ViewModel

First, check out the previous code snippet. Our ViewModel is Injected into our View - so let's not forget to add it in Startup.cs

services.AddScoped<ProgressViewModel>();

I recommend read several times about Scopes in Blazor. Once I lost a day trying to fix a problem caused by misunderstanding Blazor DI Scopes. Finally a ViewModel itself

public class ProgressViewModel : ViewModelBase
{
    private int _progress;

    public ProgressViewModel()
    {
        Add = new Command<int>(i => Progress += 1, i => Progress < 100);
        Subtract = new Command<int>(i => Progress -= 1, i => Progress > 0);
    }

    public int Progress
    {
        get => _progress;
        set { _progress = value; OnPropertyChanged(nameof(Progress)); }
    }

    public override Task InitializeAsync()
    {
        Progress = 22;
        return base.InitializeAsync();
    }

    public ICommand Add { get; set; }
    public ICommand Subtract { get; set; }
}

I will not go deep into VM, only point to main things

  • InitializeAsync called once per scope on first page-load - that means once per connection for Blazor Server because we added it with AddScope;
  • Commands set Progress which fire OnPropertyChanged which fire StateHasChanged which fire page redraw;
  • Commands have CanExecute func to ensure that Progress in range 0-100;
  • CanExecute also used to disable buttons when needed;

Spinner

Spinner is another simple example of how easy to wrap into a Razor component. Alt Text

<div class="spinner-border @Class" role="status">
    <span class="sr-only">@Text</span>
</div>

@code {
    [Parameter]
    public string Class { get; set; }

    [Parameter]
    public string Text { get; set; } = "Loading...";
}

Toasts

For toasts, I usually don't use a Bootstrap component but a nice Blazor port of very popular toastr.js

I should probably try Bootrstap toasts someday.

Modal

Bootstrap has a Modal component but it is highly dependant on JavaScript. In my work with Blazor, I prefer to minimize the amount of JS code overall, so I use Blazored.Modal - an amazing native component for Modals. You should check it out.

And that's all for now. I am not very good at writing such long articles, so will be appreciated for any feedback.