開包即食的教程帶你淺嘗最新開源的C# Web引擎Blazor


     在今年年初,恰逢新春佳節臨近的時候。微軟給全球的C#開發者們,着實的送上了一分驚喜。微軟正式開源Blazor,將.NET帶回到瀏覽器。
     這個小驚喜,迅速的在dotnet開發者中間傳開了。2018年3月22日Blazor發布了它的第一次Release Blazor到底是個什么樣的東西呢?我們是否真的可以攜着C#語言進入前端的市場中? 不如現在就跟我一起體驗dotnet blazor吧。

     一、前言

     獲取最新版的dotnet core 並安裝Blazor模板:

  • 安裝 最新的.Net Core(版本需要高於2.1.101);
  • 對於簡單的嘗試來說,VS code 已經足夠。所以筆者並沒有親自安裝Visual Studio;

     使用命令行初始化項目:

dotnet new -i Microsoft.AspNetCore.Blazor.Templates
dotnet new blazor -o BlazorApp1
cd BlazorApp1
dotnet run
  • 如果你需要使用Visual Studio:
    • 安裝最新的Visual Studio 2017;
    • 安裝 ASP.NET Core Blazor Language Services extension;
    • 在Visual Studio中創建新的測試項目;
    • 選擇 File -> New Project -> Web -> ASP.NET Core Web Application;
    • 確定在Target Framework里選擇了 .NET Core and ASP.NET Core 2.0;
    • 選擇 Blazor 模板;

    

 

     二、如何在前端渲染cshtml

     當我們運行起項目之后,就可以看到如下提示:

      

     這個時候我們在瀏覽器里打開監聽的端口 http://localhost:17477,就可以看到我們這個項目的網頁了。

     這個簡單的示例項目帶了3個頁面:

    

     第一個頁面比較簡單,但先別急,讓我們打開瀏覽器工具,先看看頁面在加載頁面過程中都加載了什么:

      

     在初次打開頁面的時候,我們看到的是這樣一個Loading..的頁面,這個頁面的代碼是這樣的:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>BlazorDemo</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>Loading...</app>
    <script src="css/bootstrap/bootstrap-native.min.js"></script>
    <script src="_framework/blazor.js" main="BlazorDemo.dll" entrypoint="BlazorDemo.Program::Main" references="Microsoft.AspNetCore.Blazor.Browser.dll,Microsoft.AspNetCore.Blazor.dll,Microsoft.Extensions.DependencyInjection.Abstractions.dll,Microsoft.Extensions.DependencyInjection.dll,mscorlib.dll,netstandard.dll,System.Core.dll,System.Diagnostics.StackTrace.dll,System.dll,System.Globalization.Extensions.dll,System.Net.Http.dll,System.Runtime.Serialization.Primitives.dll,System.Security.Cryptography.Algorithms.dll" linker-enabled="true"></script></body>
</html>

     可以看到這個頁面加載了兩個js,第一個是bootstrap的,第二個叫做blazor.js。只不過這個js有非常多的參數,有 main、entrypoint和references。看看References里寫的是不是很熟悉? 一看就是.net的dll。

     blazor.js加載了mono.js,mono.js加載了mono.wasm。這個是個什么文件?

      

     wasm代表的就是Web Assembly,簡單地說它就是編譯好的二進制文件,可以由瀏覽器直接運行,源語言可以是C/C++或者任何可以編譯到Web Assembly的文件,而這里我們加載的就是mono編譯好的Web Assembly文件,它被加載之后,相當於瀏覽器中啟動了一個mono 運行環境。

     隨后的兩個js是筆者chrome瀏覽器插入的js,在這里不要被他們干擾了。那么mono 運行時加載完成之后,就需要加載dotnet 的Dll了,首先是入口庫,接着就是需要的引用庫。

      

     好家伙 1.9MB!當所有的Dll被下載完畢之后,這個時候我們的瀏覽器就可以運行我們這個dotnet的網頁了。於是就回到了我們最開始看到的那個應用程序。

     所以,總結一下blazor.js調用mono.js,mono.js加載mono.wsam,然后根據寫在script標簽里的內容繼續的加載dotnet的庫文件。如果瀏覽器不支持wsam,就會嘗試使用asm.js加載mono.asm.js。

 

     三、Blazor的模板究竟是怎樣的?

     我們已經知道,經過前面的步驟,瀏覽器里已經運行了一個.Net 運行時了。而且加載了項目必須的dll。那么這樣一個簡單的程序,它的代碼究竟是怎么樣的呢?

     打開項目代碼,映入眼簾的是一個標准的.net Project:

    

     _ViewImports.cshtml包含了項目一些其他頁面中最常使用的namespace:

@using System.Net.Http
@using Microsoft.AspNetCore.Blazor
@using Microsoft.AspNetCore.Blazor.Components
@using Microsoft.AspNetCore.Blazor.Layouts
@using Microsoft.AspNetCore.Blazor.Routing
@using BlazorDemo
@using BlazorDemo.Shared

     Program.cs是程序的入口點:

using Microsoft.AspNetCore.Blazor.Browser.Rendering; using Microsoft.AspNetCore.Blazor.Browser.Services; using System; namespace BlazorDemo { class Program { static void Main(string[] args) { var serviceProvider = new BrowserServiceProvider(configure => { // Add any custom services here
 }); new BrowserRenderer(serviceProvider).AddComponent<App>("app"); } } }

     在入口點中,我們注冊了一個瀏覽器渲染服務 BrowserRender,讓他渲染App。

     App.cshmtl是這樣的:

<Router AppAssembly=typeof(Program).Assembly />

     這里的Router對應的是Microsoft.AspNetCore.Blazor.Routing.Router。當給它一個AppAssembly時,他就會自動的把當前的Url 和 AppAssembly的其他Pages對應起來。

     所以,當我們在瀏覽器里輸入 /Counter時,他就會加載Pages/Couter.cshtml。

     Shared文件夾里分別是布局文件、導航欄,還有一個我們自定義的控件 SurveyPrompt。

     熟悉Razor引擎的小伙伴們一定很輕車熟路了。那么當我們打開網站時,默認顯示給我們的就是Index,這個時候我們會加載Pages/Index.cshtml。

     Index.cshtml的代碼是這個樣子的:

@page "/" <h1>Hello, world!</h1> Welcome to your new app. <SurveyPrompt Title="How is Blazor working for you?" /> 

     @page 可以告訴Router,當前頁面注冊到 "/"。

     除了顯示hello world以外,我們在這里還看到了剛剛說到的第三方控件:SurveyPrompt,果然不簡單嘛,一個看似簡單的頁面,居然還告訴了我們如何使用自定義控件。

     從聲明上看,我們知道SunveyPrompt是一個控件,並且有一個屬性Title。現在我們打開它的代碼:

<div class="alert alert-survey" role="alert">
    <span class="glyphicon glyphicon-ok-circle" aria-hidden="true"></span>
    <strong>@Title</strong> Please take our <a target="_blank" class="alert-link" href="https://go.microsoft.com/fwlink/?linkid=870381"> brief survey </a> and tell us what you think. </div> @functions { // This is to demonstrate how a parent component can supply parameters public string Title { get; set; } }

     我們可以看到代碼分為兩部分,@functions上面是類似html的東西,下面是類似C#的東西。熟悉React或者Vue的伙伴們恐怕不會對這種混寫感到陌生。這個就是Blazor的語法,Html部分很像使Razor的模板方式,而最后整個頁面都會被編譯成一個類,這個類派生自 Component。如果你編譯過項目,你會在Debug下面的Shared目錄找到一個叫SurveyPrompt.g.cs的東西:

#pragma checksum "/Users/pzhi/SCM/gitHub/zhipu123/BlazorDemo/Shared/SurveyPrompt.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "a2a2ea88635b799343bc6d9647bbb818c8a20c9d"
// <auto-generated/>
#pragma warning disable 1591
namespace BlazorDemo.Shared { #line hidden
    using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Net.Http; using Microsoft.AspNetCore.Blazor; using Microsoft.AspNetCore.Blazor.Components; using Microsoft.AspNetCore.Blazor.Layouts; using Microsoft.AspNetCore.Blazor.Routing; using BlazorDemo; using BlazorDemo.Shared; public class SurveyPrompt : Microsoft.AspNetCore.Blazor.Components.BlazorComponent { #pragma warning disable 1998
        protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder) { base.BuildRenderTree(builder); builder.OpenElement(0, "div"); builder.AddAttribute(1, "class", "alert alert-survey"); builder.AddAttribute(2, "role", "alert"); builder.AddContent(3, "\n "); builder.OpenElement(4, "span"); builder.AddAttribute(5, "class", "glyphicon glyphicon-ok-circle"); builder.AddAttribute(6, "aria-hidden", "true"); builder.CloseElement(); builder.AddContent(7, "\n "); builder.OpenElement(8, "strong"); builder.AddContent(9, Title); builder.CloseElement(); builder.AddContent(10, "\n\n Please take our\n "); builder.OpenElement(11, "a"); builder.AddAttribute(12, "target", "_blank"); builder.AddAttribute(13, "class", "alert-link"); builder.AddAttribute(14, "href", "https://go.microsoft.com/fwlink/?linkid=870381"); builder.AddContent(15, "\n brief survey\n "); builder.CloseElement(); builder.AddContent(16, "\n and tell us what you think.\n"); builder.CloseElement(); builder.AddContent(17, "\n\n"); } #pragma warning restore 1998
        
    // This is to demonstrate how a parent component can supply parameters
    public string Title { get; set; } } } #pragma warning restore 1591

     我們發現@functions里面的內容會作為這個類的成員變量和成員方法,而上面的內容則被編譯到了BuildRenderTree方法中。

     那么到了這里我們大概知道了這個簡單的HomePage都有什么玄機了。我們也大概知道了Blazor的語法,也知道其實我們所有的頁面最終都會是一個Componet。

     那么什么是Componet呢? 在這里並不想用過多的筆墨介紹這個概念。如果你是一個Vue或者React的開發,你應該對這個模塊化開發不陌生。一個Componet,就是滿足一定的功能,有自己的屬性、狀態,可以展示特定數據的元素。

     就如同我們這里的SurveyPrompt,接受一個Title屬性,並且負責把它展示成這樣子:

    

     四、Blazor的刷新和綁定機制初探

     現在我們知道了一個簡單的頁面是如何渲染出來的,那么讓我們打開Counter這個配置來看一看數據是如何交互的。

     我們第二個page是這樣子:

    

     有一個button,當我們點擊的時候,上面的current count 變成了 1:

    

     這一切是怎么發生的呢? 以下是Counter.cshtml的代碼:

@page "/counter" <h1>Counter</h1>

<p>Current count: @currentCount</p>

<button @onclick(IncrementCount)>Click me</button> @functions { int currentCount = 0; void IncrementCount() { currentCount++; } }

     我們看到這個頁面非常簡單,我們定義了一個CurrentCount的Field,然后在IncreaseCount方法里給它加一,一個叫Click me的button標簽里有一個@onclick方法,將IncreaseCount作為參數。

     Counter.cshtml編譯后的代碼是這樣的:

#pragma checksum "/Users/pzhi/SCM/gitHub/zhipu123/BlazorDemo/Pages/Counter.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "05ad2dd449cbc9f09f8b759e1f06e7eb5e9583b4"
// <auto-generated/>
#pragma warning disable 1591
namespace BlazorDemo.Pages { #line hidden
    using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Net.Http; using Microsoft.AspNetCore.Blazor; using Microsoft.AspNetCore.Blazor.Components; using Microsoft.AspNetCore.Blazor.Layouts; using Microsoft.AspNetCore.Blazor.Routing; using BlazorDemo; using BlazorDemo.Shared; [Microsoft.AspNetCore.Blazor.Layouts.LayoutAttribute(typeof(MainLayout))] [Microsoft.AspNetCore.Blazor.Components.RouteAttribute("/counter")] public class Counter : Microsoft.AspNetCore.Blazor.Components.BlazorComponent { #pragma warning disable 1998
        protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder) { base.BuildRenderTree(builder); builder.OpenElement(0, "h1"); builder.AddContent(1, "Counter"); builder.CloseElement(); builder.AddContent(2, "\n\n"); builder.OpenElement(3, "p"); builder.AddContent(4, "Current count: "); builder.AddContent(5, currentCount); builder.CloseElement(); builder.AddContent(6, "\n\n"); builder.OpenElement(7, "button"); builder.AddAttribute(8, onclick(IncrementCount)); builder.AddContent(9, "Click me"); builder.CloseElement(); builder.AddContent(10, "\n\n"); } #pragma warning restore 1998
        
    int currentCount = 0; void IncrementCount() { currentCount++; } } } #pragma warning restore 1591

     我們看到@onclick其實在這里就是執行了一個Component的一個方法onclick,顧名思義,當這個Component被點擊的時候就被調用。我們的IncreaseCount被作為參數傳給了它,可見onclick會在被點擊的時候執行IncreaseCount。

     那么問題來了,當我們執行了IncreaseCount方法時,頁面怎么會知道要不要刷新? 是刷新整個頁面還是刷新所有?

     熟悉WPF的同學可能知道,在WPF中如果我們需要讓一個ViewModel可以被監聽變化,它就需要實現INotifyChanged事件。那么同樣道理,我們的這個IncreaseCount可能也是類似的嗎?

     然而基於編譯后的代碼我們可以發現 CurrentCount作為我們Counter這個類的Field,並沒有任何機會高速Page自己變化了。而且這個Field非常普通,也不是什么WPF中的DP(依賴屬性),所以到目前為止變化是怎么通知的。並沒有一個合理的解釋。后面的時間里我會嘗試閱讀Blazor的代碼搞清楚這件事情。

     第一個問題畫個問號,那么第二個問題呢? 

     打開瀏覽器工具,定位到button,再次點擊button觀察dom的反應。

    

     我們看到 在點擊Button的時候,button上面的<p>標簽閃動了,說明它被刷新了,而其他標簽並沒有。所以局部刷新的功能是有的,效率問題不用擔心了。

     編輯Click me,把他的內容變成 "點擊我",再次點擊按鈕,我們看到還是只有p變,而且button也沒有變回原來的內容:

    

     所以我們知道,這個局部刷新不是簡單的拿Dom作比較,肯定是有Virtual Dom的機制在里面。

 

     五、星星之火,可燎原?

     在簡單的嘗試了Blazor之后,還是很興奮的。可以看到Blazor是一個初具規模的產品,我們C#開發可以用Blazor在今后寫前端渲染的網頁了!

     我很期望這樣一個產品能夠持續的演進下去。

     就目前版本看(0.1.0)Blazor尚不能應用到產品中。主要還是有以下的原因:

  •  打包大小太大,1.8M的大小對於網站簡直是致命的;
  •  產品還不成熟,現在Component還只能支持簡單的事件,筆者測試的時候只有onclick,onchange;
  •  兼容性差,使用了WebAssembly,就注定了兩年前的瀏覽器必定不能支持;

     當然我們還是不能否認,Blazor為如何讓更多語言進入前端世界打開了一扇新的大門。也許未來JavaScript將不僅僅是前端唯一可以使用的利器。我們會看到C/C++、Python、Java寫的前端渲染頁面也不一定呢。

     當然在后端語言打入前端世界的道路上,WebAssembly也未必是唯一的方法,比如Scala.js就完全使用了js重寫了Scala的庫函數,類似的還有Kotlin.js。可以看到雖然JavaScript已經非常Fancy了,但是后端程序員們進軍前端的熱情可謂從未停歇過啊。

 

轉載鏈接:https://www.cnblogs.com/Gerryz/p/get-start-with-dotnet-blazor.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM