Practical .NET

Creating Web Pages with Blazor: The Basics

If you know how to create an ASP.NET MVC View, you know a great deal about how to create pages in Blazor. But, by packaging up pages as Blazor Components, you can use (and re-use) those pages more like objects.

The easiest way to create a Blazor project (assuming that you've kept your version of Visual Studio 2017 up-to-date) is to use File | New | Project, pick the ASP.NET Core Web Application project type, and (in the following dialog), pick the Blazor project template. That gives you a project with an index.html page under the wwwroot node of your project and a bunch of Razor Pages in your Pages folder.

The index.html page is the start point of your application and is responsible for loading the client-side files necessary to make Blazor work. The page does that with this line:

<script src="_framework/blazor.webassembly.js"></script>

That index.html page triggers loading the Index.cshtml Razor Page from the Pages folder in your project. It's those Razor Pages where you put your Blazor code.

If you're not familiar with Razor Pages, don't feel bad: They're new to ASP.NET MVC Core. You can think of a Razor Page as a single View, tied to a single Controller, with the Controller kept in an associated file (if the View is in a Customers.cshtml file then the Controller-like code is in a file called Customers.cshtml.cs).

A Razor Page doesn't have a Model property. Instead, code in the Razor Page's .cshtml file can access whatever public properties the Controller in the .cshtml file exposes (the @model directive at the top of the Razor Page typically references the class in the associated C# file). In a sense, Razor Pages collapse the Controller and the data transfer object it normally passes to the View into a single class.

But, in a Blazor project, that code-behind file with the Controller in it disappears. The @model directive that ties a Razor Page to its controller (or a View to its data transfer object) isn't even supported in a Blazor Page. That moves your page's code into the View, at least initially.

Adding Code
With Blazor Pages, as in a View, you can still incorporate code into the page to control how the HTML is generated. While this code looks familiar to any ASP.NET MVC developer, in a Blazor Page, this code executes on the browser rather than on the server to generate the page's HTML:

@page "/"
@{
   List<Customer> custs;
   custs = new List<Customer>()
   {
    new Customer {custId = 1, FName = "Peter", LName = "Vogel"},
    new Customer {custId = 2, FName = "Jan", LName = "Vogel"},
    new Customer {custId = 3, FName = "Jason", LName = "van de Velde"}
  };
}
<h1>Customer List</h1>

<table>
  @foreach (Customer cust in custs)
  {
    <tr>
      <td>@cust.custId</td>
      <td>@cust.FName</td>
      <td>@cust.LName</td>
    </tr>
  }
</table>

Of course, in a real application, those Customer objects would be pulled from a server-side Web Service, as Chris Sainty described in an earlier column.

Rather than a View, what you now have is a Blazor Component: a lightweight UI defined in HTML and driven by C# code and the classes that make up the .NET Standard libraries. More accurately, what you have is a specification for a class. As with a View, ASP.NET Core uses the information in the .cshtml file to assemble a C# class that produces the resulting page. That class inherits from the BlazorComponent class (the class generated from a View inherits from a class called WebViewPage).

If you want to add interactive code in your component, you can put that C# code in a block beginning with @functions. Conventionally, the @functions block follows the HTML in the component, like this:

</table>

Number of Customers: @CustomerCount
<input type="button" onclick="@UpdateCount" value="Update Count" />

@functions
{
  public int CustomerCount;

  public void UpdateCount()
  {
    CustomerCount = 3;
  }
}

In that example, I defined CustomerCount as a field but the code works just as well if I define it as a property (or, for that matter, declare it as private):

  public int CustomerCount {get; set;}

As that code shows, to access the properties and methods in your code, you just prefix the code's members with an @ -- as you would in a View to access properties in the Model object.

Separating Code from the UI
Of course, mixing code and UI is considered a bad practice so you'll want to put your code in a separate file from your HTML. One solution is to have your Blazor page inherit from a base class and put your code in that base class.

To have this customer list page inherit from a base class, I just need to add the @inherits directive to my component and provide the name of my new base class. This directive has my Blazor page inherit from a class called BaseComponent:

@page "/"
@inherits BaseComponent

That class must inherit from BlazorComponent so that my Blazor page will still work. I could now move my code out of the @functions block in my .cshtml file and into my new base component, like this:

public class BaseComponent: BlazorComponent
{
  public int CustomerCount;

  public void UpdateCount()
  {
    CustomerCount = 3;
  }
}

Unfortunately, your Blazor component can only inherit from a single class. A more flexible solution is to put your code in a class in a separate file (a "ViewModel" class). You will need to put just enough code in the @functions block to instantiate that ViewModel class and wire it up to your HTML. This allows you, if required, to combine multiple ViewModels in a single Blazor component (I showed how to do that in earlier column).

Given these choices (and assuming that Blazor will, some day, stop being an "experimental" technology), I'm planning to use inheritance for common code that I want to share among multiple components. I'll be using ViewModels for component-specific code.

Treating Components Like Objects
In addition to looking like Views, Blazor components also can be used like objects. Once you've defined a Blazor component you can, like an object, use it to build other components: You just reference the component name (as established by the .cshtml file name) as an element.

If my previous component HTML and code was in a file called Customers.cshtml, I can use it in another component with this element:

<Customers/>

I would have to delete the @page directive at the start of my Customers.cshtml: A Razor Page that's being used in another page can't begin with the page directive.

Once you've added a component to your Blazor page, you can access that component's methods and properties from the page's code from both HTML and code (though, from HTML, the properties are write-only).

To set the properties on a component from HTML, you just add attributes to the component's element that match the names of private properties in your component. To make these private properties available to your page's HTML, you must decorate the properties with the Parameter attribute.

This example adds a parameter called tableheading to my Customers component:

@functions{
    [Parameter]
    private string tableheading { get; set; };

With that in place, I can set the property in my Customers element:

<Customers tableheading="Customer List"/>

You can also pass content contained inside the element to your component. For example, I could also pass the table heading to be used by my Customers component by putting it inside the element, like this:

<Customers>
Customer List
</Customers>

To catch the content of the Customers element, I must, as I did before, declare a private property in my component and decorate it with the Parameter attribute ... but there are a few more restrictions. For a property to accept content, the property must be called ChildContent and be of type RenderFragment.

This example both declares a parameter to accept content and uses that parameter in an H1 element:

<h1>@ChildContent</h1>
@functions{
    [Parameter]
    private RenderFragment ChildContent { get; set; }

Just Like an Object
Finally, in your page's code, you can treat your component like an object and access its methods and properties. Enabling that requires two steps.

First, you must add the ref attribute to the component's element and provide the name that your page's code will use to work with the component. This example gives my Customers element the name CustomerComponent:

<Customers ref="CustomerComponent">

Next, within your @functions block, you must add a field or property with the name you assigned to your component ("CustomerComponent," in my case). Blazor will take care of binding your component to that field or property. In this example, I've defined a field to bind to my Customer component and then both set its CustomerCount property and called its UpdateCount method:

@functions 
{
  private Customers CustomerComponent {get; set;}

    private void UpdateCount()
    {
      CustomerComponent.CustomerCount = 3;
      CustomerComponent.UpdateCount();
    }
}

If you're trying this at home, don't panic if you don't get any IntelliSense support as you type in members' names for your component -- full IntelliSense support doesn't seem to be available (at least, not yet).

There are several perspectives you can use when working with Blazor pages: Is it a View? Is it an element? Is it an object? You can regard this "conceptual flexibility" of Blazor components as either weird or limiting. Whatever way you look at it, it's powerful.

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/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube