Blazor 初体验

什么是Blazor

通过官方的介绍可以知道,Blazor是一个Web端的UI框架。
但与现在流行的UI框架(Angular,VUE等)不同,Blazor 是允许使用C#来代替一些Javascript功能的,而且可以在UI端和服务端共享业务逻辑代码。
听起来就很有吸引力,作为一个多年的.NET癌,自然要尝试一下。

创建

创建Blazor项目需要Visual Studio 2019 以上版本,笔者用的是Version 16.6.2。

  • 新建项目,选择Blazor App,然后点击下一步.
  • 输入项目名称,点击创建
  • 接下来会出现两个选项,一个是基于Server的,一个是基于Web WebAssembly的,这恐怕也是Blazor与其他传统Web UI框架最大的区别,
    官方文档上也有介绍两者的区别。
    笔者此次选择的是Web WebAssembly,并且勾选了右边栏的ASP.NET Core Hosted和PWA复选框。如下图:
    Blazor 初体验_第1张图片

运行

创建完直接运行就可以了,默认的模板会创建3个页面。

  • 第1个是万能的Hello World。
    Blazor 初体验_第2张图片

  • 第2个页面包含一个计数器,每次点击按钮都会加1。
    Blazor 初体验_第3张图片

  • 第3个页面是一个数据列表。
    Blazor 初体验_第4张图片

监控网络可以发现,第一次加载页面的时候会有大量的程序集加载到浏览器,最大的甚至达到了1.4MB,对于互联网网站来说还是不太友好,
不过由于只会加载一次,如果是企业内部应用或者一些胖客户端应用,还是可以接受的。
Blazor 初体验_第5张图片

项目结构分析

由于创建的时候选择了Web WebAssembly,并且勾选了多个复选框,所以Visual Studio会自动创建3个项目。

  • BlazorApp1.Client: Blazor的核心项目,负责客户端的呈现和逻辑处理。
  • BlazorApp1.Server: 后台项目,处理服务端请求,添加了一个Web Api。
  • BlazorApp1.Shared: Client和Server的共享代码,这里只添加了一个Model。

Server和Shared项目没什么特别的, 唯一需要留意的是Server的Startup里面对Blazor中间件的声明:
Blazor 初体验_第6张图片

BlazorApp1.Client

Blazor 初体验_第7张图片

  • wwwroot 网站的根目录,里面包含了常用的css,js等文件
  • wwwroot/index.html 网站首页,当浏览器输入url(比如http://localhost:5000/)的时候首先会加载该页面,代码如下:
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>BlazorApp1title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link href="manifest.json" rel="manifest" />
    <link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
head>
<body>
    <app>Loading...app>
    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reloada>
        <a class="dismiss">a>
    div>
    <script src="_framework/blazor.webassembly.js">script>
    <script>navigator.serviceWorker.register('service-worker.js');script>
body>
html>

没错,就是一个普通的html页面。
其中的Loading…就是Blazor的容器, 他会在未加载完成之前显示Loading字样,实际项目当中应该可以定制里面的代码来显示有特色的加载提示。

是加载错误之后显示的信息。
blazor.webassembly.js 文件是用来加载webassembly的js库,项目当中是找不到这个文件的,需要发布以后才能看到。
service-worker.js 这个涉及到另一个技术了,暂时先跳过吧,调试模式当中默认也不会用到。

  • wwwroot/manifest.json 目前尚不清楚该清单文件中各个属性是如何工作的, 看起来是支持颜色配置?
{
  "name": "BlazorApp1",
  "short_name": "BlazorApp1",
  "start_url": "./",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#03173d",
  "icons": [
    {
      "src": "icon-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ]
}
  • Pages目录 用来存放Blazor页面

  • Pages/Counter.razor 这是前面提到的计数器页面,代码如下:

@page "/counter"

<h1>Counterh1>

<p>Current count: @currentCountp>

<button class="btn btn-primary" @onclick="IncrementCount">Click mebutton>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

对于从MVC过来的.NETer来说,这个页面再熟悉不过了,满满的都是Razor的语法,完全可以实现零门槛入门。
第一行通过@page指令给该页面指定了路由,当浏览器请求 http://xxx/counter 的时候会自动使用该页面来处理请求,笔者觉得这个比Angular的路由声明更友好,就是暂时不知道他是如果处理参数和权限控制的。
@code指令里面包含的是c#代码,注意这些都是在客户端Web Assembly运行的,是不是真香? 感觉快可以抛弃Javascript了,不过还能不能用浏览器调试呢?。
试运行的时候发现每次进入这个页面,计数器都会变成0,应该是每次都重新初始化了,有没有机制保持页面的状态呢?

  • Pages\FetchData.razor 这是前面提到的数据列表页面,代码如下:
@page "/fetchdata"
@using BlazorApp1.Shared
@inject HttpClient Http

<h1>Weather forecasth1>

<p>This component demonstrates fetching data from the server.p>

@if (forecasts == null)
{
    <p><em>Loading...em>p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Dateth>
                <th>Temp. (C)th>
                <th>Temp. (F)th>
                <th>Summaryth>
            tr>
        thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()td>
                    <td>@forecast.TemperatureCtd>
                    <td>@forecast.TemperatureFtd>
                    <td>@forecast.Summarytd>
                tr>
            }
        tbody>
    table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }

}

还是熟悉的味道,这里引用了共享目录下定义的Model来进行数据绑定。
同时使用@inject指令来注入了一个HttpClient,并使用她来发起一个http请求,根据返回的结果来绑定页面。
读到这里就不由得想到实际项目当中经常会在http请求的头里面添加Access Token, 以及经常需要在请求的过程中显示加载指示条,这种场景该如何实现?
OnInitializedAsync并没有地方显式调用,这么看应该属于预定义的页面事件,是不是还有其他事件?整个页面的生命周期是怎样的?

  • Pages\Index.razor 默认首页
@page "/"

<h1>Hello, world!h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

这里用到的SurveyPrompt是一个用户控件,Title是她的参数。该控件的定义在Shared/SurveyPrompt.razor文件里面。
以下是该控件的代码:


<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true">span>
    <strong>@Titlestrong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2127996">brief surveya>
    span>
    and tell us what you think.
div>

@code {
    // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string Title { get; set; }
}

这里通过[Parameter]属性定义了参数Title,那么这个参数是单向的还是双向的? 用户控件有没有事件参数?应该如何定义?

  • Shared/MainLayout.razor 这是模板页,代码如下:
@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
div>

<div class="main">
    <div class="top-row px-4">
        <a href="http://blazor.net" target="_blank" class="ml-md-auto">Abouta>
    div>

    <div class="content px-4">
        @Body
    div>
div>

她通过 @inherits LayoutComponentBase 指令把自己定义成模板页,同时还包含了用户控件NavMenu。
LayoutComponentBase 是框架自带的类,从下图的定义可以看到她是继承了ComponentBase,估计所有的控件(包括页面)都继承了ComponentBase,类似以前的Webform的概念。
Blazor 初体验_第8张图片

  • Shared/NavMenu.razor 菜单控件,这里比较特别的是以下代码:
<NavLink class="nav-link" href="fetchdata">
    <span class="oi oi-list-rich" aria-hidden="true">span> Fetch data
NavLink>

很显然这又是一个用户控件,项目里面找不到相关代码,肯定是框架自带的,她的功能就是做路由跳转,有机会要去翻翻她的源代码,应该对我们写自定义控件很有帮助。

  • _Imports.razor
    这个没什么说的,就是导入一些命名空间。

  • App.razor App的启动页

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.p>
        LayoutView>
    NotFound>
Router>

定义了启动的程序集,同时根据url加载对应的页面。

  • Program.cs 程序的入口
public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add("app");

        builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

        await builder.Build().RunAsync();
    }
}

这里可以看到注入了FetchData页面用到的HttpClient。

备忘

  • 基于Server的Blazor项目。
  • 自定义加载提示。
  • service-worker。
  • manifest.json 中各个属性的含义。
  • 路由如何处理参数和权限控制?
  • Web Assembly是否可以在浏览器调试?
  • 页面切换的时候是否可以保持页面状态?
  • HttpClient如何自定义头和显示加载指示条?
  • 页面的预定义事件以及生命周期有哪些?
  • 用户控件的参数(输入,输出,事件)如何定义?
  • NavLink控件的源码。

你可能感兴趣的:(Blazor)