March 2019

Volume 34 Number 3

[Cutting Edge]

Hierarchical Blazor Components

By Dino Esposito

Dino EspositoAs the latest framework to join the single-page application (SPA) party, Blazor had the opportunity to build on the best characteristics of other frameworks, such as Angular and React. While the core concept behind Blazor is to leverage C# and Razor to build SPA applications, one aspect clearly inspired by other frameworks is the use of components.

Blazor components are written using the Razor language, in much the same way that MVC views are built, and this is where things get really interesting for developers. In ASP.NET Core you can reach unprecedented levels of expressivity through new language artifacts called tag helpers. A tag helper is a C# class instructed to parse a given markup tree to turn it into valid HTML5. All of the branches you may face while creating a complex, made-to-measure chunk of HTML are handled in code, and all that developers write in text files is plain markup. With tag helpers the amount of code snippets decreases significantly. Tag helpers are great, but still present some programming wrinkles that Blazor components brilliantly iron out. In this article, I’ll build a new Blazor component that presents a modal dialog box through the services of the Bootstrap 4 framework. In doing so, I’ll deal with Blazor-templated components and cascading parameters.

Wrinkles of Tag Helpers

In my book, “Programming ASP.NET Core” (Microsoft Press, 2018), I present a sample tag helper that does nearly the same job discussed earlier. It turns some ad hoc non-HTML markup into Bootstrap-specific markup for modal dialog boxes (see bit.ly/2RxmWJS).

Any transformation between the input markup and the desired output is performed via C# code. A tag helper, in fact, is a plain C# class that inherits from the base class TagHelper and overrides a single method. The problem is that the transformation and markup composition must be expressed in code. While this adds a lot of flexibility, any change also requires a compile step. In particular, you need to use C# code to describe a DIV tree with all of its sets of attributes and child elements.

In Blazor, things come much easier as you don’t need to resort to tag helpers in order to create a friendlier markup syntax for sophisticated elements, such as a Bootstrap modal dialog box. Let’s see how to create a modal component in Blazor.

The idea is to set up a Blazor reusable component that wraps the Bootstrap modal dialog component. Figure 1 presents the familiar HTML5 markup tree required for Bootstrap (both 3.x and 4.x versions) to work.

Figure 1 The Bootstrap Markup for Modal Dialogs

<button type="button" class="btn btn-primary"
        data-toggle="modal"
        data-target="#exampleModal">
  Open modal
</button>
<div class="modal">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal title</h5>
        <button type="button" class="close" data-dismiss="modal">
          <span>&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>Modal body text goes here.</p>
      </div>
      <div class="modal-footer">
        <button type="button"
                class="btn btn-secondary"
                data-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

No Web developer is happy to reiterate that chunk of markup over and over again across multiple views and pages. Most of the markup is pure layout and the only variable information is the text to display and perhaps some style and buttons. Here’s a more expressive markup that’s easier to remember:

<Modal>
  <Toggle class="btn"> Open </Toggle>
  <Content>
    <HeaderTemplate> ... </HeaderTemplate>
    <BodyTemplate> ... </BodyTemplate>
    <FooterTemplate> ... </FooterTemplate>
  </Content>
</Modal>

The constituent elements of a modal component are immediately visible in the more expressive markup code. The markup includes a wrapper Modal element with two child subtrees: one for the toggle button and one for the actual content.

According to the Bootstrap syntax of modals, any dialog needs a trigger to be displayed. Typically, the trigger is a button element decorated with a pair of data-toggle and data-target attributes. The modal, however, can also be triggered via JavaScript. The Toggle sub-component just serves as the container for the trigger markup. The Content sub-component, instead, wraps the entire content of the dialog and is split in three segments: header, body and footer.

In summary, based on the previous code snippet, the resulting UI is made of a primary button labeled “Open.” Once clicked, the button will pop up a DIV filled with three layers: header, body and footer.

To create the nested components required for the modal dialog box, you need to deal with templated components and cascading parameters. Note that cascading parameters require you to run  Blazor 0.7.0 or newer.

The Modal Component

Let’s have a look at the code displayed in Figure 2. The markup is fairly minimal and includes a DIV element around a chunk of templated markup. The modal.cshtml file in Figure 2 declares a template property named ChildContent that collects (obviously enough) any child content. The result of the markup is to push out a surrounding DIV element that gathers both the toggle markup and the actual content to display in the dialog.

Figure 2 Source Code of the Modal Component

<CascadingValue Value="@Context">
  <div>
    @ChildContent
  </div>
</CascadingValue>
@functions
{
  protected override void OnInit()
  {
    Context = new ModalContext
    {
      Id = Id,
      AutoClose = AutoClose
    };
  }
  ModalContext Context { get; set; }
  [Parameter] private string Id { get; set; }
  [Parameter] private bool AutoClose { get; set; }
  [Parameter] RenderFragment ChildContent { get; set; }
}

Apparently this container component is not of great use. Nonetheless, it plays a crucial role given the required structure of the markup for Bootstrap dialog boxes. Both the Toggle and Content components share the same ID that uniquely identifies the modal dialog. By using a wrapper component, you can capture the ID value in only one place and cascade it down the tree. In this particular case, though, the ID is not even the sole param­eter you want to cascade through the innermost layers of markup. A modal dialog can optionally have a Close button in the header, as well as other attributes related to the size of the dialog or the animation. All of this information can be grouped together in a custom data transfer object and cascaded through the tree.

The ModalContext class is used to collect the ID and the Boolean value for the closing button, as shown in the code here:

public class ModalContext
{
  public string Id { get; set; }
  public bool AutoClose { get; set; }
}

The CascadingValue element captures the provided expression and automatically shares it with all innermost components that explicitly bind to it. Without the cascading parameters feature, any shared value in complex and hierarchical components must be explicitly injected wherever needed. Without this feature, you would have to indicate the same ID twice, as shown in this code:

<Modal>
  <Toggle id="myModal" class="btn btn-primary btn-lg">
    ...
  </Toggle>
  <Content id="myModal">
    ...
  </Content>
</Modal>

Cascading values are helpful in situations where the same set of values must be passed along the hierarchy of a complex component made of multiple sub-components. Note that cascading values must be grouped in a single container; therefore, if you need to pass on multiple scalar values, you should first define a container object. Figure 3 illustrates how parameters flow through the hierarchy of modal components.

Cascading Values in Hierarchical Components
Figure 3 Cascading Values in Hierarchical Components

Inside the Modal Component

The inner content of the Modal component is parsed recursively, and the Toggle and Content components take care of that. Here’s the source of the Toggle.cshtml component:

<button class="@Class"
        data-toggle="modal"
        data-target="#@OutermostEnv.Id">
  @ChildContent
</button>
@functions
{
  [CascadingParameter] protected ModalContext OutermostEnv { get; set; }
  [Parameter] string Class { get; set; }
  [Parameter] RenderFragment ChildContent { get; set; }
}

In the present implementation, the toggle element is styled through a public property named Class. The content of the button is captured through a templated property named ChildContent. Note that in Blazor, a template property named ChildContent automatically captures the entire child markup of the parent element. Also, a template property in Blazor is a property of type RenderFragment.

The interesting thing in the previous source code is the binding to the cascading values. You use the CascadingParameter attribute to decorate a component property, such as OutermostEnv. The property is then populated with cascaded values from the innermost level. As a result, OutermostEnv takes the value assigned to the instance of ModalContext freshly created in the Init method of the root component (refer back to Figure 2).

In the Toggle component, the Id cascaded value is used to set the value for the data-target attribute. In Bootstrap jargon, the data-target attribute of a dialog toggle button identifies the ID of the DIV to be popped up when the toggle is clicked.

The Content of the Modal Dialog

A Bootstrap dialog box is made of up to three DIV blocks laid out vertically: header, body and footer. All of them are optional, but you want to have at least one defined in order to give users some minimal feedback. A templated component is the right fit here. Here’s the public interface of the Content component as it results from the Content.cshtml file:

@functions
{
  [CascadingParameter] ModalContext OutermostEnv { get; set; }
  [Parameter] RenderFragment HeaderTemplate { get; set; }
  [Parameter] RenderFragment BodyTemplate { get; set; }
  [Parameter] RenderFragment FooterTemplate { get; set; }
}

The OutermostEnv cascaded parameter will bring the data defined outside the realm of the Content component. Both the ID and AutoClose properties are used here. The Id value is used to identify the outermost container of the dialog box. The DIV signed with the ID will pop up when the modal is triggered. The AutoClose value, instead, is used to control an IF statement that decides whether or not a Dismiss button should go in the header bar.

Finally, three RenderFragment template properties define the actual content for customizable areas—header, footer and body.

As you can see in Figure 4, the Content component does most of the work to render the expected Bootstrap markup for modal dialog boxes. It defines the overall HTML layout and uses template properties to import the details of the markup that would make a given dialog unique—the header, footer and body markup. Thanks to Blazor templates, any actual markup can be specified as inline content in the caller page. Note that the source code of the caller page (called Cascade in the sample application) is depicted back in Figure 3.

Figure 4 Markup of the Content Component

<div class="modal" id="@OutermostEnv.Id">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">
          @HeaderTemplate
        </h5>
        @if (OutermostEnv.AutoClose)
        {
          <button type="button" class="close"
                  data-dismiss="modal">
            <span>&times;</span>
          </button>
        }
      </div>
      <div class="modal-body">
        @BodyTemplate
      </div>
      <div class="modal-footer">
        @FooterTemplate
      </div>
    </div>
  </div>
</div>

More on Cascading Values and Parameters

Cascading values solve the problem of effectively flowing values down the stack of subcomponents. Cascading values can be defined at various levels in a complex hierarchy and go from an ancestor component to all of its descendants. Each ancestor element can define a single cascading value, possibly a complex object that gathers together multiple scalar values.

To make use of cascaded values, descendant components declare cascading parameters. A cascading parameter is a public or protected property decorated with the CascadingParameter attribute. Cascading values can be associated with a Name property, like so:

<CascadingValue Value=@Context Name="ModalDialogGlobals">
  ...
</CascadingValue>

In this case, descendants will use the Name property to retrieve the cascaded value, as shown here:

[CascadingParameter(Name = "ModalDialogGlobals")]
ModalContext OutermostEnv { get; set; }

When no name is specified, cascading values are bound to cascading parameters by type.

Wrapping Up

Cascading values are specifically designed for hierarchical components, but at the same time hierarchical (and templated) components are realistically the most common type of Blazor components that developers are expected to write. This article demonstrated cascading parameters and templated and hierarchical components, but also showed how powerful it could be to use Razor components to express specific pieces of markup through a higher-level syntax. In particular, I worked out a custom markup syntax to render a Bootstrap modal dialog box. Note that you can achieve the same in plain ASP.NET Core using tag helpers or HTML helpers in classic ASP.NET MVC.

The source code for the article is available at bit.ly/2FdGZat.


Dino Esposito has authored more than 20 books and 1,000-plus articles in his 25-year career. Author of “The Sabbatical Break,” a theatrical-style show, Esposito is busy writing software for a greener world as the digital strategist at BaxEnergy. Follow him on Twitter: @despos.

Thanks to the following Microsoft technical expert for reviewing this article: Daniel Roth


Discuss this article in the MSDN Magazine forum