Ca faisait longtemps que je n'avais pas fait un article sur Blazor.
Si vous avez manqué les 3 premiers articles sur le sujet:
Depuis mon dernier article sur le sujet, Blazor a bien évolué. Je continue à tester cette technologie. Je trouve que les évolutions vont dans le bon sens et c'est de plus en plus agréable de faire du Web avec. Il y a de plus en plus de bibliothèques disponibles qui vont simplifier le travail.
Je me concentrerai dans cet article sur les changements de Blazor.
Afin d'illustrer ces évolutions, j'ai mis à jour ma solution de lecteur musical : Song.Net. J'en ai profité pour rajouter une section qui permet de récupérer et lire des podcasts. Vous pourrez ainsi alterner vos musiques avec vos émissions préférées.
Le code source du lecteur se trouve toujours ici.
Asp.Net Core 3.1 est la version sur laquelle vont se baser les futurs développements Web dans l'écosystème .Net.
Elle bénéficie d'un support pour 3 ans. Si vous développez avec une ancienne version, il est conseillé de migrer vers 3.1.
Pour rappel, il y a 2 façons de faire du Blazor :
La syntaxe du Razor a quelque peu changé. Les attributs qui correspondent à des actions Razor sont maintenant précédés d'un arobase. On peut renseigner les méthodes ou propriétés de ces attributs directement sans double quote. Cela permet de mieux de les distinguer des attributs HTML.
<!-- Avant -->
<a class="button is-info" onclick="@FilterClick">
<!-- Après -->
<a class="button is-info" @onclick=FilterClick>
Pour le bootstrap d'une solution cliente Blazor, le code a été grandement simplifié. Plus de startup.cs, tout est dans le Main.
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
BlazorWebAssemblyHost.CreateDefaultBuilder()
.UseBlazorStartup<Startup>();
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDataManager, DataManager>();
}
public void Configure(IBlazorApplicationBuilder app)
{
app.AddComponent<App>("app");
}
}
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddSingleton<IDataManager, DataManager>();
builder.RootComponents.Add<App>("app");
builder.Services.AddBaseAddressHttpClient();
await builder.Build().RunAsync();
}
}
On peut désormais avoir du code d'un composant dans une classe partielle et ainsi, séparer facilement le Razor du code C#. Cela permet de mieux séparer la présentation de la logique.
player.cshtml
@using Blazor.Song.Net.Client.Wrap;
@using Blazor.Song.Net.Shared;
@using System.Linq;
@inject HttpClient Http;
@inject Services.IDataManager Data;
<div name="player" class="content">
<div name="playerInfoPanel" class="frame">
<PlayerAudio ref="playerAudio" bind-IsPlaying="@IsPlaying" />
<PlayerInfo ref="playerInfo" PlayerAudio="@playerAudio" />
</div>
<div class="columns is-mobile" name="playerControlPanel">
<button class="column button is-info" onclick="@PreviousTrackClick"><i class="fa fa-fast-backward"></i></button>
<button class="column button is-info" onclick="@(async () => { await playerAudio.AddTime(-5); })"><i class="fa fa-backward"></i></button>
<button class="column button is-info" onclick="@(async () => { IsPlaying = !IsPlaying; })">
@if (IsPlaying)
{
<i class="fa fa-pause"></i>
}
else
{
<i class="fa fa-play"></i>
}
</button>
<button class="column button is-info" onclick="@(async () => { await playerAudio.AddTime(5); })"><i class="fa fa-forward"></i></button>
<button class="column button is-info" onclick="@NextTrackClick"><i class="fa fa-fast-forward"></i></button>
</div>
</div>
@functions {
PlayerAudio playerAudio;
PlayerInfo playerInfo;
bool IsPlaying { get; set; }
[CascadingParameter]
ObservableList<TrackInfo> PlaylistTracks { get; set; }
int TimeStatus { get; set; }
protected override void OnInit()
{
Wrap.Functions.SetTimeout(RefreshTimeStatus, 900);
if (Data.CurrentTrack != null)
UpdateTitle(Data.CurrentTrack);
Data.CurrentTrackChanged += CurrentTrackChanged;
base.OnInit();
}
public override void SetParameters(ParameterCollection parameters)
{
Console.WriteLine("SetParameters");
base.SetParameters(parameters);
}
public void SetCurrentTrackNext()
{
if (PlaylistTracks.Count <= 1)
return;
Data.CurrentTrack = PlaylistTracks[(PlaylistTracks.IndexOf(Data.CurrentTrack) + 1) % PlaylistTracks.Count];
}
private void CurrentTrackChanged(TrackInfo info)
{
UpdateTitle(info);
}
private void UpdateTitle(TrackInfo info)
{
if (info != null)
Document.UpdateTitle(info.Title + ", " + info.Artist + " - Blazor Song.Net");
else
Document.UpdateTitle("Blazor Song.Net");
}
private void NextTrackClick()
{
SetCurrentTrackNext();
}
void PreviousTrackClick()
{
if (PlaylistTracks.Count <= 1)
return;
if (PlaylistTracks.IndexOf(Data.CurrentTrack) == 0)
Data.CurrentTrack = PlaylistTracks[PlaylistTracks.Count - 1];
else
Data.CurrentTrack = PlaylistTracks[(PlaylistTracks.ToList().IndexOf(Data.CurrentTrack) - 1) % PlaylistTracks.Count];
}
void RefreshTimeStatus()
{
if (playerAudio != null && Data.CurrentTrack != null && Data.CurrentTrack.Duration.TotalSeconds != 0)
{
playerAudio.GetCurrentTime().ContinueWith((res) =>
{
TimeStatus = 100 * res.Result / (int)Data.CurrentTrack.Duration.TotalSeconds;
playerInfo.Refresh(TimeStatus);
});
}
Wrap.Functions.SetTimeout(RefreshTimeStatus, 900);
}
}
Player.razor
@using Blazor.Song.Net.Shared
<div name="player" class="content">
<div name="playerInfoPanel" class="frame">
<PlayerAudio @ref=playerAudio @bind-IsPlaying=IsPlaying />
<PlayerInfo @ref=playerInfo PlayerAudio=@playerAudio />
</div>
<div class="columns is-mobile" name="playerControlPanel">
<button class="column button is-info" @onclick=PreviousTrackClick><i class="fa fa-fast-backward"></i></button>
<button class="column button is-info" @onclick="(async () => { await playerAudio.AddTime(-5); })"><i class="fa fa-backward"></i></button>
<button class="column button is-info" @onclick="(async () => { IsPlaying = !IsPlaying; })">
@if (IsPlaying)
{
<i class="fa fa-pause"></i>
}
else
{
<i class="fa fa-play"></i>
}
</button>
<button class="column button is-info" @onclick="(async () => { await playerAudio.AddTime(5); })"><i class="fa fa-forward"></i></button>
<button class="column button is-info" @onclick=NextTrackClick><i class="fa fa-fast-forward"></i></button>
</div>
</div>
player.razor.cs
using Blazor.Song.Net.Client.Wrap;
using Blazor.Song.Net.Shared;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Linq;
using System.Threading.Tasks;
namespace Blazor.Song.Net.Client.Shared
{
public partial class Player : ComponentBase
{
protected PlayerAudio playerAudio;
protected PlayerInfo playerInfo;
public Document Document { get; private set; }
public Functions Functions { get; private set; }
[Inject]
public IJSRuntime JsRuntime { get; private set; }
[Inject]
protected Services.IDataManager Data { get; set; }
protected bool IsPlaying { get; set; }
[CascadingParameter]
private ObservableList<TrackInfo> PlaylistTracks { get; set; }
private int TimeStatus { get; set; }
public void SetCurrentTrackNext()
{
if (PlaylistTracks.Count <= 1)
return;
Data.CurrentTrack = PlaylistTracks[(PlaylistTracks.IndexOf(Data.CurrentTrack) + 1) % PlaylistTracks.Count];
}
public override async Task SetParametersAsync(ParameterView parameters)
{
await base.SetParametersAsync(parameters);
}
protected void NextTrackClick()
{
SetCurrentTrackNext();
}
protected override async Task OnInitializedAsync()
{
Document = new Document(JsRuntime);
Functions = new Functions(JsRuntime);
Functions.SetTimeout(RefreshTimeStatus, 900);
if (Data.CurrentTrack != null)
await UpdateTitle(Data.CurrentTrack);
Data.CurrentTrackChanged += CurrentTrackChanged;
await base.OnInitializedAsync();
}
protected void PreviousTrackClick()
{
if (PlaylistTracks.Count <= 1)
return;
if (PlaylistTracks.IndexOf(Data.CurrentTrack) == 0)
Data.CurrentTrack = PlaylistTracks[PlaylistTracks.Count - 1];
else
Data.CurrentTrack = PlaylistTracks[(PlaylistTracks.ToList().IndexOf(Data.CurrentTrack) - 1) % PlaylistTracks.Count];
}
private async Task CurrentTrackChanged(TrackInfo info)
{
await UpdateTitle(info);
}
private void RefreshTimeStatus()
{
if (playerAudio != null && Data.CurrentTrack != null && Data.CurrentTrack.Duration.TotalSeconds != 0)
{
playerAudio.GetCurrentTime().ContinueWith((res) =>
{
TimeStatus = (int)(100 * res.Result / Data.CurrentTrack.Duration.TotalSeconds);
playerInfo.Refresh(TimeStatus);
});
}
Functions.SetTimeout(RefreshTimeStatus, 900);
}
private async Task UpdateTitle(TrackInfo info)
{
if (info != null)
await Document.UpdateTitle($"{info.Title}, {info.Artist} - Blazor Song.Net");
else
await Document.UpdateTitle("Blazor Song.Net");
}
}
Avant, on utilisait la classe statique JSRuntime pour appeler des méthodes Javascript
public void Play()
{
JSRuntime.Current.InvokeAsync<bool>("audioElement.play", _id);
}
C'est maintenant un service qu'on peut injecter. Beaucoup plus pratique pour les tests
public AudioElement(string id, IJSRuntime jsRuntime)
{
JsRuntime = jsRuntime;
}
public void Play()
{
JsRuntime.InvokeAsync<bool>("audioElement.play", _id);
}
Blazor permet de réaliser des solutions web complètes. La communauté est active et enthousiaste.
Rejoignez le mouvement. A vous de jouer !
Commentaires :
Aucun commentaires pour le moment
Laissez un commentaire :