1. 程式人生 > >【Blazor】在ASP.NET Core中使用Blazor元件 - 建立一個音樂播放器

【Blazor】在ASP.NET Core中使用Blazor元件 - 建立一個音樂播放器

前言

Blazor正式版的釋出已經有一段時間了,.NET社群的各路高手也建立了一個又一個的Blazor元件庫,其中就包括了我和其他小夥伴一起參與的AntDesign元件庫,於上週終於釋出了第一個版本0.1.0,共計完成了59個常用元件,那麼今天就來聊一聊如何在ASP.NET Core MVC專案中使用這些Blazor元件吧

 

環境搭建

.NET Core SDK 3.0.301

Vistual Studio 2019.16.6.3

 

呼叫Blazor元件

建立ASP.NET Core MVC專案,如果想要在已有的專案上使用AntDesign,需要確保Target Framework是netcoreapp3.1,然後在Nuget中搜索並安裝AntDesign 0.1.0版本。

修改Startup.cs

在ConfigureServices方法中新增

1 // add for balzor
2 services.AddServerSideBlazor();
3 // add for AntDesign
4 services.AddAntDesign();

在Configure方法中新增

1 app.UseEndpoints(endpoints =>
2 {
3     endpoints.MapControllerRoute(
4     name: "default",
5     pattern: "{controller=Home}/{action=Index}/{id?}");
6     // add for blazor
7     endpoints.MapBlazorHub();
8 });

修改./Views/Shared/_Layout.cshtml

在head區域新增

1 <!--add for AntDesign-->
2 <link href="/_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet">
3 <base href="/" />

在script區域新增

1 <!--add for blazor-->
2 <script src="~/_framework/blazor.server.js"></script>

 

這裡我們需要利用一箇中間層,否則直接在View裡新增元件會有很多限制,不太方便

建立一個razor檔案./Components/HelloWorld.razor

 1 @using AntDesign
 2  
 3 <Button type="primary" OnClick="(e)=>OnClick(e)">@_content</Button>
 4  
 5 @code{
 6     private string _content = "Primay";
 7     private void OnClick(Microsoft.AspNetCore.Components.Web.MouseEventArgs args)
 8     {
 9         _content += "*";
10     }
11 }

最後在View中新增剛剛新建的中間元件

修改./Views/Home/Index.cshtml,新增程式碼

1 <component type="typeof(HelloWorld)" render-mode="ServerPrerendered" />

 

Build & Run

這時候主頁應該會出現一個ant-design風格的button,點選後button的內容會變為Priamary*,每點選一次就多一個*,效果如下

 

小結

一般來說,在MVC專案中,先將介面需要使用的元件組合在一起,然後整體包裝在一箇中間元件(HelloWolrd.razor)中,最後在呼叫的View中展示中間元件。可以理解為元件庫為我們提供了各種各樣的零件,中間層將這些零件(以及原生HTML標籤)組合成一個產品,最後在View中展示產品。

 

建立一個播放器元件

首先我們建立好需要用到的JavaScript指令碼

Nuget安裝Microsoft.TypeScript.MSBuild

建立檔案main.ts

 1 interface Window {
 2     SoBrian: any;
 3 }
 4  
 5 function Play(element, flag) {
 6     var dom = document.querySelector(element);
 7     if (flag) {
 8         dom.play();
 9     }
10     else {
11         dom.pause();
12     }
13 }
14  
15 function GetMusicTime(element) {
16     var dom = document.querySelector(element);
17     let obj = {
18         currentTime: dom.currentTime,
19         duration: dom.duration
20     }
21     let json = JSON.stringify(obj);
22  
23     return json
24 }
25  
26 function SetMusicTime(element, time) {
27     var dom = document.querySelector(element);
28     dom.currentTime = time;
29 }
30  
31 window.Music = {
32     print: Print,
33     play: Play,
34     getMusicTime: GetMusicTime,
35     setMusicTime: SetMusicTime
36 }

建立檔案tsconfig.json

{
  "compileOnSave": true,
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": false,
    "target": "es2015",
    "outDir": "wwwroot/js"
  },
  "files": [ "main.ts" ],
  "exclude": [
    "node_modules",
    "wwwroot"
  ]
}

建立資料夾./wwwroot/music/

放入幾首你喜歡的音樂,但要注意支援的檔案格式

<audio> can be used to play sound files in the following formats:

.mp3: Supported by all modern browsers.
.wav: Not supported by Internet Explorer.
.ogg: Not supported by Internet Explorer and Safari.
 

建立./Components/MusicPlayer.razor

 1 @namespace SoBrian.MVC.Components
 2 @inherits AntDesign.AntDomComponentBase
 3  
 4 <audio id="audio" preload="auto" src="@_currentSrc"></audio>
 5 <div>
 6     <AntDesign.Row Justify="center" Align="middle">
 7         <AntDesign.Col Span="4">
 8             <p>@System.IO.Path.GetFileNameWithoutExtension(_currentSrc)</p>
 9         </AntDesign.Col>
10         <AntDesign.Col Span="4">
11             <AntDesign.Space>
12                 <AntDesign.SpaceItem>
13                     <AntDesign.Button Type="primary" Shape="circle" Icon="left" OnClick="OnLast" />
14                 </AntDesign.SpaceItem>
15                 <AntDesign.SpaceItem>
16                     <AntDesign.Button Type="primary" Shape="circle" Icon="@PlayPauseIcon" Size="large" OnClick="OnPlayPause" />
17                 </AntDesign.SpaceItem>
18                 <AntDesign.SpaceItem>
19                     <AntDesign.Button Type="primary" Shape="circle" Icon="right" OnClick="OnNext" />
20                 </AntDesign.SpaceItem>
21             </AntDesign.Space>
22         </AntDesign.Col>
23         <AntDesign.Col Span="9">
24             <AntDesign.Slider Value="@_currentTimeSlide" OnAfterChange="OnSliderChange" />
25         </AntDesign.Col>
26         <AntDesign.Col Span="3">
27             <p>@($"{_currentTime.ToString("mm\\:ss")} / {_duration.ToString("mm\\:ss")}")</p>
28         </AntDesign.Col>
29     </AntDesign.Row>
30 </div>

建立./Components/MusicPlayer.razor.cs

  1 public partial class MusicPlayer : AntDomComponentBase
  2 {
  3     private bool _isPlaying = false;
  4     private bool _canPlayFlag = false;
  5     private string _currentSrc;
  6     private List<string> _musicList = new List<string>
  7     {
  8         "music/周杰倫 - 蘭亭序.mp3",
  9         "music/周杰倫 - 告白氣球.mp3",
 10         "music/周杰倫 - 聽媽媽的話.mp3",
 11         "music/周杰倫 - 園遊會.mp3",
 12         "music/周杰倫 - 夜曲.mp3",
 13         "music/周杰倫 - 夜的第七章.mp3",
 14         "music/周杰倫 - 擱淺.mp3"
 15     };
 16     private Timer _timer;
 17     private double _currentTimeSlide = 0;
 18     private TimeSpan _currentTime = new TimeSpan(0);
 19     private TimeSpan _duration = new TimeSpan(0);
 20     private string PlayPauseIcon { get => _isPlaying ? "pause" : "caret-right"; }
 21     private Action _afterCanPlay;
 22     [Inject]
 23     private DomEventService DomEventService { get; set; }
 24  
 25     protected override void OnInitialized()
 26     {
 27         base.OnInitialized();
 28  
 29         _currentSrc = _musicList[0];
 30         _afterCanPlay = async () =>
 31         {
 32             // do not use _isPlaying, this delegate will be triggered when user clicked play button
 33             if (_canPlayFlag)
 34             {
 35                 try
 36                 {
 37                     await JsInvokeAsync("Music.play", "#audio", true);
 38                     _canPlayFlag = false;
 39                 }
 40                 catch (Exception ex)
 41                 {
 42                 }
 43             }
 44         };
 45     }
 46  
 47     protected override Task OnFirstAfterRenderAsync()
 48     {
 49         // cannot listen to dom events in OnInitialized while render-mode is ServerPrerendered
 50         DomEventService.AddEventListener<JsonElement>("#audio", "timeupdate", OnTimeUpdate);
 51         DomEventService.AddEventListener<JsonElement>("#audio", "canplay", OnCanPlay);
 52         DomEventService.AddEventListener<JsonElement>("#audio", "play", OnPlay);
 53         DomEventService.AddEventListener<JsonElement>("#audio", "pause", OnPause);
 54         DomEventService.AddEventListener<JsonElement>("#audio", "ended", OnEnd);
 55         return base.OnFirstAfterRenderAsync();
 56     }
 57  
 58         #region Audio EventHandlers
 59  
 60         private async void OnPlayPause(MouseEventArgs args)
 61         {
 62             try
 63             {
 64                 await JsInvokeAsync("Music.play", "#audio", !_isPlaying);
 65         }
 66             catch (Exception ex)
 67             {
 68             }
 69         }
 70  
 71     private async void OnCanPlay(JsonElement jsonElement)
 72     {
 73         try
 74         {
 75             string json = await JsInvokeAsync<string>("Music.getMusicTime", "#audio");
 76             jsonElement = JsonDocument.Parse(json).RootElement;
 77             _duration = TimeSpan.FromSeconds(jsonElement.GetProperty("duration").GetDouble());
 78  
 79             _afterCanPlay();
 80         }
 81         catch (Exception)
 82         {
 83         }
 84     }
 85  
 86     private void OnPlay(JsonElement jsonElement)
 87     {
 88         _isPlaying = true;
 89     }
 90  
 91     private async void OnLast(MouseEventArgs args)
 92     {
 93         _canPlayFlag = true;
 94         int index = _musicList.IndexOf(_currentSrc);
 95         index = index == 0 ? _musicList.Count - 1 : index - 1;
 96         _currentSrc = _musicList[index];
 97     }
 98  
 99     private async void OnNext(MouseEventArgs args)
100     {
101         _canPlayFlag = true;
102         int index = _musicList.IndexOf(_currentSrc);
103         index = index == _musicList.Count - 1 ? 0 : index + 1;
104         _currentSrc = _musicList[index];
105     }
106  
107     private void OnPause(JsonElement jsonElement)
108     {
109         _isPlaying = false;
110         StateHasChanged();
111     }
112  
113         private void OnEnd(JsonElement jsonElement)
114     {
115         _isPlaying = false;
116         StateHasChanged();
117  
118         OnNext(new MouseEventArgs());
119     }
120  
121     private async void OnTimeUpdate(JsonElement jsonElement)
122     {
123         // do not use the timestamp from timeupdate event, which is the total time the audio has been working
124         // use the currentTime property from audio element
125         string json = await JsInvokeAsync<string>("Music.getMusicTime", "#audio");
126         jsonElement = JsonDocument.Parse(json).RootElement;
127         _currentTime = TimeSpan.FromSeconds(jsonElement.GetProperty("currentTime").GetDouble());
128         _currentTimeSlide = _currentTime / _duration * 100;
129  
130         StateHasChanged();
131     }
132  
133     #endregion
134  
135     private async void OnSliderChange(OneOf<double, (double, double)> value)
136     {
137         _currentTime = value.AsT0 * _duration / 100;
138         _currentTimeSlide = _currentTime / _duration * 100;
139         await JsInvokeAsync("Music.setMusicTime", "#audio", _currentTime.TotalSeconds);
140     }
141 }

 

建立./Controllers/MusicController.cs

1 public class MusicController : Controller
2 {
3     public IActionResult Index(string name)
4     {
5         return View();
6     }
7 }

建立./Views/Music/Index.cshtml

1 <component type="typeof(MusicPlayer)" render-mode="Server" />

修改./Views/Shared/_Layout.cshtml,新增以下程式碼

1 <li class="nav-item">
2     <a class="nav-link text-dark" asp-area="" asp-controller="Music" asp-action="Index">Music</a>
3 </li>

Build & Run

點選選單欄的Music,效果如下

 

總結

 

WebAssembly並不是JavaScript的替代品,Blazor當然也不是,在開發Blazor元件的過程中,大部分情況下,仍然要通過TypeScript / JavaScript來與DOM進行互動,比如在這個播放器的案例中,還是需要JavaScript來呼叫audio的play,pause等方法。但是在View層面使用播放器這個元件時,我們幾乎可以不再關心JavaScript的開發。這讓前端的開發更類似於開發WPF的XAML介面。事實上,社群裡也有這樣的專案,致力於提供一種類WPF介面開發的元件庫。

 

同時,也希望大家能多多關注國內小夥伴們共同參與開發的AntDesign,作為最熱門的Blazor元件庫之一,在今年的MS Build大會上也獲得了微軟官方的認可。雖然目前元件還有不少BUG和效能問題,但是在社群的努力下,相信它會越來越好,讓我們一起為.NET生態添磚加瓦!

 

社群元件庫:

https://github.com/ant-design-blazor/ant-design-blazor

https://github.com/ArgoZhang/BootstrapBlazor

 

參考:

https://catswhocode.com/html-audio-tag

https://www.w3schools.com/TAGS/tag_audio.asp

&n