【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     Music: 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

 

posted @ 2020-07-07 13:16  苹果没有熟  阅读(1399)  评论(0编辑  收藏  举报