The Practical Client
Managing Page Layouts in Blazor
Blazor, like most systems for generating Web pages, supports using layout pages for repeated content. Here's what works and what doesn't (yet) work.
At the top of all your Web pages you undoubtedly have some content that you want repeated on many/most/all of your site's pages. The same is probably true of content along the bottom of the page and (probably) the left-hand side of your page.
To support this in Blazor-enabled pages, you can always use ASP.NET Layout Views and have your repeated content merged into your pages on the server, using your CPU cycles and downloading the repeated content on every page request.
Or, you can use Blazor Layouts to use the client's CPU cycles to do the merging and download your repeated content only once (you can see which choice I prefer).
In this column, I'm going to show how to use Blazor Layouts. But, before I do that, I just want to point out that this technology demonstrates the issues when working with "experimental" technology like Blazor. In the initial two versions of Blazor, layouts were handled by having a Blazor component (a cshtml file) implement the ILayoutComponent interface. Starting in version 0.3.0, Layouts were handled by having layout components inherit from the BlazorLayoutComponent class. In the most recent version of Blazor (0.8.0.0), layout components now must inherit from LayoutComponentBase.
The good news is that all these changes could be handled with global copy-and-replace. But, before you write a lot of code in Blazor, remember: You may not always be so lucky.
Creating a Template
Creating a Blazor Layout is relatively easy to do. First, you add a new cshtml file to your Blazor application's Pages folder. In that cshtml file, you'll remove all the directives and HTML and replace the contents with:
- An @inherits directive using LayoutComponentBase
- Any HTML that precedes the content from your individual Blazor Views
- An @Body statement
- Any HTML that follows the content from your individual Blazor Views
Following, for example, is the world's simplest Blazor Layout component. Traditionally, the layout used by most of the pages in your application goes into a file called _Layout.cshtml:
@inherits LayoutComponentBase
<h1>Start Layout</h1>
@Body
<h1>End Layout</h1>
You don't need to provide the namespace for the LayoutComponentBase class because, by default, your project will include a ViewImports file with a using statement for the namespace:
@using Microsoft.AspNetCore.Components.Layouts
If, however, you get red wavy lines under your use of LayoutComponentBase, you'll need to add the above using directive to your layout component.
It's the @Body statement that merge's your page's content into the layout. The LayoutComponentBase's Body property holds the content from the View to be displayed. The @Body statement takes the value of that property and dumps it into the HTML going to the browser. There is, by the way, no equivalent to a View's RenderSection method.
To use this layout, you tie it to other Blazor components using the layout directive, pointing to the Layout component. The reference to the Layout component is to the component's class name (not the file name) so you can't use routing and need to match the layout name's spelling and casing. Since a component's name is set through the name of the cshtml file it's part of and I put my Layout in a file called _Layout.cshtml, the necessary layout directive in one of my Blazor components look like this:
@layout _Layout
Theoretical Options
In theory, you don't have to put that layout directive in each of your Blazor components. You can, instead, put the layout directive in your application's _ViewStart.cshtml file. Because I placed my _Layout component in my application's Pages folder, its fully qualified name is Pages._Layout. If I move my layout directive to the _ViewImports file, I'll need to enhance the directive to include the full class name, like this:
@layout Pages._Layout
However, when I moved the layout directive to my ViewImports file, my application refused to start (did I mention that this is "experimental" technology?).
I did find, however, that I could add a Layouts folder under my application's Pages folder and put my _Layout.cshtml file in there. I could then reference my layout from my Blazor component that used the fully qualified class name with a directive like this:
@layout Layouts._Layout
The idea of putting any of my application's layout pages in a separate folder appealed to me. However, while I could use the layout component after I put it in that Layouts subfolder, I found I couldn't make any changes to my layout component without generating compile errors.
Nested Layouts
On the other hand, another feature does work as advertised: nested layouts. Nested layouts make sense when you have a site made up of subsites. In this scenario, you create a hierarchy of layouts. At the top you have the layout for your site as a whole (for example, company name at the top and copyright information at the bottom). In the middle, you have multiple layouts, one for each subsite (the name of the subsite with any subsite-specific icons). At the bottom of the hierarchy, you have the component being displayed.
As an example, here's a layout for the full site, which, being at the top of the hierarchy, does not reference any other layout:
@inherits LayoutComponentBase
<h1>Buy Our Products!</h1>
@Body
Next is the subsite's layout component, which references the layout for the site it's part of (the order of layout and inherits directives inside the component doesn't make any difference):
@layout _ProductsLayout
@inherits LayoutComponentBase
<h1>Buy Our Books!</h1>
@Body
Any actual component only references the layout for the subsite it belongs to, like this:
@page "/Products/Books"
@layout _BookSubSiteLayout
<h1>Here are our new arrivals:</h1>
This is a more limited set of features than is available in ASP.NET Views (I do miss having an equivalent to ASP.NET's RenderSection method). And, as I've noted, not all of this seems to work as I would expect (yet). And, as I noted, you shouldn't commit a lot of code to Blazor (yet) because things might change.
On the other hand, this is version 0.8.0.0. It's getting close to being ready for primetime.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.