通过官方的介绍可以知道,Blazor是一个Web端的UI框架。
但与现在流行的UI框架(Angular,VUE等)不同,Blazor 是允许使用C#来代替一些Javascript功能的,而且可以在UI端和服务端共享业务逻辑代码。
听起来就很有吸引力,作为一个多年的.NET癌,自然要尝试一下。
创建Blazor项目需要Visual Studio 2019 以上版本,笔者用的是Version 16.6.2。
创建完直接运行就可以了,默认的模板会创建3个页面。
监控网络可以发现,第一次加载页面的时候会有大量的程序集加载到浏览器,最大的甚至达到了1.4MB,对于互联网网站来说还是不太友好,
不过由于只会加载一次,如果是企业内部应用或者一些胖客户端应用,还是可以接受的。
由于创建的时候选择了Web WebAssembly,并且勾选了多个复选框,所以Visual Studio会自动创建3个项目。
Server和Shared项目没什么特别的, 唯一需要留意的是Server的Startup里面对Blazor中间件的声明:
<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页面。
其中的
{
"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,应该是每次都重新初始化了,有没有机制保持页面的状态呢?
@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并没有地方显式调用,这么看应该属于预定义的页面事件,是不是还有其他事件?整个页面的生命周期是怎样的?
@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,那么这个参数是单向的还是双向的? 用户控件有没有事件参数?应该如何定义?
@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的概念。
<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加载对应的页面。
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。