Let's Do Blazor 2/2 | Pages & Basic functionalities

Let us continue exploring Blazor and how it provides us with great tooling to create super apps. If you didn't have the chance to read the first part I recommend it to read it first here: Blazor basics and initial configuration in 1/2 of Let's Do Blazor (hashnode.dev)
So let's go further and see another page from the template:

Pages/Index.razor

@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

As you can see from the route defined in @page directive, you will see this page when you navigate to / or home. The overall structure is quite simple and the element that bears the most importance is SurveyPrompt component due to the passed parameter. We won't cover the parameter in this part so let's go to the next page:

Pages/FetchData.razor

@page "/fetchdata"
@using LetsDoBlazor.Data
@inject WeatherForecastService ForecastService

<PageTitle>Weather forecast</PageTitle>

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now));
    }
}

As the page title says, this component demonstrates fetching data from a service. Let's go and see what it is all about. At the top, we see a directive that is telling us this page is accessible on route /fetchdata. Next, we have @using LetsDoBlazor.Data which means that we will be using classes from that namespace. We are then injecting a WeatherForecastService class as a service using @inject operator.

If-statement essentially serves as the loading status if we don't have any forecasts. As soon as the forecasts variable is not null we will get the table rendered on our page. Within the table body, we see foreach loop that loops through the forecasts array and outputs one table row for each forecast element in forecasts.

In the code part, we first see the declaration of the forecasts variable as WeatherForecast[] array. Finally, OnInitializedAsync() method is responsible for the asynchronous fetching of the data to the forecasts variable. Inside the method, we see that the GetForecastAsync method from the WeatherForecastService class is assigned to the forecasts variable.

Let us see what Microsoft documentation says about OnInitializedAsync() method:

OnInitialized and OnInitializedAsync are invoked when the component is initialized after having received its initial parameters in SetParametersAsync.

If synchronous parent component initialization is used, the parent initialization is guaranteed to complete before child component initialization. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running.

As we are dealing with the asynchronous code in this example, the OnInitializedAsync() method is overridden and we also had to use await.

We won't go through the WeatherForecastService class at the moment but I will just briefly mention that in this example the class mimics fetching data from an API. If we would want to use a proper web API to fetch the weather data, we would go to WeatherForecastService class and implement fetching from an API.

Pages/Counter.razor

Our Counter component in the Pages folder is essentially less complex than the FetchData component and it looks like this:

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

As with other examples, our page starts with the directive that is showing us this component is reachable through /counter url. We then have @currentCount interestingly positioned within <p> element and also a button. With Blazor we can show a value of a variable directly in HTML by appending the variable name to the @ sign. For comparison, in JavaScript we could have similar output by using string literals like so:

...
<p role="status">`Current count: ${currentCount}`</p>
...

Let's look at the button:

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

On click is an event and this syntax is the event handling in Blazor. Essentially, we are telling Blazor to execute IncrementCount method once the button is clicked. To me, this is incredibly similar to the way VueJS does inline handlers.

As always, the code section is where all fun happens so let's see once again what we have there:

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

We first initialize currentCount variable as an Integer and assign it the initial value 0. Next, we have a simple IncrementCount() method that increments or adds 1 to the aforementioned variable value.

This counter app is rather poor, right? Let's see what we could add here:

  • a reset button that would reset the counter to the initial value

  • a decrement button to decrement our counter by one

  • an input field where we would define how much to increment or decrement

Reset button

For reset functionality we need another button and a method inside code section that resets variable to the initial count.

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<button class="btn btn-primary" @onclick="ResetCount">Reset</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    private void ResetCount()
    {
        currentCount = 0;
    }
}

Decrement button

If you've been following in the code, you've successfully been able to reset the counter to 0. Let's add another button and yet another method. I will also rename the increment button to something more logical and demote our Reset button to btn-secondary class:

<button class="btn btn-primary" @onclick="IncrementCount">Increment</button>
<button class="btn btn-primary" @onclick="DecrementCount">Decrement</button>
<button class="btn btn-secondary" @onclick="ResetCount">Reset</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    private void DecrementCount()
    {
        currentCount--;
    }

    private void ResetCount()
    {
        currentCount = 0;
    }
}

Here is question for you. How would you implement a requirement to count only positive numbers i.e. to prevent the counter to go into negative numbers? Think about conditions before executing certain expressions and disabling buttons if the conditions are not met. Another bool type variable would go quite handy there. You can do this on your own for the practice.

Now let us continue with the third feature we planned to add to this counter app.

Manual Input

What if we wouldn't like to increase or decrease the counter by only one? We could have a predetermined list of increment or decrement values such as +5 +10 -5 -10 or we could have an input field where we could state the value and then use either increment or decrement the current count by set value. We will also implement a reset button to reset our value and the final look our app looks like this:

Our focus is not on CSS styling so I used <br/> and <hr/> HTML elements to create visual dividers in the app. This is how it looks in code:


<p role="status">Current count: @currentCount</p>
<button class="btn btn-secondary" @onclick="ResetCount">Reset</button>
<br/>
<hr/>
<br/>
<h2>Increment or Decrement by 1</h2>
<button class="btn btn-primary" @onclick="IncrementCount">Increment</button>
<button class="btn btn-primary" @onclick="DecrementCount">Decrement</button>
<br/>
<hr/>
<br/>
<h2>Increment or Decrement by your value</h2>
<input type="number" @bind="value" />
<button class="btn btn-secondary" @onclick="ResetValue">Reset</button><br/><br/>
<button class="btn btn-primary" @onclick="IncrementCountByValue">Increment by @value</button>
<button class="btn btn-primary" @onclick="DecrementCountByValue">Decrement by @value</button>


@code {
    private int currentCount = 0;
    private int value = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    private void DecrementCount()
    {
        currentCount--;
    }

    private void ResetCount()
    {
        currentCount = 0;
    }

    private void IncrementCountByValue()
    {
        currentCount += value;
    }

    private void DecrementCountByValue()
    {
        currentCount -= value;
    }

    private void ResetValue()
    {
        value = 0;
    }
}

Here we see one important concept in the <input> element and that is data binding. With @bind Razor directive we are telling blazor that the value of the input field should be the value of the variable we have set. If you try this code, you will notice that the value updates when the input field loses focus. We can adjust that by applying another directive @bind:event

<input type="number" @bind="value" @bind:event="oninput" />

When you run the app with this directive added, you will see that the value updates as soon as you type it or use arrows to scroll numbers up or down.

You can read more about data binding in Blazor in MS documentation: ASP.NET Core Blazor data binding | Microsoft Learn

Summary

Or counter app now looks more useful. There are so many different things you can add such as enabling/disabling buttons, different colors of the numbers depending on the value, and many others where your imagination is the limit. When adding simple features, google is your friend and never be afraid to try different approaches and work through the errors or inexplainable app behaviour.

This concludes this mini two-part series on meeting Blazor. I've also been learning along the way so I can say I'm excited to code my future projects in .NET and Blazor. One special way I'd personally like to go is cross-platform development with .NET MAUI so I'll explore more projects in that direction.