4

Blazor WASM 实现人民币大写转换器

 3 years ago
source link: https://edi.wang/post/2021/3/1/blazor-wasm-rmb-capitalize
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

.NET 5 正式发布已经有一段时间了,其中 Blazor 技术是该版本的亮点之一。作为微软技术的被坑者,年少的我曾经以为 SilverLight 能血虐 Flash,Zune 能团灭 iPod,WP 能吊打 iPhone,UWP 能统一全平台…… 可是后…… 最终步入大龄程序员的我发现,只有陪伴了我将近 20 年的 ASP.NET 还没有完蛋。于是我这两天花了点时间,尝试将我的一个 UWP 小工具用 Blazor 重写,分享给大家。

无法抢救的 UWP


img-cf224fc4-e8b3-434a-8364-f8144703ad2b.png

人民币大写转换器” 是我年少无知时开发的小工具之一,它的主要功能有:

- 将数字金额转化为大写中文

- 复制结果

- 使用中文语音朗读结果

- 显示参照表

可惜 UWP 不论是充满 Bug 的 SDK,Runtime,还是微软的龟速更新与混乱的规划,都已经无可救药了,是时候给应用找个新家了。

Blazor


Blazor 是 .NET Core 时代微软推出的用于 Web 应用开发的新框架,它可以运行在服务器端,也可使用 WASM 运行在客户端,即浏览器中。

对我来说,这个技术最吸引人的,就是 WASM。像我这种已经30多岁,学不了新东西的 .NET 程序员,根本搞不定 Angular、Vue、React 这些花里胡哨的框架,而 Blazor WASM 是把 .NET 运行时搬到了浏览器端,和 SilverLight 类似,但这次是以WASM标准的形式运行,不需要安装插件,并且也能跨平台。

于是我可以继续使用熟悉的 .NET 和 C# 开发 SPA Web 应用。更重要的是,既然是原汁原味的 .NET,就可以很方便的重用以前的代码,以及现成的成千上万个 NuGet 包,而不用像一个新发明的框架那样从0开始积累生态。

我 996 了 2 小时,成功将“人民币大写转换器”重写到 Blazor WASM,效果如下:

img-61f3aa6b-3899-4399-8860-2525b2855e32.png

Demo | 源代码

由于篇幅关系,本文不叙述重写的每处细节,只参数关键点。其他细节大家可到 GitHub 阅读源代码了解。

创建 Blazor WASM 工程


我们可以使用 Visual Studio 2019 创建 Blazor WASM 工程。

img-0dd03f4c-a240-4942-8636-4f11ad979340.png

选择 Blazor WebAssembly App 就可以了

img-537af2b2-7ba0-4555-a725-29f2634e7341.png


一个 Blazor WASM 项目的典型结构如上图。Program.cs 包含应用如何启动与承载的逻辑。

wwwroot 中的文件为纯 HTML/CSS/JS 文件,不包含.NET的逻辑。其中 index.html 为承载应用的默认页面,和 Angular 等 SPA 框架非常类似,它将会把应用页面加载到 <div id="app"> 中。

MainLayout.razor 是整个应用的布局页面,如果你有多个页面和视图,那么通常这里会放 Header,Footer 等内容。

Index.razor 为应用的默认主页。我这个应用只有一个页面,所以一切逻辑都在这里实现就可以了。

可重用的代码


人民币大写的转换类与框架和平台无关,因此完全可以直接复制到Blazor工程里用,即 RMBConverter.cs。

UWP 应用的视图通常采用 MVVM 模式开发,这些逻辑可以很方便的迁移到 Blazor。

Index.razor

就像写 MVC 的 cshtml 一样,使用熟悉的 Razor 语法,就能绑定数据和事件。

对于 input,简单的双向数据绑定可以直接用 @bind="属性" 实现。但我这个应用里要求用户一边输入金额一边进行实时计算,所以只能写成事件绑定。

<div>
    <h3> @Result </h3>
</div>
<div>
    <div>
        <input type="text" @bind-value="InputAmount" @bind-value:event="oninput" />
    </div>
    <div>
        <button @onclick="CopyResult">复制</button>
        <button @onclick="ReadAloud">朗读</button>
        <button @onclick="Clear">清除</button>
    </div>
</div>

对于有参数的事件处理函数,要注意它和正常 C# 写事件一样,是个 Lambda 表达式,如果放在循环里的话要注意变量的值是在循环里被修改。

下面的代码必须使用 var num = i 来存储 i 的值,如果直接使用 KeyPadClicked(i),那么 i 一定永远等于1。

<div class="row">
    @for (int i = 1; i <= 9; i++)
    {
        var num = i;
        <div class="col-4">
           <button class="btn btn-light key" @onclick="() => KeyPadClicked(num.ToString())">@i</button>
        </div>
    }
</div>
<div class="row">
    <div class="col-8">
        <button class="btn btn-light key" @onclick='() => KeyPadClicked("0")'>0</button>
    </div>
    <div class="col">
        <button class="btn btn-light key" @onclick='() => KeyPadClicked(".")'>.</button>
    </div>
</div>

和 XAML 的 MVVM 以及 Angular 稍有不同的是,处理逻辑不是在 code behind 文件里写的,而是可以在 razor 页面本身写。你也可以自己新建 Index.razor.cs 作为部分类来达到HTML与C#相分离的目的。

@code {
    private string _inputAmount;
    public string InputAmount
    {
        get => _inputAmount;
        set
        {
            _inputAmount = value;

            // 验证和处理逻辑...
            Result = string.IsNullOrWhiteSpace(_inputAmount) ?
                    string.Empty :
                    RMBConverter.GetCapitalizedRmb(InputAmount);
        }
    }

    public string Result { get; set; }

    private async Task CopyResult()
    {
        // ...
    }

    private async Task ReadAloud()
    {
        // ...
    }

    private void Clear()
    {
        InputAmount = string.Empty;
    }

    private void KeyPadClicked(string value)
    {
        InputAmount += value switch
        {
            "0" when InputAmount != "0" => 0,
            "." when !InputAmount.Contains(".") => ".",
            _ => value
        };
    }
}

需要重新实现的功能


在 UWP 中,复制可以调用 Windows 的 Clipboard API 来完成。但是在浏览器端,没有 Windows 的 API,Blazor 也没有封装剪切板 API,因此我们只能借用 JS 来完成。

index.html

window.clipboardCopy = {
    copyText: function (text) {
        navigator.clipboard.writeText(text).then(function () {
            console.log(text);
        })
            .catch(function (error) {
                alert(error);
            });
    }
};

Index.razor

使用依赖注入,引入 IJSRuntime 的实例。这是 Blazor 用于和 JavaScript 交互的接口。

@inject IJSRuntime JavaScriptRuntime

然后就可以调用 JS 进行复制

private async Task CopyResult()
{
    if (!string.IsNullOrWhiteSpace(Result))
    {
        await JavaScriptRuntime.InvokeVoidAsync("clipboardCopy.copyText", Result);
    }
}

类似的,在 UWP 里,朗读使用的是 Windows 的 SpeechSynthesizer API。浏览器端也有个类似的 SpeechSynthesisUtterance

index.html

window.readAloud = {
    readText: function (text) {
        let utterance = new SpeechSynthesisUtterance(text);
        utterance.lang = 'zh-CN';
        speechSynthesis.speak(utterance);
    }
}

Index.razor

private async Task ReadAloud()
{
    if (!string.IsNullOrWhiteSpace(Result))
    {
        await JavaScriptRuntime.InvokeVoidAsync("readAloud.readText", Result);
    }
}

目前 Blazor WASM 还没有本地应用的官方支持,必须打开浏览器才能使用,现有的版本只能使用PWA完成一部分本地应用化操作。但在今年即将发布的 .NET 6 版本中,Blazor 会迎来官方最纯正的本地应用支持。只要不出自 SilverLight、Zune、WP、WinRT、UWP 团队之手,就不会被坑!

现存的问题


Blazor WASM 虽然看着香,但目前有一些痛点还有待解决。

首先,框架本身的体积依然较大,由于众所周知而不可描述的原因,如果服务器部署在海外,那么我国网络加载 Blazor 应用会比较慢。

另外,不是所有版本的浏览器都可以跑 WASM,尤其是手机端。Blazor 的兼容性相比 Angular,Vue,React 等,还有些差距。


使用 Blazor WASM 开发 Web 应用能够让 .NET 程序员充分利用既有的知识和技能快速上手,结合 Web 的强大生态 与 .NET 的高效生产力,成就不凡。而 UWP 只能哭晕在厕所也没人听见……


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK