Blazor - More Than Deluxe WebForms

A challenge to convert a WebForms application to Blazor is to resolve all PostBack elements of the WebForms using concepts of Single Page Applications.
 
The answer to this question is simple: Blazor Components.
 
After I had written the article Blazor: Is It Javascript’s Beginning of the End?, I received lots of complaints from the Javascript lovers (like me). Because I use the word WebForms in this article,  it won't be a surprise if some Haters show up.
 
It is essential to say that if you are a new developer or have 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
 
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
 
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 using MVVM since Silverlight, KnockoutIS, AngularJS until now in Angular, Vue, and also Xamarin Forms.
 
The MVVM listens to all changes that are 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
 
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.
 
In the WebForms application that I am using to convert to Blazor of course there 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  created some fields and features,
 
Blazor - More Than A Deluxe WebForms
  • When filling the Password MaxLength TextBox field, it will limit (in characters) the Password Textbox field.
  • When leaving blank the Token TextBox field, it will be marked in a 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
  1. <div class="col-md-4 mb-3">  
  2.     <label>User:</label>  
  3.     <TextBox Id="tbxUser"  
  4.              CssClass="teste"  
  5.              @bind-Text="@user"></TextBox>  
  6. </div>  
TextBox Number
  1. <div class="col-md-4 mb-3">  
  2.     <label>Password MaxLength:</label>  
  3.     <TextBox Id="tbxMax"  
  4.              @bind-Text="@max"  
  5.              TextMode="TextBoxMode.Number"></TextBox>  
  6. </div>  
TextBox Password
  1. <div class="col-md-4 mb-3">  
  2.     <label>Password:</label>  
  3.     <TextBox Id="tbxPassword"  
  4.              @bind-Text="@password"  
  5.              MaxLength="@max"  
  6.              TextMode="TextBoxMode.Password"></TextBox>  
  7. </div>  
TextBox Multiline 
  1. <div class="col-md-12 mb-3">  
  2.     <label>Token:</label>  
  3.     <TextBox Id="tbxToken"  
  4.              @bind-Text="@token"  
  5.              TextMode="TextBoxMode.MultiLine"  
  6.              Required="true"  
  7.              Rows="5"></TextBox>  
  8. </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.
  1. <div class="row">  
  2.     <div class="col-md-4 mb-3">  
  3.         <label>Week Days:</label>  
  4.         <DropDownList Id="ddlWeekDays"  
  5.                       @bind-SelectedValue="@weekDay">  
  6.             <DropDownListItem Text="Sunday" Value="1"></DropDownListItem>  
  7.             <DropDownListItem Text="Monday"></DropDownListItem>  
  8.             <DropDownListItem Text="Tusday" Selected="true"></DropDownListItem>  
  9.             <DropDownListItem Text="Wednesday"></DropDownListItem>  
  10.             <DropDownListItem Text="Thursday"></DropDownListItem>  
  11.             <DropDownListItem Text="Friday"></DropDownListItem>  
  12.             <DropDownListItem Text="Saturday"></DropDownListItem>  
  13.         </DropDownList>  
  14.     </div>  
  15. </div>  
  16.    
  17. <div class="row">  
  18.     <div class="col-md-4 mb-3">  
  19.         <label>Items List:</label>  
  20.         <DropDownList Id="ddlItemsList"  
  21.                       DataSource="@items"  
  22.                       @bind-SelectedValue="@item"></DropDownList>  
  23.     </div>  
  24. </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  created the following architecture and heritance for the components,
 
Blazor - More Than A Deluxe WebForms 
 
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. 
  1. public class TextBoxComponent : ControlComponent  
  2. {  
  3.     public TextBoxComponent()  
  4.     {  
  5.         MaxLength = "500";  
  6.     }  
  7.     
  8.     [Parameter]  
  9.     public bool Required { getset; }  
  10.     
  11.     [Parameter]  
  12.     public string Text { getset; }  
  13.     
  14.     [Parameter]  
  15.     public string MaxLength { getset; }  
  16.     
  17.     [Parameter]  
  18.     public string Rows { getset; }  
  19.     
  20.     [Parameter]  
  21.     public TextBoxMode TextMode { getset; }  
  22.     
  23.     [Parameter]  
  24.     public EventCallback<string> TextChanged { getset; }  
  25.     
  26.     [Parameter]  
  27.     public EventCallback<string> MaxLengthChanged { getset; }  
  28.     
  29.     [Parameter]  
  30.     public EventCallback<string> TextValueChanged { getset; }  
  31.     
  32.     protected async Task OnChangeAsync(  
  33.         ChangeEventArgs e)  
  34.     {  
  35.         Text = e.Value as string;  
  36.         IsValid = !(Required && string.IsNullOrEmpty(Text));  
  37.     
  38.         await TextChanged.InvokeAsync(Text);  
  39.         await TextValueChanged.InvokeAsync(Text);  
  40.     }  
  41. }  
The Parameter attribute in component property means that it will be available to pass a value in other components like the sample below,
  1. <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,
  1. public class ControlComponent : ComponentBase  
  2. {  
  3.     public ControlComponent()  
  4.     {  
  5.         IsValid = true;  
  6.     }  
  7.     
  8.     [Parameter]  
  9.     public string Id { getset; }  
  10.     
  11.     [Parameter]  
  12.     public string CssClass { getset; }  
  13.     
  14.     [Parameter]  
  15.     public bool Disabled { getset; }  
  16.     
  17.     [Parameter]  
  18.     public bool IsValid { getset; }  
  19.     
  20.     public string ValidCssClass => IsValid ? "" : "is-invalid";  
  21.     
  22.     public string AllCssClass => $"form-control {CssClass ?? ""} {ValidCssClass}";  
  23.     
  24.     public void ToggleDisabled()  
  25.     {  
  26.         Disabled = !Disabled;  
  27.     }  
  28. }  

Blazor: The Components – View

 
The TextBox component view is pretty simple,
  1. @inherits TextBoxComponent  
  2.     
  3. @if (TextMode == TextBoxMode.MultiLine)  
  4. {  
  5.     <textarea id="@(Id ?? "tbx1")"  
  6.               class="@AllCssClass"  
  7.               maxlength="@MaxLength"  
  8.               rows="@Rows"  
  9.               disabled="@Disabled"  
  10.               required="@Required"  
  11.               @onchange="OnChangeAsync">@Text</textarea>  
  12. }  
  13. else  
  14. {  
  15.     <input type="@TextMode.ToString().ToLower()"  
  16.            id="@(Id ?? "tbx1")"  
  17.            class="@AllCssClass"  
  18.            value="@Text"  
  19.            maxlength="@MaxLength"  
  20.            disabled="@Disabled"  
  21.            required="@Required"  
  22.            @onchange="OnChangeAsync" />  
  23. }  
As you can see above, all the HTML INPUT/TEXTAREA attributes use C# properties.
 
One attribute calls for our attention, I am talking about the @onchange attribute that contains a reference for an asynchronous C# method.
  1. protected async Task OnChangeAsync(  
  2.     ChangeEventArgs e)  
  3. {  
  4.     Text = e.Value as string;  
  5.     IsValid = !(Required && string.IsNullOrEmpty(Text));  
  6.     
  7.     await TextChanged.InvokeAsync(Text);  
  8.     await TextValueChanged.InvokeAsync(Text);  
  9. }  
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.
  1. [Parameter]  
  2. public EventCallback<string> TextChanged { getset; }  
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.
  1. @inherits DropDownListComponent  
  2.     
  3.     <select @onchange="OnChangeAsync"  
  4.             id="@Id"  
  5.             class="@AllCssClass"  
  6.             disabled="@Disabled">  
  7.         @foreach (var data in DataSource)  
  8.         {  
  9.             var value = data.Value ?? data.Text;  
  10.             <option value="@value" selected="@(value == SelectedValue)">@data.Text</option>  
  11.         }  
  12.     </select>  
  13.     
  14. <CascadingValue Value=this>  
  15.     @ChildContent  
  16. </CascadingValue>  
Another difference is that DropDownList can receive DropDownListItem components as child ones. 
  1. <div class="row">  
  2.     <div class="col-md-4 mb-3">  
  3.         <label>Week Days:</label>  
  4.         <DropDownList Id="ddlWeekDays"  
  5.                       @bind-SelectedValue="@weekDay">  
  6.             <DropDownListItem Text="Sunday" Value="1"></DropDownListItem>  
  7.             <DropDownListItem Text="Monday"></DropDownListItem>  
  8.             <DropDownListItem Text="Tusday" Selected="true"></DropDownListItem>  
  9.             <DropDownListItem Text="Wednesday"></DropDownListItem>  
  10.             <DropDownListItem Text="Thursday"></DropDownListItem>  
  11.             <DropDownListItem Text="Friday"></DropDownListItem>  
  12.             <DropDownListItem Text="Saturday"></DropDownListItem>  
  13.         </DropDownList>  
  14.     </div>  
  15. </div>  
ChildContent works like a @Body in a Razor View, so you must use it somewhere in your child component.
  1. [Parameter]  
  2. public RenderFragment ChildContent { getset; }  
  1. <CascadingValue Value=this>  
  2.     @ChildContent  
  3. </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.
  1. public class DropDownListItemComponent : ComponentBase  
  2. {  
  3.     [Parameter]  
  4.     public string Text { getset; }  
  5.     
  6.     [Parameter]  
  7.     public string Value { getset; }  
  8.     
  9.     [Parameter]  
  10.     public bool Selected { getset; }  
  11.     
  12.     [CascadingParameter]  
  13.     public DropDownListComponent ParentDropDownList { getset; }  
  14.     
  15.     protected override void OnInitialized()  
  16.     {  
  17.         ParentDropDownList?.AddItem(this);  
  18.     
  19.         base.OnInitialized();  
  20.     }  
  21. }  
  22.    
  23. internal void AddItem(  
  24.     DropDownListItemComponent item)  
  25. {  
  26.     DataSource.Add(new Models.Item  
  27.     {  
  28.         Text = item.Text,  
  29.         Value = item.Value ?? item.Text  
  30.     });  
  31.     
  32.     if (item.Selected)  
  33.     {  
  34.         SelectedValue = item.Value ?? item.Text;  
  35.         SelectedValueChanged.InvokeAsync(SelectedValue).GetAwaiter();  
  36.     }  
  37. }  
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.
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     services.AddRazorPages();  
  4.     services.AddServerSideBlazor(); // here  
  5.     services.AddSingleton<WeatherForecastService>();  
  6. }  
  7.     
  8. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
  9. {  
  10.     if (env.IsDevelopment())  
  11.     {  
  12.         app.UseDeveloperExceptionPage();  
  13.     }  
  14.     else  
  15.     {  
  16.         app.UseExceptionHandler("/Error");  
  17.     }  
  18.     
  19.     app.UseStaticFiles();  
  20.     
  21.     app.UseRouting();  
  22.     
  23.     app.UseEndpoints(endpoints =>  
  24.     {  
  25.         endpoints.MapBlazorHub(); // here  
  26.         endpoints.MapFallbackToPage("/_Host"); // here  
  27.     });  
  28. }  
The Visual Studio nests Razor components and their classes to make it easy to maintain the source code.
 
Blazor - More Than A Deluxe WebForms
 
The Tag Helpers and Razor Views still work, because Blazor 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 ๐Ÿ™‚


Similar Articles