Blazor初体验

使用Blazor 来实现前后端统一使用C#开发,用C#写前端,想想就刺激😃

什么是Blazor

Blazor 是一个使用 .NET 生成交互式客户端 Web UI 的框架:

  • 使用 C# 代替 JavaScript 来创建信息丰富的交互式 UI。
  • 共享使用 .NET 编写的服务器端和客户端应用逻辑。
  • 将 UI 呈现为 HTML 和 CSS,以支持众多浏览器,其中包括移动浏览器。
  • 与新式托管平台(如 Docker)集成。

使用 .NET 进行客户端 Web 开发可提供以下优势:

  • 使用 C# 代替 JavaScript 来编写代码。
  • 利用现有的 .NET 库生态系统。
  • 在服务器和客户端之间共享应用逻辑。
  • 受益于 .NET 的性能、可靠性和安全性。
  • 在 Windows、Linux 和 macOS 上使用 Visual Studio 保持高效工作。
  • 以一组稳定、功能丰富且易用的通用语言、框架和工具为基础来进行生成

Blazor应用基于组件。 Blazor 中的组件是指 UI 元素,例如页面、对话框或数据输入窗体。

组件是内置到 .NET 程序集的 .NET C# 类,它们用于:

  • 定义灵活的 UI 呈现逻辑。
  • 处理用户事件。
  • 可以嵌套和重用。
  • 可作为 Razor 类库NuGet 包共享和分发。

创建项目

此文使用 vs2019 .net core 3.1

这里我从空项目开始创建。

image-20210406092424290

配置项目

配置Starup

ConfigureServices
1
2
3
4
5
6
7
8
public void ConfigureServices(IServiceCollection services)
{
//启用RazorPage
services.AddRazorPages();
//启用服务端Blazor
services.AddServerSideBlazor();
}

Configure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

//启用静态文件
app.UseStaticFiles();
//启用路由
app.UseRouting();
//启用终端节点
app.UseEndpoints(endpoints =>
{
//配置实时连接SinglR
endpoints.MapBlazorHub();
//配置Blazor的主页
endpoints.MapFallbackToPage("_Host");
});
}

配置通用依赖项

创建_Imports.razor 文件加入内容

1
2
3
4
5
6
7
8
9
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using BlazorAppGo
@using BlazorAppGo.Shared

书写_Host主页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@page "/"
@namespace BlazorAppGo.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BlazorAppGo</title>
<base href="~/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
</head>
<body>
<app>
<component type="typeof(App)" render-mode="ServerPrerendered" />
</app>

<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

<script src="_framework/blazor.server.js"></script>
</body>
</html>

Razor 页面

在Blazor中,每一个页面以.cshtml结尾,页面可以绑定一个模型类用户数据操作。

Rezor布局

在Blazor中,在Shared文件夹下书写布局和通用组件。创建.razor结尾的文件写入如下文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@inherits LayoutComponentBase

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

<div class="main">
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>

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

在布局文件中,使用@inherits LayoutComponentBase 来继承自LayoutComponentBase 标注为布局文件

App.razor

创建App.razor文件用于配置路由情况和默认布局

1
2
3
4
5
6
7
8
9
10
11

<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>

Razor 组件

创建.razor结尾的文件作为Razaor组件,组件可以在其他页面或组件中复用

直接使用<组件名称>当作html元素使用即可。

CURD小例子

EF连接Sqlite

在数据库连接上,这里我使用比较简单的sqlite

image-20210406154848381

安装相关包

实体配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using Microsoft.EntityFrameworkCore;

namespace BlazorAppGo.Models
{
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }

public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{

}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

}
}
}

注入

1
services.AddDbContext<AppDbContext>(options => options.UseSqlite(Configuration.GetConnectionString("sqlite")));

appsetting

1
2
3
4
//连接字符串
"ConnectionStrings": {
"sqlite": "Data Source=./myData.db"
},

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using BlazorAppGo.Models;
using System.Collections.Generic;
using System.Linq;

namespace BlazorAppGo.Services
{
public class UserService
{

private readonly AppDbContext dbContext;

public UserService(AppDbContext dbContext)
{
this.dbContext = dbContext;
}

public void Add(User user)
{
dbContext.AddAsync(user);
dbContext.SaveChanges();
}

public void Update(int id, User data)
{
var user = dbContext.Users.FirstOrDefault(x => x.Id == id);
user.Name = data.Name;
dbContext.Update(user);
dbContext.SaveChanges();
}

public void Remove(int id)
{
dbContext.Remove(dbContext.Users.FirstOrDefault(x => x.Id == id));
dbContext.SaveChanges();
}

public List<User> List()
{
return dbContext.Users.ToList();
}

public User FindOne(int id)
{
return dbContext.Users.FirstOrDefault(x => x.Id == id);
}

}
}
注入
1
2
3
4
5
6
7
/*
* AddSingleton 添加唯一注入
* AddScoped 添加会话注入,每次会话不同
* AddTransient 添加不同实例,每次请求都不同
*/
services.AddScoped<AppDbContext>();
services.AddScoped<UserService>();

Razor界面

userList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@page "/userList"
@using BlazorAppGo.Models
@using BlazorAppGo.Services
@inject BlazorAppGo.Services.UserService userService
@inject NavigationManager NavigationManager



<button class="btn btn-primary" @onclick="Add">新增</button>
<div class="row">
<div class="col-1">ID</div>
<div class="col-2"><input type="text" class="form-control" @bind-value="id"></div>
<div class="col-1">Name</div>
<div class="col-2"><input type="text" class="form-control" @bind-value="name"></div>
</div>


<table class="table" style="margin-top:15px">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Options</th>
</tr>
</thead>
<tbody>
@foreach (var user in users)
{
<tr>
<td>@user.Id</td>
<td>@user.Name</td>
<td>
<button class="btn btn-info" @onclick="@(e=>Info(user.Id))">详细</button>
<a href="/userUpdate/@user.Id" class="btn btn-warning">修改</a>
<button class="btn btn-danger" @onclick="@(e=>Del(user.Id))">删除</button>
</td>
</tr>
}
</tbody>
</table>

<h3>@cid</h3>

@code {
private List<User> users;

private int id;
private string name;

private int cid;

protected override void OnInitialized()
{
users = userService.List();
}




private void Add()
{
userService.Add(new User { Id = id, Name = name });
users = userService.List();

//重新绑定 刷新页面
//StateHasChanged();

//刷新
ShouldRender();


}

private void Info(int id)
{
NavigationManager.NavigateTo($"/userInfo/{id}");

}

private void Del(int id)
{
userService.Remove(id);
users = userService.List();
ShouldRender();

}
}

userUpdate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@page "/userUpdate/{Id}"
@inject BlazorAppGo.Services.UserService userService

<button class="btn btn-primary" @onclick="Save">保存</button>
<div class="row">
<div class="col-1">ID</div>
<div class="col-2"><input type="text" class="form-control" @bind-value="Id"></div>
<div class="col-1">Name</div>
<div class="col-2"><input type="text" class="form-control" @bind-value="name"></div>
</div>

@if (show)
{
<h4>修改成功</h4>
}


<a href="/userList">返回</a>


@code {

[Parameter]
public string Id { get; set; }

string name;

bool show;


protected override void OnInitialized()
{
var user = userService.FindOne(int.Parse(Id));
name = user.Name;

}

void Save()
{
userService.Update(int.Parse(Id), new Models.User { Id = int.Parse(Id), Name = name });
show = true;
}
}

userInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@page "/userInfo/{Id}"
@inject BlazorAppGo.Services.UserService userService


<h1>UserInfo @Id</h1>

<h2>Id:@Id</h2>
<h2>Name:@Name</h2>
<a href="/userList" class="btn btn-primary">返回</a>



@code{

[Parameter]
public string Id { get; set; }
private string Name;

protected override void OnInitialized()
{
var user = userService.FindOne(int.Parse(Id));
Name = user.Name;
}
}

效果

效果