blazor-hero-banner

Aujourd’hui dans ce nouvel article, je vais vous présenter Blazor 🦖

1. C’est quoi Blazor ?

Blazor est un framework web open source C# .net développé par Microsoft, lancé en 2018 et qui s’appuie sur Razor pour fournir du HTML dynamique.
Il permet en autre d’utiliser du code, des libraires et la logique métier C# dans une application web, tout en offrant une expérience utilisateur rapide et fluide. 

Le framework met à disposition des composants pré-fait afin de faciliter et de réduire le temps de développement.
Cette liste de composants peut être enrichie avec l’utilisation d’autres frameworks et d’autres composants (Material Design, Bootstrap, Fontawsome).

Blazor est un framework isomorphique c’est à dire qu’il peut s’exécuter dans plusieurs types d’environnements.

1.a Web Assembly

Web Assembly (WASM) est une application client-side qui permet de compiler le code C# afin de s’exécuter dans le navigateur web du client.
C’est une technologie qui est très performante avec une exécution dans un sand-box. Sauf pour le premier chargement qui est long puisqu’il doit installer tous les packages.

L’échange de données client <-> serveur ce fait avec des appels api HTTP.

1.b Server side

Ce mode de rendu offre de très bonnes performances puisque le serveur va travailler en plus pour rendre le HTML, le style ainsi que le javascript, à la place du client. Mais le server-side a un inconvénient, il consomme beaucoup de ressources.
En effet, pour chaque client connecté, le serveur génère et gère une instance du site, ce qui implique une utilisation du CPU et de la mémoire cache.

Le serveur side rendering est idéal pour les applications qui ont des besoins en SEO (Search Engine Optimization), en sécurité ou bien qui s’exécute dans des environnements qui dispose de peu de ressource matérielle comme les télévisions connectées et autres appareils IoT. Exemple, Netflix qui est une application server side (ref : A Netflix Web Performance Case Study )

Niveau architecture, le frontend et le backend n’existe pas à proprement parler, puisque les 2 sont sur le même server. Comme expliqué précédemment, le serveur fait tout et envoie les pages au client par le biais d’un Hub SignalR qu’il utilise par la suite pour transmettre des données bi directionnellement entre le serveur et le client (résultat d’une requête à une API, enregistrement de données…).

Ainsi pour appeler des services back depuis le front, il est recommandé d’appeler ces services via l’injection de dépendances ou directement la méthode visée sans passer par des endpoints appelé en HTTP, car le context http (cookies, token …) ne sera pas transmis puisque les requêtes internes (front <-> back) sont fantômes, totalement invisibles.
C’est notamment le cas si vous utilisez des Authentification policies, certaines exigent d’être authentifié pour les requêtes entrantes (ce qui comprend également ces requêtes fantômes).

schema-server-side

1.c Pre-rendering

Le pre-rendering est un mixte des deux précédentes technologies. Le serveur s’occupe de précompiler le code HTML et d’envoyer une page statique au client qui n’a plus qu’à l’afficher.

Si le client a besoin d’appeler du code C# pour des évènements ou pour récupérer des données, cela se fait automatiquement via la websocket de SignalR. Quant au JavaScript, c’est le client qui s’en occupe.

shema-prerendering

2. Les fondamentaux

2.a Prérequis

Pour ce projet de découverte, il vous faut les outils suivants :

Ainsi que des connaissances en C# et HTML/CSS/JS.

Pour créer le projet, sur Visual Studio, créez un nouveau projet Application WebAssembly Blazor ou en CLI dotnet new blazorwasm –o BlazingApp

2.b Créer un composant

Comme vu en introduction de cette article, Blazor utilise le moteur de vues et de composants Razor. 
Un composant contient à la fois du code HTML et du C# et est réutilisable.

On commence par créer notre premier composant pour afficher le nom et le prénom de l’utilisateur :

/* MonComposant.razor */
<h3>Mon Composant Blazor</h3>
<p>Bonjour @UserFullName()</p>

@code {
	User currentUser = new User { FirstName = "John", FamilyName = "Doe" };
	private string UserFullName()
	{
		return $"{currentUser.FirstName} {currentUser.FamilyName}";
	}
}
<div>
	<MonComposant />
</div>

Maintenant créons un composant qui prend des paramètres :

/* ViewCart.razor */
@if (inProgress) {
	<p> Chargement en cours ...</p>
} else {
	if (ItemsInCart != null && ItemsInCart.Count > 0){
		<h3>@ItemsInCart.Count articles</h3>
		foreach(var item in ItemsInCart){
			<div>
				<p>@item.Name</p>
			</div>
		}
	} else {
		<p>Panier vide<p>
	}
}

@code {
	// Paramètres
	[Parameter] public bool InProgress { get; set; }
	[Parameter] public List<Item>? ItemsInCart { get; set; }
}
<ViewCart InProgress="@inProgress" ItemsInCart="@items" />

Et pour finir, un composant qui contient des composants enfants :

/* ItemCard.razor */
<div>
	<div>@CardHeader</div>
	<div>@CardBody</div>
	<div>@CardFooter</div>
</div>

@code {
	// Paramètres composants enfants
	[Parameter] public RenderFragment CardHead { get; set; }
	[Parameter] public RenderFragment CardBoody { get; set; }
	[Parameter] public RenderFragment CardFooter { get; set; }
	// Paramètres
	[Parameter] public Item Item { get; set; }
}
<ItemCard Item="@item">
	<CardHead>
		<h4>@item.Name</h4>
	</CardHead>
	<CardBody>
		<div>
			<p>@item.Description</p>
		</div>
	</CardBody>
	<CardFooter>
		<div>
			<span>@item.Prize</span> <span>€</span>
		</div>
	</CardFooter>
</ItemCard>

Ainsi, si vous voulez faire un composant avec 1 seul composant enfant, il vous suffit de déclarer un paramètre [Parameter] public RenderFragment ChildContent { get; set; } et d’ajouter le/les fragments directement entre les balises de votre composant sans spécifier de nom de balise.

Tout comme ses concurrents React et Vue, les composants Blazor dispose d’un cycle de vie.

  1. OnInitialized(): Méthode qui s’exécute lors de l’initialisation du composant. Peut être utilisé en asynchrone avec OnInitializedAsync().
  2. OnParametersSet(): Méthode appelée lorsque le composant à reçu les paramètres du composant parent. Peut être utilisé en asynchrone avec OnParametersSet().
  3. ShouldRender(): Permet de désactiver l’actualisation du composant.
  4. OnAfterRender(): S’exécute quand le composant est rendu. Peut être utilisé en asynchrome avec OnAfterRenderAsync().

2.c Créer une page

Une page n’est ni plus ni moins un composant auquel on va rajouter un décorateur @page "/{path}". Rien d’autre à faire, Blazor gère le routing des pages.
Pour ajouter une page avec un chemin qui contient des paramètres, rien de plus simple :

/* ViewItem.razor */
@page "/item/{id:int}"
....
@code {
	[Parameter] public int Id { get; set; }
}

2.d JavaScript

Avec Blazor, il possible d’ajouter du code JavaScript à exécuter directement depuis le code C#.
Pour cela nous allons créer une extension à la classe JSRuntimeExtensions et y ajouter une fonction pour afficher un message de confirmation quand on supprime un item du panier.

/* JSRuntimeExtensions.cs */
using Microsoft.JSInterop;

public static class JSRuntimeExtensions
{
	public static ValueTask<bool> Confirm(this IJruntime jsRuntime, string message)
	{
		return jsRuntime.InvockeAsync<bool>("confirm", message);
	}
}
/* Checkout.razor */
@page "/checkout"
@inject IJSRuntime JS
....
public async Task OnDeleteItem(Item deleteItem)
{
	if (await JS.Confirm($"Voulez-vous supprimer {deleteItem.Name} de votre panier ?"))
	{
		ItemsInCart.Remove(item);
	}
}

2.e Deep dive

Dans cette partie, nous irons un peu plus loin sur les fonctionnalités que nous offres Blazor.

2.e.a Formulaires

Blazor dispose d’un système de validation de saisie de données qui se met en place coté serveur et coté client. Dans cet exemple nous allons voir comment le mettre en place coté client. 

Cette validation se base sur un EditContext qui stocke l’état du formulaire et sur le composant <EditForm> qui est en apparence un simple <form> HTML mais qui intègre cette mécanique. \ Prenons pour exemple, le formulaire de renseignement de l’adresse de livraison :

  1. Créons le model DeliveryAddress qui contient les propriétés d’une adresse de livraison et pour chaque propriété, des contraintes de validation (Required, MaxLentgh, voir plus)
public class DeliveryAddress 
{
	[Required]
	public string Road { get; set; }
	[Required]
	public string City { get; set; }
	[Required, MaxLength(20)]
	public string PostalCode { get; set; }
}
  1. Passons au formulaire :
/* Checkout.razor */
....
<EditForm Model="DeliveryAddress" OnValidSumbit="OnSubimit">
	<div class="form-field">
	    <label>Rue:</label>
	    <div>
	        <InputText @bind-Value="DeliveryAddress.Road" />
	        <ValidationMessage For="@(() => DeliveryAddress.Road)" />
	    </div>
	</div>

	<div class="form-field">
		<label>Ville:</label>
		<div>
			<InputText @bind-Value="DeliveryAddress.City" />
			<ValidationMessage For="@(() => DeliveryAddress.City)" />
		</div>
	</div>

	<div class="form-field">
		<label>Code postal:</label>
		<div>
			<InputText @bind-Value="DeliveryAddress.PostalCode" />
			<ValidationMessage For="@(() => DeliveryAddress.PostalCode)" />
		</div>
	</div>
	<input type="submit" />
	<!-- Permet d'afficher les erreurs sous les champs du formulaire -->
	<DataAnnotationsValidator />
</EditForm>

Voici ce que nous obtenons lorsque la saisie des champs est incorrecte : form-validation

2.e.b Identification & Autorisation

Comme évoqué en introduction, le framework met à disposition des composants pré-faits. C’est notamment ceux qui concernent l’autorisation que nous allons voir dans cette partie.

Le composant <AuthorizeView> permet d’afficher ou masquer du contenu selon l’état d’identification de l’utilisateur, obtenu par l’injection de dépendance avec le service : services.AddAuthentification() et le app.UseAuthorization().
Ce composant dispose de 3 composants enfants :

  • <Authorizing> : Contenu affiché durant l’identification.
  • <Authorized> : Contenu affiché quand l’utilisateur et connecté/autorisé.
  • <NotAuthorized> : Contenu affiché quand l’utilisateur n’est pas connecté/autorisé.

Pour illustrer cela, reprenons le composant MonComposant qui affiche un petit message de bienvenu :

/* MonComposant.razor */
<h3>Mon Composant Blazor</h3>
<AuthorizeView>
	<Authorized>
		<p>Bonjour @UserFullName()</p>
	</Authorized>
	<NotAuthorized>
		<p>Bonjour inconnu</p>
	</NotAuthorized>
</AuthorizeView>

3. Comparatif avec Next.JS

Next.JS est un framework web basé sur React et sur Node.JS. Tout comme Blazor, il est isomorphique et dispose également de 3 modes de rendus : Server Side Rendering (SSR), Static Site Generation (SSG) et Incremental Static Regeneration (ISR) qui sont très similaire à ce que propose Blazor.  

Next.JS dispose d’un système de routing différent de celui de Blazor. Blazor utilise un décorateur avec un chemin à renseigner manuellement alors que Next.JS se base sur l’arborescence avec le nom des dossiers et fichiers. 

Il reprendre également la logique de composant pour créer des pages 

Ces deux Frameworks sont donc très semblables. Mais, un s’intègre parfaitement sur un environnement C# et l’autre sur un environnement JavaScript.

Par comparaison, la taille d’une application client side est de 1 à 2 MB pour Next.JS et d’environ 24 MB pour Blazor soit quasiment 20x plus.

4. Conclusion

D’après moi, Blazor est un Framework pratique/simple/génial à utiliser pour faire de petites applications et monter rapidement un front à un back C#. Il est très facile à prendre en main lorsqu’on connait la stack .Net..
Quelques connaissances en HTML/CSS/JavaScript sont nécessaires pour créer une application avec Blazor mais cela reste à la portée d’un développeur back-end .Net. De nombreux composants déjà fait existent.
Il reprend le concept de composant qu’on retrouve dans l’autre framework (React, Vue), la mise en place d’IHMs dynamiques par l’ajout de conditions et de variables dans le HTML est très simple à faire.
Ce Framework permet de développer des applications web modernes en utilisant la stack .Net quasiment partout. Cela peut être un gros avantage en fonction des ressources disponibles sur un projet.

Dans un prochain article, nous allons voir comment effectuer des tests d’intégration, faire de la CI/CD et déployer une application Blazor sur le cloud ☁

Liens utiles :