【Blazor】在ASP.NET Core中使用Blazor組件 – 創建一個音樂播放器
- 2020 年 7 月 7 日
- 筆記
前言
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生態添磚加瓦!
參考:
//catswhocode.com/html-audio-tag
//www.w3schools.com/TAGS/tag_audio.asp
//github.com/ant-design-blazor/ant-design-blazor