Blazor : Song.Net en .Net Core 3.1 avec podcasts

ASP.NET CoreBlazor.NET Core
Jean-Baptiste Raulin - 06/05/2020 à 16:50:470 commentaire

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


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.


Blazor


Pour rappel, il y a 2 façons de faire du Blazor :

  • Blazor Server, supporté depuis Asp.Net Core 3.0 où les calculs se font côté serveur
  • Blazor Wasm, qui profite du Web Assembly présent dans les navigateurs Web. Cette méthode est toujours en preview sous Asp.Net Core 3.1. C'est celle que j'utilise ici.


Côté Razor


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>


Côté code


Pour le bootstrap d'une solution cliente Blazor, le code a été grandement simplifié. Plus de startup.cs, tout est dans le Main.


Avant

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");
    }
}


Après

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();  
    }
}


Composants


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.


Avant

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 { getset; }

   [CascadingParameter]
   ObservableList<TrackInfo> PlaylistTracks { getset; }

   int TimeStatus { getset; }

   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);
   }
}


Après

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 { getprivate set; }
 
        public Functions Functions { getprivate set; }
 
        [Inject]
        public IJSRuntime JsRuntime { getprivate set; }
 
        [Inject]
        protected Services.IDataManager Data { getset; }
 
        protected bool IsPlaying { getset; }
 
        [CascadingParameter]
        private ObservableList<TrackInfo> PlaylistTracks { getset; }
 
        private int TimeStatus { getset; }
 
        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");
        }
    }


L'interopérabilité avec le javascript


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 :

Réalisé par
Expaceo