【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