前言
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
参考: