来自后端的突袭? --开包即食的教程带你浅尝最新开源的C# Web引擎 Blazor
在今年年初, 恰逢新春佳節(jié)臨近的時(shí)候. 微軟給全球的C#開發(fā)者們, 著實(shí)的送上了一分驚喜. 微軟正式開源Blazor ,將.NET帶回到瀏覽器.
這個(gè)小驚喜, 迅速的在dotnet開發(fā)者中間傳開了. 而就在昨天(2018年3月22日) Blazor發(fā)布了它的第一次Release. Blazor到底是個(gè)什么樣的東西呢?我們是否真的可以攜著C#語言進(jìn)入前端的市場中? 不如現(xiàn)在就跟我一起體驗(yàn)dotnet blazor吧.
首先
獲取最新版的dotnet core 并安裝Blazor模板:
安裝 最新的.Net Core(版本需要高于2.1.101)
對于簡單的嘗試來說, VS code 已經(jīng)足夠. 所以筆者并沒有親自安裝Visual Studio.
使用命令行初始化項(xiàng)目:
dotnet new -i Microsoft.AspNetCore.Blazor.Templatesdotnet new blazor -o BlazorApp1cd BlazorApp1dotnet run?
如果你需要使用Visual Studio,
安裝最新的Visual Studio 2017.
安裝 ASP.NET Core Blazor Language Services extension
在Visual Studio中創(chuàng)建新的測試項(xiàng)目:
選擇 File -> New Project -> Web -> ASP.NET Core Web Application
確定在Target Framework里選擇了 .NET Core and ASP.NET Core 2.0.
選擇 Blazor 模板
敵后根據(jù)地? 如何在前端渲染cshtml
當(dāng)我們運(yùn)行起項(xiàng)目之后, 就可以看到如下提示
個(gè)時(shí)候我們在瀏覽器里打開監(jiān)聽的端口 http://localhost:17477. 就可以看到我們這個(gè)項(xiàng)目的網(wǎng)頁了.
這個(gè)簡單的示例項(xiàng)目帶了3個(gè)頁面
?第一個(gè)頁面比較簡單, 但先別急,讓我們打開瀏覽器工具. 先看看頁面在加載頁面過程中都加載了什么
在初次打開頁面的時(shí)候, 我們看到的是這樣一個(gè)Loading..的頁面.? 這個(gè)頁面的代碼是這樣的.
blazor.js 加載了mono.js, mono.js 加載了mono.wasm. 這個(gè)是個(gè)什么文件?
asm代表的就是Web Assembly, 簡單地說它就是編譯好的二進(jìn)制文件, 可以由瀏覽器直接運(yùn)行, 源語言可以是C/C++或者任何可以編譯到Web Assembly的文件, 而這里我們加載的就是mono 編譯好的Web Assembly文件, 它被加載之后, 相當(dāng)于瀏覽器中啟動(dòng)了一個(gè)mono 運(yùn)行環(huán)境.
隨后的兩個(gè)js 是筆者chrome瀏覽器插入的js, 在這里不要被他們干擾了. 那么mono 運(yùn)行時(shí)加載完成之后. 就需要加載dotnet 的Dll了, 首先是入口庫, 接著就是需要的引用庫
好家伙 1.9MB. 當(dāng)所有的Dll被下載完畢之后, 這個(gè)時(shí)候我們的瀏覽器就可以運(yùn)行我們這個(gè)dotnet的網(wǎng)頁了. 于是就回到了我們最開始看到的那個(gè)應(yīng)用程序.
所以 總結(jié)一下 blazor.js 調(diào)用mono.js, mono.js加載mono.wsam, 然后根據(jù)寫在script標(biāo)簽里的內(nèi)容繼續(xù)的加載dotnet的庫文件. 如果瀏覽器不支持wsam, 就會(huì)嘗試使用asm.js加載mono.asm.js
柳暗花明又一村,Blazor的模板究竟是怎樣的.
我們已經(jīng)知道,經(jīng)過前面的步驟,瀏覽器里已經(jīng)運(yùn)行了一個(gè).Net 運(yùn)行時(shí)了. 而且加載了項(xiàng)目必須的dll. 那么這樣一個(gè)簡單的程序,它的代碼究竟是怎么樣的呢??
打開項(xiàng)目代碼,映入眼簾的是一個(gè)標(biāo)準(zhǔn)的.net Project
_ViewImports.cshtml包含了項(xiàng)目一些其他頁面中最常使用的namespace
5 7 | @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是程序的入口點(diǎn)
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");
? ? ? ? }
? ? }
}
在入口點(diǎn)中, 我們注冊了一個(gè)瀏覽器渲染服務(wù) BrowserRender,讓他渲染App
App.cshmtl是這樣的
<Router AppAssembly=typeof(Program).Assembly />這里的Router對應(yīng)的是Microsoft.AspNetCore.Blazor.Routing.Router. 當(dāng)給它一個(gè)AppAssembly時(shí), 他就會(huì)自動(dòng)的把當(dāng)前的Url 和 AppAssembly的其他Pages對應(yīng)起來.
所以 當(dāng)我們在瀏覽器里輸入 /Counter時(shí),他就會(huì)加載Pages/Couter.cshtml.
Shared文件夾里分別是布局文件,導(dǎo)航欄, 還有一個(gè)我們自定義的控件 SurveyPrompt.?
熟悉Razor引擎的小伙伴們一定很輕車熟路了. 那么當(dāng)我們打開網(wǎng)站時(shí), 默認(rèn)顯示給我們的 就是Index, 這個(gè)時(shí)候我們會(huì)加載Pages/Index.cshtml
Index.cshtml的代碼是這個(gè)樣子的
@page "/"<h1>Hello, world!</h1>Welcome to your new app. <SurveyPrompt Title="How is Blazor working for you?" />@page 可以告訴Router, 當(dāng)前頁面注冊到 "/"
除了顯示hello world以外, 我們在這里還看到了剛剛說到的第三方控件. SurveyPrompt. 果然不簡單嘛, 一個(gè)看似簡單的頁面, 居然還告訴了我們?nèi)绾问褂米远x控件.
從聲明上看, 我們知道 SunveyPrompt是一個(gè)控件,并且有一個(gè)屬性Title. 現(xiàn)在我們打開它的代碼
<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的伙伴們恐怕不會(huì)對這種混寫感到陌生. 這個(gè)就是Blazor的語法. Html部分很像使Razor的模板方式. 而最后整個(gè)頁面都會(huì)被編譯成一個(gè)類, 這個(gè)類派生自 Component. 如果你編譯過項(xiàng)目, 你會(huì)在Debug下面的Shared目錄找到一個(gè)叫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
我們發(fā)現(xiàn)@functions里面的內(nèi)容 會(huì)作為這個(gè)類的成員變量和 成員方法, 而上面的內(nèi)容則被編譯到了BuildRenderTree方法中.
那么到了這里我們大概知道了這個(gè)簡單的HomePage都有什么玄機(jī)了. 我們也大概知道了Blazor的語法, 也知道其實(shí)我們所有的頁面最終都會(huì)是一個(gè)Componet.
那么什么是Componet呢? 在這里并不想過多的去筆墨介紹這個(gè)概念. 如果你是一個(gè)Vue或者React的開發(fā), 你應(yīng)該對這個(gè)模塊化開發(fā)不陌生. 一個(gè)Componet, 就是滿足一定的功能, 有自己的屬性, 狀態(tài). 可以展示特定數(shù)據(jù)的元素.
就如同我們這里的SurveyPrompt, 接受一個(gè)Title屬性,并且負(fù)責(zé)把他展示成這樣子
?數(shù)據(jù)驅(qū)動(dòng)? Blazor的刷新和綁定機(jī)制初探
現(xiàn)在我們知道了一個(gè)簡單的頁面是如何渲染出來的. 那么讓我們打開Counter這個(gè)配置來看一看. 數(shù)據(jù)是如何交互的
我們第二個(gè)page張這樣子
有一個(gè)button, 大聲的叫我們點(diǎn)它. 當(dāng)我們點(diǎn)擊的時(shí)候. 上面的current count 變成了 1
?
這一切是怎么發(fā)生的呢? 以下是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++;
? ? }
}
?我們看到 這個(gè)頁面非常簡單, 我們定義了一個(gè)CurrentCount的Field, 然后在IncreaseCount方法里給它加一. 一個(gè)叫Click me的button標(biāo)簽里 有一個(gè)@onclick方法, 將IncreaseCount作為參數(shù)
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其實(shí)在這里就是執(zhí)行了一個(gè)Component的一個(gè)方法onclick, 顧名思義,當(dāng)這個(gè)Component被點(diǎn)擊的時(shí)候就被調(diào)用. 我們的IncreaseCount被作為參數(shù)傳給了它, 可見onclick會(huì)在被點(diǎn)擊的時(shí)候執(zhí)行IncreaseCount.
那么問題來了,當(dāng)我們執(zhí)行了IncreaseCount方法時(shí), 頁面怎么會(huì)知道要不要刷新? 是刷新整個(gè)頁面還是刷新所有?
熟悉WPF的同學(xué)可能知道, 在WPF中如果我們需要讓一個(gè)ViewModel可以被監(jiān)聽變化, 他就需要實(shí)現(xiàn)INotifyChanged事件. 那么同樣道理, 我們的這個(gè)IncreaseCount可能也是類似的嗎?
然而基于編譯后的代碼我們可以發(fā)現(xiàn) CurrentCount作為我們Counter這個(gè)類的Field, 并沒有任何機(jī)會(huì)高速Page自己變化了. 而且這個(gè)Field非常普通,也不是什么WPF中的DP, 所以到目前為止變化是怎么通知的.并沒有一個(gè)合理的解釋. 后面的時(shí)間里我會(huì)嘗試閱讀Blazor的代碼搞清楚這件事情.?
第一個(gè)問題畫個(gè)問號(hào), 那么第二個(gè)問題呢??
打開瀏覽器工具, 定位到button, 再次點(diǎn)擊button觀察dom的反應(yīng).
我們看到 在點(diǎn)擊Button的時(shí)候, button上面的<p>標(biāo)簽閃動(dòng)了, 說明它被刷新了, 而其他標(biāo)簽并沒有. 所以局部刷新的功能是有的. 效率問題不用擔(dān)心了.?
編輯Click me, 把他的內(nèi)容變成 "點(diǎn)擊我", 再次點(diǎn)擊按鈕, 我們看到還是只有p變, 而且button也沒有變回原來的內(nèi)容
?
?所以我們知道, 這個(gè)局部刷新不是簡單的拿Dom作比較, 肯定是有Virtual Dom的機(jī)制在里面.
?
?星星之火,可燎原?
在簡單的嘗試了Blazor之后, 還是很興奮的. 可以看到Blazor是一個(gè)初具規(guī)模的產(chǎn)品. 我們C#開發(fā)可以用Blazor在今后寫前端渲染的網(wǎng)頁了!?
我很期望這樣一個(gè)產(chǎn)品能夠持續(xù)的演進(jìn)下去.
就目前版本看(0.1.0), Blazor尚不能應(yīng)用到產(chǎn)品中. 主要還是有以下的原因
?打包大小太大, 1.8M的大小對于網(wǎng)站簡直是致命的.
?產(chǎn)品還不成熟, 現(xiàn)在Component還只能支持簡單的事件, 筆者測試的時(shí)候只有onclick,onchange.?
?兼容性差,使用了WebAssembly,就注定了兩年前的瀏覽器必定不能支持.
當(dāng)然我們還是不能否認(rèn), Blazor為如何讓更多語言進(jìn)入前端世界打開了一扇新的大門. 也許未來JavaScript將不僅僅是前端唯一可以使用的利器. 我們會(huì)看到C/C++, Python, Java寫的前端渲染頁面也不一定呢.
當(dāng)然在后端語言打入前端世界的道路上, WebAssembly也未必是唯一的路勁, 比如Scala.js就完全使用了js重寫了Scala的庫函數(shù), 類似的還有Kotlin.js. 可以看到雖然JavaScript已經(jīng)非常Fancy了,但是后端程序員們進(jìn)軍前端的熱情可謂從未停歇過啊.
祝dotnet的應(yīng)用越來越廣, 祝廣大后端程序員們新年成就慢慢, 加薪升職.
原文:?https://www.cnblogs.com/Gerryz/p/get-start-with-dotnet-blazor.html?
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號(hào)文章匯總 http://www.csharpkit.com
總結(jié)
以上是生活随笔為你收集整理的来自后端的突袭? --开包即食的教程带你浅尝最新开源的C# Web引擎 Blazor的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 快速搭建CentOS+ASP.NET C
- 下一篇: Microsoft AI - Custo