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.
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.
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.
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 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.
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,
- 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
- <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 created the following architecture and heritance for the components,
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 calls for 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();
- 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();
- endpoints.MapFallbackToPage("/_Host");
- });
- }
The Visual Studio nests Razor components and their classes to make it easy to maintain the source code.
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 ๐