使用 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
这里我从空项目开始创建。
# 配置项目
# 配置 Starup
public void ConfigureServices(IServiceCollection services) | |
{ | |
// 启用 RazorPage | |
services.AddRazorPages(); | |
// 启用服务端 Blazor | |
services.AddServerSideBlazor(); | |
} |
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 文件加入内容
@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 主页
@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 结尾的文件写入如下文件。
@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 文件用于配置路由情况和默认布局
<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
# 实体配置
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); | |
} | |
} | |
} |
# 注入
services.AddDbContext<AppDbContext>(options => options.UseSqlite(Configuration.GetConnectionString("sqlite"))); |
# appsetting
// 连接字符串 | |
"ConnectionStrings": { | |
"sqlite": "Data Source=./myData.db" | |
}, |
# service
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); | |
} | |
} | |
} |
/* | |
* AddSingleton 添加唯一注入 | |
* AddScoped 添加会话注入,每次会话不同 | |
* AddTransient 添加不同实例,每次请求都不同 | |
*/ | |
services.AddScoped<AppDbContext>(); | |
services.AddScoped<UserService>(); |
# Razor 界面
# userList
@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
@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
@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; | |
} | |
} |