Blazor: More Than a Deluxe WebForms

Also available in portuguese

A challenge to convert a WebForms application to Blazor is to resolve all PostBack things of the WebForms using concepts of Single Page Application.

Blazor: More Than a Deluxe WebForms

The answer to these questions is simple: Blazor Components.

After I had written the article Blazor: Is Javascript’s Beginning of the End?, I had received lots of complaints from the javascript lovers (like me).

Because I had used the word WebForms in this article, it will not be a surprise if some Haters showed up.

Any comparison between Blazor and WebForms would be the least irresponsible e I didn’t would do it for sure, in other life :D.

It is essential to say that if you are a new developer or never seen ASP.NET CORE before, I suggest you read my article .NET CORE for .NET Developers first.


#The Blazor

Blazor is a new Microsoft technology to create frontend using ASP.NET CORE and C#, instead of Javascript.

There are two types of Blazor applications: One that runs at the server-side called Blazor Server App, and it uses SignalR to communicate between client/server.

Blazor: More Than a Deluxe WebForms 1

The other type of Blazor application is Blazor Web Assembly, still in a preview version.

In that case, the application runs at the web browser, even C# DLLs.

Blazor: More Than a Deluxe WebForms 2

Blazor is considered Microsoft’s SPA (Single Page Application) that could fight and replace popular technologies like Angular, Vue, React, and other Javascript frameworks.


#Blazor: MMVV – Model View View Model

The concepts and design patterns of MVVM are not actual.

I have been used MVVM since Silverlight, KnockoutIS, AngularJS until now in Angular, Vue, and also Xamarin Forms.

The MVVM listens to all changes that had made in a View or a Model (Javascript/C#).

If any state changes, all references in a View or a Model are updated.

Blazor: More Than a Deluxe WebForms 3

If you have been developing in Angular/AngularJs, imagine the variable above @currentCount like this {{ @currentCount }}.


#Blazor: POC – Proof of Concept

Even Blazor uses ASP.NET CORE and Razor Views. It is reasonable to do a POC to learn more about the workflow/behavior of some things like passing parameters between components.

The WebForms application that I am using to convert to Blazor off course the are lots of TextBox and DropDownList components.

I have decided to create all these components in Blazor with the same names, properties, and events as WebForms.

So, I had created some fields and features:

Blazor: More Than a Deluxe WebForms 4

When filling the Password MaxLength TextBox field, it will limit (in characters) the Password Textbox field.
When letting blank the Token TextBox field, it will be marked in red line an invalid one.
When clicking the Select Three button, it will select the item 3 on the ItemsList DropDownList field.
When clicking the Disable Week Day button, it will disable the Week Days DropDownList field.
When clicking the Include Options button, it will include one more option on the Dynamic Options DropDownList field.
The Labels at the bottom will be updated when any field changed.

The usability of these components in the Razor Views are:

TextBox String:

<div class="col-md-4 mb-3">
    <label>User:</label>
    <TextBox Id="tbxUser"
             CssClass="teste"
             @bind-Text="@user"></TextBox>
</div>

TextBox Number:

<div class="col-md-4 mb-3">
    <label>Password MaxLength:</label>
    <TextBox Id="tbxMax"
             @bind-Text="@max"
             TextMode="TextBoxMode.Number"></TextBox>
</div>

TextBox Password:

<div class="col-md-4 mb-3">
    <label>Password:</label>
    <TextBox Id="tbxPassword"
             @bind-Text="@password"
             MaxLength="@max"
             TextMode="TextBoxMode.Password"></TextBox>
</div>

TextBox Multiline:

<div class="col-md-12 mb-3">
    <label>Token:</label>
    <TextBox Id="tbxToken"
             @bind-Text="@token"
             TextMode="TextBoxMode.MultiLine"
             Required="true"
             Rows="5"></TextBox>
</div>

In the case of the DropDownList, there are two ways to use it: one is declaring all DropDownListItem as options. A second way is to provide a DataSource.

<div class="row">
    <div class="col-md-4 mb-3">
        <label>Week Days:</label>
        <DropDownList Id="ddlWeekDays"
                      @bind-SelectedValue="@weekDay">
            <DropDownListItem Text="Sunday" Value="1"></DropDownListItem>
            <DropDownListItem Text="Monday"></DropDownListItem>
            <DropDownListItem Text="Tusday" Selected="true"></DropDownListItem>
            <DropDownListItem Text="Wednesday"></DropDownListItem>
            <DropDownListItem Text="Thursday"></DropDownListItem>
            <DropDownListItem Text="Friday"></DropDownListItem>
            <DropDownListItem Text="Saturday"></DropDownListItem>
        </DropDownList>
    </div>
</div>

<div class="row">
    <div class="col-md-4 mb-3">
        <label>Items List:</label>
        <DropDownList Id="ddlItemsList"
                      DataSource="@items"
                      @bind-SelectedValue="@item"></DropDownList>
    </div>
</div>


#Blazor: The Components – Model

One of the things that are also similar between Blazor and WebForms is the “code-behind”.

It is not mandatory but is similar.

I had created the following architecture and heritance for the components:

Blazor: More Than a Deluxe WebForms 5

The TextBox.razor file is for HTML like INPUT tags, and this is a class that has a parent one called TextBoxComponent, which contains properties for text fields.

public class TextBoxComponent : ControlComponent
{
    public TextBoxComponent()
    {
        MaxLength = "500";
    }
 
    [Parameter]
    public bool Required { get; set; }
 
    [Parameter]
    public string Text { get; set; }
 
    [Parameter]
    public string MaxLength { get; set; }
 
    [Parameter]
    public string Rows { get; set; }
 
    [Parameter]
    public TextBoxMode TextMode { get; set; }
 
    [Parameter]
    public EventCallback<string> TextChanged { get; set; }
 
    [Parameter]
    public EventCallback<string> MaxLengthChanged { get; set; }
 
    [Parameter]
    public EventCallback<string> TextValueChanged { get; set; }
 
    protected async Task OnChangeAsync(
        ChangeEventArgs e)
    {
        Text = e.Value as string;
        IsValid = !(Required && string.IsNullOrEmpty(Text));
 
        await TextChanged.InvokeAsync(Text);
        await TextValueChanged.InvokeAsync(Text);
    }
}

The Parameter attribute in component property means that it will be available to pass a value in other components like the sample below:

<TextBox Id="tbxUser" CssClass="teste" @bind-Text="@user"></TextBox>

The TextBoxComponent has a parent called ControlCompont, which contains properties for all types of fields like Id and CssClass.

public class ControlComponent : ComponentBase
{
    public ControlComponent()
    {
        IsValid = true;
    }
 
    [Parameter]
    public string Id { get; set; }
 
    [Parameter]
    public string CssClass { get; set; }
 
    [Parameter]
    public bool Disabled { get; set; }
 
    [Parameter]
    public bool IsValid { get; set; }
 
    public string ValidCssClass => IsValid ? "" : "is-invalid";
 
    public string AllCssClass => $"form-control {CssClass ?? ""} {ValidCssClass}";
 
    public void ToggleDisabled()
    {
        Disabled = !Disabled;
    }
}


#Blazor: The Components – View

The TextBox component view is pretty simple:

@inherits TextBoxComponent
 
@if (TextMode == TextBoxMode.MultiLine)
{
    <textarea id="@(Id ?? "tbx1")"
              class="@AllCssClass"
              maxlength="@MaxLength"
              rows="@Rows"
              disabled="@Disabled"
              required="@Required"
              @onchange="OnChangeAsync">@Text</textarea>
}
else
{
    <input type="@TextMode.ToString().ToLower()"
           id="@(Id ?? "tbx1")"
           class="@AllCssClass"
           value="@Text"
           maxlength="@MaxLength"
           disabled="@Disabled"
           required="@Required"
           @onchange="OnChangeAsync" />
}

As you can see above, all the HTML INPUT/TEXTAREA attributes use C# properties.

One attribute call our attention, I am talking about the @onchange attribute that contains a reference for an asynchronous C# method.

protected async Task OnChangeAsync(
    ChangeEventArgs e)
{
    Text = e.Value as string;
    IsValid = !(Required && string.IsNullOrEmpty(Text));
 
    await TextChanged.InvokeAsync(Text);
    await TextValueChanged.InvokeAsync(Text);
}

When the input value changes, Blazor fires an event and calls the OnChangeAsync method.

When this happens, all references of Text are updated, even labels, other inputs, or a C# model.

This concept is called Two-Way-Binding. I mean that when you modify a property in a component, the same is updated outside.

That is possible because the TextChanged property called inside the OnChangeAsync method.

The rule to create that binding is: Create an EventCallback property, named with PropertyName + Changed, and put a Parameter attribute on it.

[Parameter]
public EventCallback<string> TextChanged { get; set; }

After that, call that property at any moment in your C# component.


#Blazor: DropDownList and DropDownListItem

One of the differences between the TextBox and DropDownList component is the DataSource property that receives a list of items used in a Foreach statement.

@inherits DropDownListComponent
 
    <select @onchange="OnChangeAsync"
            id="@Id"
            class="@AllCssClass"
            disabled="@Disabled">
        @foreach (var data in DataSource)
        {
            var value = data.Value ?? data.Text;
            <option value="@value" selected="@(value == SelectedValue)">@data.Text</option>
        }
    </select>
 
<CascadingValue Value=this>
    @ChildContent
</CascadingValue>

Another difference is that DropDownList can receive DropDownListItem components as child ones.

<div class="row">
    <div class="col-md-4 mb-3">
        <label>Week Days:</label>
        <DropDownList Id="ddlWeekDays"
                      @bind-SelectedValue="@weekDay">
            <DropDownListItem Text="Sunday" Value="1"></DropDownListItem>
            <DropDownListItem Text="Monday"></DropDownListItem>
            <DropDownListItem Text="Tusday" Selected="true"></DropDownListItem>
            <DropDownListItem Text="Wednesday"></DropDownListItem>
            <DropDownListItem Text="Thursday"></DropDownListItem>
            <DropDownListItem Text="Friday"></DropDownListItem>
            <DropDownListItem Text="Saturday"></DropDownListItem>
        </DropDownList>
    </div>
</div>

ChildContent works like a @Body in a Razor View, so you must use it somewhere in your child component.

[Parameter]
public RenderFragment ChildContent { get; set; }
<CascadingValue Value=this>
    @ChildContent
</CascadingValue>

The DropDownListItem component has a reference of its parent DropDownList, and when it renders, it inserts a new option in the parent’s DataSource property.

public class DropDownListItemComponent : ComponentBase
{
    [Parameter]
    public string Text { get; set; }
 
    [Parameter]
    public string Value { get; set; }
 
    [Parameter]
    public bool Selected { get; set; }
 
    [CascadingParameter]
    public DropDownListComponent ParentDropDownList { get; set; }
 
    protected override void OnInitialized()
    {
        ParentDropDownList?.AddItem(this);
 
        base.OnInitialized();
    }
}

internal void AddItem(
    DropDownListItemComponent item)
{
    DataSource.Add(new Models.Item
    {
        Text = item.Text,
        Value = item.Value ?? item.Text
    });
 
    if (item.Selected)
    {
        SelectedValue = item.Value ?? item.Text;
        SelectedValueChanged.InvokeAsync(SelectedValue).GetAwaiter();
    }
}

You can download the full source code in my Github.


#Blazor: Remarks

In a Blazor application:

The _Imports.razor file works like the Views/web.config in an MVC application.

The Startup.cs file contains all configuration to make Blazor and ASP.NET work.

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor(); // here
    services.AddSingleton<WeatherForecastService>();
}
 
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }
 
    app.UseStaticFiles();
 
    app.UseRouting();
 
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub(); // here
        endpoints.MapFallbackToPage("/_Host"); // here
    });
}

The Visual Studio nests Razor components and their classes to make it easy to maintain the source code.

Blazor: More Than a Deluxe WebForms 6

The Tag Helpers and Razor Views still work, because Blazor it is an ASP.NET Core application.

At least, all Blazor magic only works with the Pages/_Host.cshtml file that calls the App.razor component, which references the blazor.server.js script file, responsible for the SignalR stuff.

As I said, this article is about Blazor Server, and it is more potent than I had imagined. I think it will become accessible, even more, when Microsoft launches the Blazor Web Assembly version.

And about you? Have you used Blazor before?

Thank you for reading it 🙂

Do complete download of the source code on github.
About the Author:
He works as a solution architect and developer, has more than 18 years of experience in software development on several platforms and more than 16 years only for the insurance market.