来自后端的突袭? --开包即食的教程带你浅尝最新开源的C# Web引擎 Blazor

2021-04-19 23:25

阅读:673

1
2
3
4
5
6
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是程序的入口点

技术分享图片
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");
        }
    }
}
技术分享图片

在入口点中, 我们注册了一个浏览器渲染服务 BrowserRender,让他渲染App

App.cshmtl是这样的

这里的Router对应的是Microsoft.AspNetCore.Blazor.Routing.Router. 当给它一个AppAssembly时, 他就会自动的把当前的Url 和 AppAssembly的其他Pages对应起来.

所以 当我们在浏览器里输入 /Counter时,他就会加载Pages/Couter.cshtml.

Shared文件夹里分别是布局文件,导航栏, 还有一个我们自定义的控件 SurveyPrompt. 

熟悉Razor引擎的小伙伴们一定很轻车熟路了. 那么当我们打开网站时, 默认显示给我们的 就是Index, 这个时候我们会加载Pages/Index.cshtml

Index.cshtml的代码是这个样子的

技术分享图片
@page "/"

Hello, world!

Welcome to your new app.
技术分享图片

@page 可以告诉Router, 当前页面注册到 "/"

除了显示hello world以外, 我们在这里还看到了刚刚说到的第三方控件. SurveyPrompt. 果然不简单嘛, 一个看似简单的页面, 居然还告诉了我们如何使用自定义控件.

从声明上看, 我们知道 SunveyPrompt是一个控件,并且有一个属性Title. 现在我们打开它的代码

技术分享图片


@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"
// 
#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"

Counter

Current count: @currentCount

@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"
// 
#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上面的

标签闪动了, 说明它被刷新了, 而其他标签并没有. 所以局部刷新的功能是有的. 效率问题不用担心了. 

编辑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了,但是后端程序员们进军前端的热情可谓从未停歇过啊.

祝dotnet的应用越来越广, 祝广大后端程序员们新年成就慢慢, 加薪升职.

部分 FAQ(翻译自Blazor官方)

Q:Is this Silverlight all over again? 这又是一个Silverlight吗? 

No, Blazor is a .NET web framework based on HTML and CSS that runs in the browser using open web standards. It requires no plugin and works on mobile devices and older browsers.

 

当然不是了, Blazor完全是基于Css 和 Html展现的, 它的运行不需要任何插件.

 

Q:What features will Blazor support?Blazor都会支持什么功能?

Blazor会支持现在主流单页面Web Application所应具有的功能:

  • A component model for building composable UI 一个面向组件化UI构建而设计的控件模型
  • Routing 路由
  • Layouts 布局
  • Forms and validation 表单验证
  • Dependency injection 依赖注入 
  • JavaScript interop 与JavaScript的互操作
  • Live reloading in the browser during development 开发过程中浏览器的热重载
  • Server-side rendering 后端渲染(前后端均可渲染页面)
  • Full .NET debugging both in browsers and in the IDE 在浏览器和IDE中都可以获得全面的.Net 调试支持
  • Rich IntelliSense and tooling 丰富的代码提示和辅助工具
  • Ability to run on older (non-WebAssembly) browsers via asm.js 在老旧浏览器上能通过 asm.js加载网页
  • Publishing and app size trimming 进一步缩减发布网站时的大包大小

Q: Can I access the DOM from a Blazor app? 我可以在Blazor中操作DOM吗

You can access the DOM through JavaScript interop from .NET code. However, Blazor is a component based framework that minimizes the need to access the DOM directly.

你可以使用JavaScript互操作层操纵DOM. 但是请注意, Blazor是一个模块化渲染的框架, 他的目的就是尽可能减少直接操纵DOM

 

 

最后软广一波, 

别具一格,追求卓越的ThoughtWorks 2018全面招人啦!

不关你是前端, 后端, 开发,测试, 产品经理, BA, 还是运维, 我们这里有号称最难的面试就问你敢不敢 来挑战?

想来的同学们请留言或者发信哈 我的邮箱是 gerry.zhi@foxmail.com

技术分享图片

 

 

 

 

 


评论


亲,登录后才可以留言!