WebAssembly和Blazor:解决了一个存在十年的老问题
本文要點
WebAssembly 是一種新的客戶端技術,可以在所有現代瀏覽器(包括移動瀏覽器)中實現近乎原生的性能,而且不需要插件。
許多語言,包括 C、C#、Go 和 Rust,都可以編譯成面向基于棧的 WebAssembly 虛擬機的代碼。
.NET 代碼可以在任何地方運行,包括瀏覽器內部。
Blazor 是一個客戶端庫,它在 WebAssembly 上使用.NET 來支持借助 Razor 模板使用 C# 編寫的單頁應用程序。
Blazor 支持代碼重用和將遺留代碼移植到現代 Web 應用程序的能力。
在 2019 年 4 月中旬,微軟悄悄地推出了一個年輕的框架,從“一切皆有可能”的實驗階段過渡到“我們致力于實現這一目標”的預覽版。這個框架名為Blazor,因為它在瀏覽器中運行,并利用了一個名為 Razor 的模板系統或“視圖引擎”,促成了這個.NET 開發人員幾乎放棄了的場景。它不僅允許開發人員使用 C# 構建客戶端代碼(不需要 JavaScript),還允許開發人員在沒有插件的情況下在瀏覽器中運行現有的.NET 標準 DLL。
Blazor 有兩種托管模式。本文主要關注客戶端版本。你可以閱讀“Blazor 服務器端托管模型”了解更多關于服務器端版本的信息。
Silverlight 的希望
在任何地方運行.NET 的夢想始于 2006 年,當時有一個名為“Windows Presentation Foundation/Everywhere(WPF/E)”的應用程序框架以 Silverlight 的形式向公眾發布。第一個版本支持通過 WPF 引入的聲明性用戶界面,即可擴展應用程序標記語言(Extensible Application Markup Language,簡稱 XAML)。該平臺提供了對 UI 元素的細粒度控制,并提供了自己的文檔對象模型(DOM),可以通過 JavaScript 訪問。
當 Silverlight 2 在 2008 年發布時,它通過一個作為瀏覽器插件運行的公共語言運行時(CLR)實現.NET 的完全支持,從而加快了采用速度。開發人員可以使用任何.NET 語言來構建 Web 應用程序,利用成熟的數據綁定模式,如 Model-View-ViewMode(MVVM),并使用 REST 或 Windows Communication Foundation(WCF)客戶端與 Web API 通信。看起來,.NET 開發人員可以擺脫 JavaScript 的束縛,不用再擔心跨瀏覽器測試,而是專注于一個具有公共代碼庫的平臺來交付他們的應用程序。
Silverlight 開發者不知道的是,2007 年對于這個平臺來說是艱難的一年。兩個看似不相干的事件發生了,最終導致了它的滅亡。首先,Web 超文本應用技術工作組(WHATWG)和萬維網聯盟(W3C)之間開始著手合作編寫將于 2008 年發布的 HTML5 規范初稿。
第二,2007 年 6 月 29 日,蘋果發布了 iPhone。
每隔一段時間,我們就會有一件革命性的產品橫空出世,并徹底改變一切。
——史蒂夫?喬布斯
比賽開始了。手機幾乎是在一夜之間從帶有聯系人列表的翻蓋手機發展到帶有游戲和內置網絡瀏覽器的便攜式電腦。在很短的一段時間內,Silverlight 的未來似乎充滿了希望。微軟對 iPhone 的回應是 Windows Phone 7,支持以 Silverlight 作為開發平臺。Chrome 支持即將到來。如果微軟能夠找到一種將 Silverlight 應用到 iPhone 和 Android 手機上的方法,那么“一次編寫,到處運行”的圣杯將最終被發現。
只是,它沒有找到。
出于許多原因,包括運行“瀏覽器中的虛擬機”的安全性考慮,以及潛在的電池消耗,通向瀏覽器插件的大門砰地關上了,特別是在移動設備上。業界開始期待 HTML5 在打造移動體驗方面的前景。微軟改變了自己的關注點,到 2011 年 Silverlight 5 發布時,大多數開發人員已經看到了不祥之兆:不會再有新版本了。
HTML5 和 JavaScript 繼續贏得 Web 開發人員的青睞。jQuery 等工具對 DOM 進行了標準化,使構建多瀏覽器應用程序變得更加容易,同時,瀏覽器引擎開始采用通用的 DOM 標準,使“一次構建,到處運行”變得更加容易。Angular、React 和 Vue.js 等前端框架的爆炸式增長使單頁應用程序(SPA)成為主流,并鞏固了 JavaScript 作為瀏覽器操作系統首選語言的地位。
JavaScript 即平臺
2013 年 3 月,asm.js正式推出。其文檔把它描述為JavaScript 的一個嚴格子集,可以用作編譯器的一種低級、高效的目標語言。該規范本質上定義了一組 JavaScript 約定,這些約定使通過提前編譯優化代碼成為可能,并提供了強類型(JavaScript 本身是一種動態語言)和基于堆的內存模型。
Asm.js 的推出使將 C/C++ 代碼編譯成 JavaScript 成為可能,從而開啟了一個新的可能性領域。對約定的限制使得“支持 asm.js”的引擎可以有效地將 JavaScript 編譯成高性能的本地代碼。為了更好地理解這是如何實現的,請考慮下面的 C 代碼片段:
該代碼有效地掃描一個字符串,尋找標記其結束的測試字符或零字節,并計算偏移量。C++ 已經可以使用一個名為Clang的工具編譯成字節碼,該工具與LLVM 工具鏈兼容。LLVM 是一組支持快速跨平臺編譯代碼的技術。一個名為Emscripten的項目利用該工具鏈來生成 asm.js。
使用 Emscripten 編譯 C++ 代碼可以生成幾十行高度優化的 JavaScript。以下代碼經過簡化,用于說明生成的內容:
function find(buf, test) {
buf = buf|0;
var cur = buf|0;
var result = -1|0;
while (1) {
var check = HEAP8[cur>>0]|0;
var foundZero = (check) === (0);
if (foundZero) {
break;
}
var foundTest = (check) === (test|0);
if (foundTest) {
result = (cur - buf)|0;
break;
}
}
return result|0;
}
生成的 JavaScript 與所有瀏覽器兼容并且運行良好。帶零操作的異或(|0)可以很容易地將任何數字轉換為帶符號整數。在較老的瀏覽器中,這可以確保數字沒有小數部分。在現代瀏覽器中,該約定可以提前通知編譯器使用 32 位整數(使數學運算更快),而不是默認的 64 位浮點值。0 右移(>>0)可以防止溢出,它還聲明了一個“索引”整數類型,該類型在 HEAP8 上迭代,而 HEAP8 是一個可以在 asm.js 中使用的類型化的字節緩沖區。
在 asm.js 中沒有定義 for 循環。所有內容都被轉換為 while(1) 循環。這使得應用編譯器優化變得更容易。這些優化非常有效,以至于一個團隊能夠將 Unreal 4 引擎移植到 Web 瀏覽器中,以近乎原生的性能直接運行 3D 第一人稱視角游戲。
WebAssembly:新希望
時間很快來到 2017 年,WebAssembly發布了,這是一種基于棧的虛擬機的二進制指令格式。WebAssembly 提供了一個可移植的編譯目標(簡稱 Wasm),與 asm.js 相比,它有幾個優點:
作為字節碼格式,不需要解析腳本和預編譯來進行優化。代碼可以直接翻譯成本機指令。與 asm.js 相比,加載和開始執行代碼的啟動時間要快幾個數量級。
字節碼格式是一種更緊湊的代碼交付方式。
Wasm 實現了自己的指令集,因此不受 JavaScript 語言的限制。
任何編譯成 asm.js 的代碼都可以把 WebAssembly 作為目標。對于前面的示例,編譯器標志的一個簡單修改就會生成一個擴展名為.wasm 的文件。該文件只有 116 字節長。盡管該文件包含字節碼,但也存在代碼的標準化文本表示形式,名為WebAssembly 文本格式。這是 WebAssembly 中 find 模塊的文本表示:
(module
(type $t0 (func (param i32 i32) (result i32)))
(import "env" "memory" (memory $env.memory 256 256))
(func $a (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
(local $l0 i32) (local $l1 i32) (local $l2 i32) (local $l3 i32)
get_local $p0
set_local $l0
loop $L0
get_local $l0
i32.load8_s
tee_local $l2
i32.eqz
set_local $l1
get_local $l0
i32.const 1
i32.add
set_local $l3
get_local $l1
i32.const 1
i32.xor
get_local $p1
i32.const 24
i32.shl
i32.const 24
i32.shr_s
get_local $l2
i32.ne
i32.and
if $I1
get_local $l3
set_local $l0
br $L0
end
end
i32.const -1
get_local $l0
get_local $p0
i32.sub
get_local $l1
select)
代碼大小進行了優化,因此函數被重命名為了 a。
WebAssembly 現在是 1.x 穩定版,支持所有的現代瀏覽器,包括手機。若干語言都以 Wasm 作為有效的編譯目標。你可以使用 C、C++、Go、Rust、TypeScript 和許多其他語言來構建 WebAssembly 程序。它已經應用于計算機視覺、音頻混合、視頻編解碼器支持、數字信號處理、醫學成像、物理模擬、加密、壓縮等解決方案中。
但是 C# 呢?
在 WebAssembly 推出之后,將. NET 框架的工作版本(包括它的公共語言運行時)移植到 WebAssembly 上運行的工作就立即開始了。
這一努力取得了成功。
瀏覽器和 Razor 視圖引擎
2017 年底,微軟軟件工程師 Steve Sanderson 在他的個人博客上宣布了 Blazor 的消息。當時,它“只是一個實驗”,并不是正式產品。它始于這樣一個問題:“我們如何讓.NET 在 WebAssembly 中運行?”第一個答案是比較老的簡化版.NET 運行時,他能夠在幾個小時內將其編譯成 Wasm 二進制文件。.NET 本身在瀏覽器中并不是非常有用:你需要一個 UI 和某種與用戶交互的方式。Razor 文件將標記和 C# 結合起來創建 Web 模板,基于這項扎實的工作,Blazor 添加了大量的服務,從數據綁定和依賴注入到可重用組件、布局,以及調用 JavaScript 和從 JavaScript 調用。所有這些服務結合使得使用.NET 和 C# 構建單頁面應用程序(SPA)成為可能。
圖 1 默認 Blazor 應用程序為什么會有人在意呢?開發人員最初對 Blazor 的反應非常積極,這主要是因為:
它允許開發人員使用他們已經熟悉的語言(C#)和框架(.NET)來構建以前深深扎根在 JavaScript 中的客戶端應用程序。
它可以在所有現代瀏覽器中運行,包括移動瀏覽器,而且不需要插件。
它使開發人員能夠進入.NET 生態系統并“按原樣”使用現有的庫。例如,如果你正在構建一個使用 Markdown 的博客引擎,那么你可以為現有的 Markdown 引擎安裝 NuGet 包,并將 Markdown 直接轉換為 HTML,以便在瀏覽器中預覽。
.NET 的性能會隨著時間的推移不斷提高,因此在瀏覽器的 Wasm 上運行已經足夠了。
Blazor 是一個真正的單頁應用程序,它從一組靜態資產運行,可以使用Azure Storage 靜態網站等服務以非常低的成本托管這些靜態資產。
現在,你已經了解了 Blazor 背后的歷史和動機,讓我們來研究一些技術細節。
本文中的所有代碼示例都可以在Blazor WebAssembly?GitHub 存儲庫中找到。
安裝 Blazor 并開始使用所需要了解的所有內容都可以在Blazor 入門這篇文章中找到。在安裝了 Blazor 之后,你可以選擇只創建客戶端或帶有ASP.NET Core后端的客戶端。對于現有的基于 MVC 的服務器端項目來說,該項目看起來非常熟悉。但是,生成的 DLL 直接加載到瀏覽器中,并由.NET 的 WebAssembly 版本運行。
圖 2 Blazor 應用中的網絡活動Mono.js?JavaScript 動態加載 mono.wasm 并開始在瀏覽器中運行.NET。其余的加載是組成應用程序的實際 DLL 文件。
瀏覽器中的 C#(帶依賴注入)
默認模板包含一個獲取模擬天氣信息的頁面。這是 Razor 視圖,完全由 Wasm 在客戶端上渲染。
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
(forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<tableclass="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
(var forecast in forecasts)
{
<tr>
<td>
<td>
<td>
<td>
</tr>
}
</tbody>
</table>
}
在模板的上部,一組指令決定了頁面的路徑,聲明了一個 using 指令,并使用依賴注入來獲取.NETFramework 的 HttpClient 副本,該副本可以在瀏覽器中使用。
@page "/fetchdata"
@using GetStarted.Shared
@inject HttpClient Http
最后,頁面上的一小段代碼嵌入到 @functions 塊中。這里需要注意的是,代碼完全是 C# 的。你可以使用熟悉的 HttpClient 執行網絡操作,并且支持 async/wait。
WeatherForecast[] forecasts;
protected override async Task OnInitAsync()
{
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
視圖模板渲染一個頁面,但是控件呢?
可重用組件
Blazor 基于分層組件的可組合 UI。天氣預報組件與其他組件的唯一區別是提供路由的頁面指令。下面是一個名為 LabelSlider.razor 的組件的模板,使用一個顯示當前值的 span 擴展內置的 HTML Input Range。
<inputtype="range"min="@Min"max="@Max"bind-value-oninput="@CurrentValue"/>
<span>@CurrentValue</span>
綁定語法的格式為 bind-{property}-{event}。事件是可選的,觸發事件時綁定會更新。如果沒有這個,滑塊只會在用戶停止移動滾動條時更新 span。通過連接到 oninput,該值將隨著滑塊的移動而刷新。
關聯的代碼暴露了參數,這些參數允許父組件設置最小和最大范圍值,并將數據綁定到當前值。Action 屬性暴露一個與 CurrentValue 關聯的事件,并按照約定將其命名為 CurrentValueChanged,以方便雙向數據綁定(父組件可以“偵聽”更改事件并相應更新綁定值)。
[Parameter]
int ; set; }
[Parameter]
int ; set; }
private int _currentValue;
[Parameter]
int
{
get ;
set
{
if
{
_currentValue ;
CurrentValueChanged?.Invoke(value);
}
}
}
[Parameter]
Action<int> CurrentValueChanged ; set; }
注意,沒有使用 Parameter 屬性標記的屬性只對組件可見。組件重用非常簡單,只需把組件名置入標簽并提供必要的參數,用法如下:
<LabelSlider Min="0" Max="99" bind-CurrentValue="@currentCount"/>
在本例中,一個與父組件上的 currentCount 屬性之間的雙向綁定建立起來了。
使用現有的庫
Blazor 的一個非常強大的優點是能夠“按原樣”集成現有的類庫。例如,考慮一個使用Markdown的博客引擎,它能夠在瀏覽器中預覽生成的 HTML。在 Blazor 中構建它就像安裝一個 NuGet 包一樣簡單,在本例中是開源的MarkDig處理器。然后,可以直接調用庫:
var html = Markdig.Markdown.ToHtml(SourceText);
NuGet DLL 像其他項目引用一樣被導入瀏覽器,并且可以從客戶端應用程序調用。
圖 3 瀏覽器中的 Markdown 轉換調用 JavaScript/ 從 JavaScript 調用
Blazor 提供的一個重要服務是能夠從.NET 調用 JavaScript,反之亦然。你希望從 Blazor 調用的任何 JavaScript 方法都必須能夠從全局 window 對象訪問,調用方法如下:
window.jsAlert = msg => alert(msg);
該互操作功能的使用方式如下:
await JsRuntime.InvokeAsync<object>("jsAlert", "Wow!");
InvokeAsync 方法支持傳遞和返回值,這些值將自動在 JavaScript 與.NET 之間轉換并由 Blazor 運行時編組。使用 JsInvokable 屬性來暴露 C# 方法,以便可以從 JavaScript 調用它。下面是一個例子,它封裝了 Markdown 轉換調用:
public static class Markdown
{
[]
public static string Convert(string src)
{
return Markdig.Markdown.ToHtml(src);
}
}
從 JavaScript 調用 DotNet.invoke 方法并傳遞程序集名稱、暴露的方法名稱和任何參數。
圖 4 從 JavaScript 調用.NET這使得擴展遺留應用程序和使用現有的 JavaScript 庫成為可能。你甚至可以從 Blazor 應用程序調用其他 WebAssembly 模塊。
Blazor 在發展
微軟將 Blazor 移出了實驗階段,進入了官方預覽版。使用組件模型進行服務器端渲染的 Blazor 版本將與.NET?Core 3 的最終版本一起發布(請參閱.NET Core 路線圖),客戶端版本將在隨后不久發布。還有工作要完成:調試體驗極其有限,必須改進;有機會通過提前編譯生成本機 Wasm 來優化代碼性能;在將未使用的代碼庫發送到瀏覽器之前,需要從庫中刪除未使用的代碼,從而降低總體大小(這個過程稱為樹抖動)。對 WebAsssembly 的興趣和采用與日俱增,借助 Blazor,編寫可以在任何地方運行的 C# 和.NET 代碼的夢想終于實現了。
關于作者
Jeremy Likness是微軟 Azure 的云推廣專員。Jeremy 在 1982 年編寫了他的第一個程序,并且已經開發企業應用程序 25 年了。他是四本科技書籍的作者,曾做過 8 年微軟 MVP,是國際性的主題演講家。Jeremy 的飲食以植物為主,在大部分空閑時間里,他都在太平洋西北部他的家附近跑步、徒步旅行和露營。關注Jeremy 的博客。
原文地址:https://www.infoq.cn/article/GERYOPIV4B8OSD3_kSmB
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?
總結
以上是生活随笔為你收集整理的WebAssembly和Blazor:解决了一个存在十年的老问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [开源] FreeSql.Tools R
- 下一篇: NopCommerce 4.2的安装与运