O desafio de converter uma aplicação WebForms para Blazor é tentar resolver todas as questões PostBack do Webforms usando os conceitos de Single Page Application do Blazor.
A resposta para essas questão é simples: Blazor Components.
Depois que escrevi o artigo Blazor: O Começo do Fim do Javascript?, recebi uma série de críticas dos amantes de javascript (assim como eu).
Como usei a palavra WebForms no título desse artigo, não estarei surpreso se aparecem mais alguns Haters aqui.
A comparação de Blazor com WebForms seria no mínimo irresponsável e com certeza eu não faria isso… em outra vida :D.
Algumas informações importantes para quem está chegando agora. Se você é um desenvolvedor .NET mas nunca viu nada de .NET Core, sugiro você ler antes o artigo .NET Core para DesenvolvedoreS .NET.
Table of Contents
O Blazor
Blazor é uma “nova” tecnologia da Microsoft para criação de frontends usando ASP.NET CORE e C# ao invés de javascript.
Existem basicamente dois tipos de aplicações Blazor: Uma que roda no servidor Blazor Server App e utiliza o SignalR para comunicação entre o client/server.
E o outro tipo de aplicação Blazor é o Blazor Web Assembly, ainda em versão preview.
Nesse caso, a aplicação web roda inteira no navegador, até as DLLs C#.
Blazor é considerado o SPA (Single Page Application) da Microsoft que poderia bater de frente e substituir Angular, Vue, React e outros frameworks javascript.
Blazor: MVVM – Model View View Model
Os conceitos e design patterns de MVVM são bem antigos por sinal.
Eu utilizo MVVM desde Silverlight, KnockoutJS, AngularJS até chegar nos dias de hoje como Angular, Vue, React e até Xamarin Forms.
Consiste em “escutar” mudanças realizadas na View (campos INPUT por exemplo) e/ou Model (javascript/C# por exemplo).
Se um valor mudar, todas as referências View e/ou Model são atualizadas.
Você que já desenvolveu em Angular ou AngularJS, na imagem acima, imagine aquela variável @currentCount como {{ @currentCount }}.
Exibirá o valor de @currentCount sempre que a variável mudar.
Blazor: POC – Prova de Conceito
Apesar do Blazor usar como base o ASP.NET CORE e Razor Views, é legal fazer POC para conhecer melhor o funcionamento/comportamento de algumas coisas, entre elas a troca de informação entre componentes.
Como era de se esperar, na aplicação WebForms que estou convertendo, possui vários campos TextBox e DropDownList.
Decidi fazer uma prova de conceito criando esses dois componentes no Blazor com o mesmo nome de propriedades/eventos dos respectivos no WebForms.
Assim, criei os seguintes campos e funcionalidades:
– Ao preencher o TextBox Password MaxLength, o campo TextBox Password conterá o máximo possíveis desses caracteres.
– Ao deixar em branco o campo TextBox Token, ele será grifado em vermelho como inválido.
– Ao clicar no botão Select Three irá selecionar o item 3 do DropDownList ItemsList.
– Ao clicar no botão Disable Week Day irá desabilitar o DropDownList Week Days.
– Ao clicar no botão Include Options irá incluir mais uma opção no DropDownList Dynamic Options.
– Toda alteração será refletida nos Labels da parte inferior da tela.
O uso e declaração das tags HTML dos Componentes nas Razor Views ficaram assim:
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 (textarea):
<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>
Os valores dos TextBox serão armazenados nas variáveis C# user, max, password e token respectivamente.
DropDownList com Items declarados:
<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>
O valor selecionado no DropDownList será armazenado na variável C# weekDay.
No caso do DropDownList, ainda há possibilidade de ao invés de declarar explicitamente os itens como no exemplo acima, podemos passar os itens ao um DataSource:
<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>
No caso acima, o valor selecionado será armazenado na variável C# item.
Os nomes dos componentes e propriedades na declaração nas Views parece muito com o WebForms.
Blazor: Os Componentes – Model
Uma comparação do Blazor/Components do ASP.NET CORE é a questão do codebehind de um determinado Component ou Page.
Não é obrigatoriamente necessário, mas é parecido.
A arquitetura e herança codebehind (aqui chamadas de Model) dos Componentes ficaram assim:
No arquivo TextBox.razor fica tudo o que for HTML das tags INPUT e TEXTAREA.
O TextBox.razor herda da classe C# TextBoxComponent, que possui todas as propriedades e eventos de um TextBox, como MaxLength e Quantidade de Linhas.
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);
}
}
Aquelas propriedades que possuem atributo [Parameter], significa que será possível usá-los na declaração no HTML, vide exemplo da propriedade CssClass abaixo:
<TextBox Id="tbxUser" CssClass="teste" @bind-Text="@user"></TextBox>
A classe TextBoxComponent herda de uma mais básica ControlComponent, que possui propriedade básicas a qualquer Control (campo de tela) como Id, Disabled, CssClass entre outros.
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;
}
}
Por fim, ControlComponent herda da classe básica do ASP.NET CORE ComponentBase.
Blazor: Os Componentes – View
A View do Component Textbox.razor ficou assim:
@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" />
}
Os atributos HTML de INPUT/TEXTAREA que usam as propriedades C# AllCssClass, Text, MaxLength, Disabled, Id e Required estão bem claras no código fonte.
A única coisa diferente nesse código que chama atenção é o atributo @onchange definido em ambos campos.
É uma palavra-chave interna para o evento HTML onchange que chama uma função C# OnChangeAsync.
protected async Task OnChangeAsync(
ChangeEventArgs e)
{
Text = e.Value as string;
IsValid = !(Required && string.IsNullOrEmpty(Text));
await TextChanged.InvokeAsync(Text);
await TextValueChanged.InvokeAsync(Text);
}
Basicamente a função assíncrona pega o valor do INPUT/TEXTAREA e atribui para a propriedade Text.
Fazendo isso, todas as referências que usam a propriedade Text serão atualizadas, ou seja, se Text estiver sendo usado em um Label por exemplo, após sair do INPUT/TEXTAREA, esse Label será atualizado com o novo valor.
Esse conceito é chamado de Two-Way-Binding, ou seja, o componente tem uma entrada de dados que modifica o componente e o componente modifica sua referência fora dele.
Isso é possível devido a existência do método TextChanged chamado dentro de OnChangeAsync.
Para esse tipo de binding, a regra é: Crie uma propriedade (com atributo Parameter) do tipo EventCallback com o mesmo nome da propriedade que queira fazer o binding (no caso Text) + sufixo Changed:
[Parameter]
public EventCallback<string> TextChanged { get; set; }
E por fim chame esse evento em algum momento na sua Model.
Blazor: DropDownList e DropDownListItem
A diferença do DropDownList para o TextBox é a existência de um DataSource, ou seja uma lista de items/options representados por foreach.
DropDownList.razor:
@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>
A Model DropDowListComponent tem a mesma lógica do TextBoxComponent, com uma pequena ressalva.
O DropDownList pode receber outras tags HTML/Componentes em seu Body.
Vamos lembrar:
<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>
Veja que estou passando outro componente DropDownListItem (filho) dentro do componente DropDownList (pai).
Isso é possível por essa simples propriedade na Model DropDowListComponent:
[Parameter]
public RenderFragment ChildContent { get; set; }
E também é ncessário chamar as seguintes tags na View do DropDownList:
<CascadingValue Value=this>
@ChildContent
</CascadingValue>
A lógica é, quando o componente filho DropDownListItem for executado, esse terá uma referência ao pai DropDownList, que por sua vez irá inserir a opção/item correspondente na propriedade DataSource do pai.
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();
}
}
Ao usar as tags CascadingValue no DropDownList (pai) e que contenha uma propriedade com atributo [CascadingParameter] no DropDownListItem (filho), podemos acessar o componente pai.
Repare no evento interno OnInitialized do DropDownListItem.
É através do método AddItem que o item/option é inserido no componente pai.
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();
}
}
Baixe o código fonte no meu github e veja as customizações dos componentes.
Blazor: Observações
Em uma aplicação Blazor:
O arquivo _Imports.razor funciona como se fosse um arquivo web.config localizado dentro da pasta Views no ASP.NET MVC tradicional.
No arquivo Startup.cs, está a configuração e inicialização da aplicação ASP.NET CORE / Blazor, vide:
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
});
}
A última configuração, a mais interessante, é aquela que mapeaia client/server para utilização do SignalR como forma de escutar/propagar as alterações entre View e/ou Model.
Quando criar seus Components ou Pages, se você criar a Model (classe C#) com o mesmo nome do arquivo da View, por exemplo DropDownListItem.razor (View) e DropDownListItem.razor.cs (Model), o Visual Studio irá agrupá-los na Solution facilitando bastante a manutenção do código fonte.
As Razor Views e Tag Helpers ainda funcionam pois é uma aplicação comum ASP.NET CORE.
Por fim, para conhecimento geral, a mágica do Blazor só funciona com o arquivo Pages/_Host.cshtml, que chama a App.razor e o arquivo de script blazor.server.js, responsável pelo gerenciamento do SignalR.
O Blazor é muito mais poderoso do que imaginava, e com certeza irá revolucionar o desenvolvimento web, ainda mais após a conclusão da versão Blazor Web Assembly.
E aí? Já usa Blazor em seus projetos? Comenta aí!
Obrigado 🙂
Artigos sobre Blazor e ASP.NET CORE:
Crie seu Framework em ASP.NET CORE 3 e Blazor
Blazor: O Começo do Fim do Javascript?
Benchmark: ASP.NET 4.8 vs ASP.NET CORE 3.0
AppSettings: 6 Formas de Ler o Config no ASP.NET CORE 3.0
JWT: Customizando o Identity no ASP.NET CORE 3.0
Crie um Gerenciador de Arquivos do Zero em .NET Core e VueJS
IIS: Como Hospedar Aplicação .NET Core em 10 Passos
.NET Core para Desenvolvedores .NET
| Faça download completo do código fonte no github. |








