Joris Lops
A component-based SPA framework that archieves interactivity with C#
Examples + Exercises: https://github.com/jorislops/BlazorCourse
Blazor Web App
9.0 (or higher)
We need to set the Render Mode globally (if we use Rider)
//check in Program.cs if the last line is presented
app.MapRazorComponents<App>()
.AddInteractiveServerComponents();Enable (Check) Interactive Server Render Mode
In the example project, check and copy
See next slide
//App.razor
@inject IWebHostEnvironment WebHostEnvironment
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/>
<link rel="stylesheet" href="bootstrap/bootstrap.min.css"/>
<link rel="stylesheet" href="app.css"/>
<link rel="stylesheet" href="BlazorCourse.styles.css"/>
<link rel="icon" type="image/png" href="favicon.png"/>
<script type="module" src="https://cdn.jsdelivr.net/npm/@@fluentui/web-components@@2.5.16/dist/web-components.min.js"></script>
<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender:
!WebHostEnvironment.IsDevelopment())"/>
</head>
<body>
<Routes @rendermode="new InteractiveServerRenderMode(prerender:
!WebHostEnvironment.IsDevelopment())"/>
<script src="_framework/blazor.web.js"></script>
</body>
</html>Navigate to the /counter.razor
Directives
Markup (Razor)
Logic
Binding
Binding
!rendermode
not need
global!
Directives
Markup (Razor)
Logic
Binding
Binding
Directives are built-in macros that alter the transpiled C# code that is generated from Razor mark-up. Directives are used by preceding the identifier with the @ symbol, the identifier being what we'd typically expect to be either the name of an HTML attribute or the name of a component's property.
Take a look at RazorSyntax.razor
or demo!
<ul>
@{
string[] names = [ "Joris", " ", "Nick", "Jos", "Dick" ];
}
@foreach (var name in names)
{
if (string.IsNullOrWhiteSpace(name))
{
<li>Empty name</li>
}
else
{
<li>@name</li>
}
}
</ul>
<h2>One way binding</h2>
<p>@_dateTimeMessage</p>
@code {
readonly string _dateTimeMessage = "The date and time is: " + @DateTime.Now;
}
<h2>Two way binding</h2>
<input @bind=_name size="100"/>
<p>@_name</p>
@code {
string _name = "Change the text and click outside the input field (or press enter) and you see the changes!";
}<input @bind="_name2" @bind:event="oninput" size="100"/>
<p>@_name2</p>
<h2>Two way binding, with C# (Blazor) event</h2>
<input value="@_name3" @oninput=@(e => _name3 = e.Value.ToString()) size="100"/>
<p>@_name3</p>
@code {
string _name3 = "Change the text and you see the changes immediately! This time with a C# event.";
}@bind:event="JavaScript event"
or
@oninput=@(C# code to execute)
There is a new way: bind:set, bind:get
options:
Relatively new (introduced in .NET 7), better?
Specify delegate event handlers in Razor component markup with @on{DOM EVENT}="{DELEGATE}" Razor syntax:
{DOM EVENT} placeholder is a DOM event (for example, click).{DELEGATE} placeholder is the C# delegate event handler.For event handling:
StateHasChanged.
Draw the idea on the board!
Let's view the files one by one
Directives
Markup (Razor)
Logic
Binding
Binding
What Are Blazor Components?
Component consist of:
So why a component?
Joris
Take a look:
/simple-component-example
The name of the component is the filename:
Simple definition of state:
The values that the instance-variables have!
Components "communicate" with each other:
[Parameter, EditorRequired] public TodoItem Item { get; set; }
Make it required! (if not optional)
Make it required! (if not optional)
[Parameter, EditorRequired]
public EventCallback<TodoItem> OnDeleteItem { get; set; }private void DeleteItem(TodoItem item)
{
// Call the delete method from the parent component
OnDeleteItem.InvokeAsync(item);
}
https://rolandguijt.com/blazor-chained-binds/
https://www.youtube.com/watch?v=oG5TOxiaEpM
<AddTodoForm
@ref="_addTodoForm"
OnAdd="AddNewTodo"></AddTodoForm>
<button class="btn btn-primary"
@onclick=@(() => _addTodoForm.ToggleVisibility())>Add</button>
[Parameter]
public RenderFragment<TodoItem>? TextContent { get; set; }
Seperate UI & Code
See SimpleComponentPartialExample.cs and SimpleComponentPartialExample.razor
Driver
MySqlConnector
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"bieren": "Server=localhost;Database=bieren;Uid=root;Pwd=Test@1234!;",
"todo": "Server=localhost;Database=TodoBlazorCourse;Uid=root;Pwd=Test@1234!;"
}
}
public class ConfigurationHelper
{
public static IConfiguration Configuration { get; set; } = null!;
}var app = builder.Build();
ConfigurationHelper.Configuration = app.Configuration;var connectionString = ConfigurationHelper.Configuration.GetConnectionString("todo");@page "/todo-without-repository"
@using MySqlConnector
@using Dapper
@using BlazorCourse.Models
<h3>Todo Without Repository</h3>
@foreach(var todo in _todos)
{
<p>@todo.Title</p>
}
@code {
private List<TodoItem> _todos = null!;
protected override void OnInitialized()
{
base.OnInitialized();
var connectionString = ConfigurationHelper.Configuration.GetConnectionString("todo");
using var connection = new MySqlConnection(connectionString);
_todos = connection.Query<TodoItem>("SELECT * FROM TodoItems").ToList();
}
}Without repository
The problem: (Page)Components get cluttered with db code
| CRUD | Repository | SQL |
|---|---|---|
| Create | Add(...) | INSERT |
| Read | GetById(id), GetAll(), Get(), etc | SELECT |
| Update | Update(...) | UPDATE |
| Delete | Delete/Remove | DELETE |
Routing Parameters
Take a look at
TodoRepository.cs
and
TodoDetailDb.razor
The razor pages are only responsible for the UI and not the database logic. Imagine how the size (readability) of the code is, if we didn't use a repository.
When the parameter changes, the OnParametersSet() function is called.
Use this to initialize components
Use OnInitialize() to initialize pagecomponents.
@page "/todo-details-db/{TodoId:int}"
[Parameter] //Route Parameter & Component Parameter public int TodoId { get; set; }
{paramName:constraint}
{Text?}
[Parameter] public string? Text { get; set; }
Zie BierRepository.cs
GetIncludeBrouwer()
The Blazor Rendering mode we use is only server side. This makes life easier!
public class Bier
{
public int Biercode { get; set; }
[Required, MinLength(2, ErrorMessage = "Naam moet minimaal twee charachters bevatten"), MaxLength(50)]
public string Naam { get; set; } = null!;
[Required, MinLength(2), MaxLength(30)]
public string Type { get; set; } = null!;
[Required, MaxLength(20)]
public string Stijl { get; set; } = null!;
[Required, Range(0, 16, ErrorMessage = "Alcoholpercentage moet tussen 0 en 16 liggen")]
public double Alcohol { get; set; }
[Required(ErrorMessage = "Je moet een brouwer invullen")]
public int? Brouwcode { get; set; }
public Brouwer Brouwer { get; set; } = null!;
}private Bier _bier = new Bier();<EditForm Model="_bier" OnValidSubmit="AddBierToDb">
<DataAnnotationsValidator></DataAnnotationsValidator>
<ValidationSummary></ValidationSummary>
<div class="mb-3">
<label for="naam" class="form-label">Naam</label>
<InputText class="form-control" id="naam" @bind-Value="_bier.Naam"/>
<ValidationMessage For=@(() => _bier.Naam)/>
</div>
<div class="mb-3">
<label for="type" class="form-label">Type</label>
<InputText class="form-control" id="type" @bind-Value="_bier.Type"/>
<ValidationMessage For=@(() => _bier.Type)/>
</div>
<div class="mb-3">
<label for="stijl" class="form-label">Stijl</label>
<InputText class="form-control" id="stijl" @bind-Value="_bier.Stijl"/>
<ValidationMessage For=@(() => _bier.Stijl)/>
</div>
<div class="mb-3">
<label for="alcohol" class="form-label">Alcohol</label>
<InputNumber class="form-control" id="alcohol" @bind-Value="_bier.Alcohol"/>
<ValidationMessage For=@(() => _bier.Alcohol)/>
</div>
<div class="mb-3">
<label for="brouwer" class="form-label">Brouwer</label>
<InputSelect class="form-control" id="brouwer" @bind-Value="_bier.Brouwcode">
<option value="">
Selecteer een brouwer
</option>
@foreach (var brouwer in _brouwers)
{
<option value="@brouwer.Brouwcode">@brouwer.Naam</option>
}
</InputSelect>
<ValidationMessage For=@(() => _bier.Brouwcode)/>
</div>
<button class="btn btn-danger" type="reset">Reset</button>
<button class="btn btn-primary" type="submit">Add Bier</button>
</EditForm><EditForm Model="_bier" OnValidSubmit="AddBierToDb">First, we need to create a Validator!
public class BierValidator : AbstractValidator<Bier>
{
public BierValidator()
{
RuleFor(b => b.Naam).NotEmpty()
.WithMessage("Error message From BierValidator.cs")
.MinimumLength(2).WithMessage("omvang te klein")
.MaximumLength(50).WithMessage("omvang te groot");
RuleFor(b => b.Type).NotEmpty()
.WithMessage("Error message From BierValidator.cs").MinimumLength(2).MaximumLength(30);
RuleFor(b => b.Stijl).NotEmpty().MaximumLength(20);
RuleFor(b => b.Alcohol).NotEmpty().InclusiveBetween(0, 16);
RuleFor(b => b.Brouwcode).NotEmpty();
}
}To use FluenValidation we need
Blazored.FluentValidation
Just install it and add a to the page or _Import.razor
@using Blazored.FluentValidation
Make sure to use the
<FluentValidationValidator>
Diagram on Board
SPA = Single Page Application
Show the routing:
App.razor --> Routes.razor --> MainLayout -> Body (razor page)
Take a look at this image:
Blazorschool has some interesting tutorials!
Generic classes:
This idea can also be applied to Blazor components:
T (the Type Parameter) is called TItem in BLazor
/blazorise-datagrid
<DataGrid TItem="Bier"
Data="@_bieren"
ReadData="args => OnReadData(args)"
TotalItems="@_totalItems"
PageSize="10" Sortable ShowPager Responsive
SortMode="DataGridSortMode.Single">
<DataGridCommandColumn/>
<DataGridColumn Field="@nameof(Bier.Naam)" Caption="Naam"/>
<DataGridColumn Field="@nameof(Bier.Alcohol)" Caption="Alcohol"/>
<DataGridColumn Field="@nameof(Bier.Stijl)" Caption="Stijl"/>
</DataGrid>Let's take a look at
A more advanced example (also includes cascade parameters to make nesting of components possible)
Do we have to make those components ourselves?
No, not invented here syndrome!
There are many good UI Component libraries, some are free (open-source), most of them have a demo page with code examples!
See Blackboard: FluentUI Example
Every component has Lifecycle events.
The most famous one:
protected override void OnInitialized() { base.OnInitialized(); _treeDataSource = NavigationTreeData.GetNavigationTree(); NavigationManager.LocationChanged += LocationChanged; }
OnInitialized: load data + register events.
Only once per Component/Page
Every component has Lifecycle events.
Take a look at:
https://blazor-university.com/components/component-lifecycles/
SetParameters(ParameterView parameters): When a parameter changes here we "can skip OnParametersSet", i.e. not send them to OnParametersSet() (not calling base.SetParametersAsync(parameters))
OnParameterSet(): When a parameter of a component changes.
OnAfterRender(...): after rendering (DOM is complete), for working with JavaScript
After an event a component is rerenderd! InvokeAsync(StateHasChanged): To trigger rerender a component,
when update is not detected by the component.
For demo:
/parent-child-and-service-example
The problem
For a component:
it's variables and property values
For an application:
For each user (tab in the browser) one circuit
Circuit is a piece of memory to hold state information
Client-side
url
Browser Storage (localStorage, sessionStorage)
cookies
Server-side
database
url
Service
Cascade Parameters
LoginService, see Blackboard (Les 4)
For transient data representing navigation state, model the data as a part of the URL. Examples of user state modeled in the URL include:
The contents of the browser's address bar are retained:
For information on defining URL patterns with the @page directive, see ASP.NET Core Blazor routing and navigation.
The normal way (HTML/Razor):
@{ var link = brouwer.Naam; if (brouwer.AantalBrouwersZelfdeNaam > 1) { link += $"/{brouwer.Land}"; } } <a href="/Bier/@link">Show Beers</a>
We need to obtain the Navigation Manager
@inject NavigationManager NavigationManager
Blazor code calls a method (GoToBeerPage) that needs navigation
<button class="btn btn-primary" @onclick="@(() => GoToBeerPage(brouwer))">
Show Beers</button>
Use the Navigation Manager to navigate to another page
private void GoToBeerPage(BrouwerVm brouwer)
{
var link = brouwer.Naam;
if (brouwer.AantalBrouwersZelfdeNaam > 1)
{
link += $"/{brouwer.Land}";
}
NavigationManager.NavigateTo($"/Bier/{link}");
}When to use: when you need navigation from C# code
var uriAsString = NavigationManager.Uri;
var url = new Uri(uriAsString);
var queryString = url.Query; // returns "?Brouwcode=someInt"
var queryParameters = HttpUtility.ParseQueryString(queryString);
//Query parameters are always strings and the key (Brouwcode) is case sensitive!!
var brouwcodeAsString = queryParameters["Brouwcode"]; // returns "someInt"
if (int.TryParse(brouwcodeAsString, out var brouwcode))
{
Brouwcode = brouwcode;
}The prefered (Blazor-way): [Parameter, SupplyParameterFromQuery]
[Parameter, SupplyParameterFromQuery]
public int? CodeFromBrouwer { get; set; }@page "/bier" @page "/bier/{Brouwcode:int}" @page "/bier/{Brouwnaam}/{Land}" @page "/bier/{Brouwnaam}"
[Parameter] public int? Brouwcode { get; set; } [Parameter] public string? Brouwnaam { get; set; } [Parameter] public string? Land { get; set; }
zie antwoord codepilot, plaatje op het bord (&vorige sheets)
https://copilot.microsoft.com/
blazor cascade parameters
Use-cases:
In summary, use cascading parameters when you want to pass data within a specific component hierarchy, and use a service with events when you need to manage global state or communicate between unrelated components. Choose the approach that best fits your specific use case!
https://copilot.microsoft.com/
Blazor when to use a service with events or cascade parameters?
Example of Login
See Blackboard
Demo it
Draw a picture
We can register for a service
and a service maintains Action(s) to which consumer can register
publis-subscribe pattern (event)