EasyAdminBlazor 实战:从 Entity 到完整 Admin Blazor 后台详解

这是整个系列的综合教程:只给一个实体类,走完全部 11 步,得到一个上线可用的后台模块。 每一步都给出可执行的代码和判断依据,哪一步不需要就跳过。


总览:11 步清单

text 复制代码
1  写实体
2  生成 / 写页面
3  建菜单 + 按钮
4  角色授权
5  数据权限(需要行级隔离时)
6  高级查询与关联(需要时)
7  表单增强:文件 / 富文本 / 字典(需要时)
8  Excel 导入导出(需要时)
9  审批(需要时)
10 日志与审计
11 部署上线

演示项目里的 6 个模块就是按这个流程做的,合计 15 个文件 / 707 行(第 30 篇有统计口径)。


第 1 步:写实体

csharp 复制代码
using FreeSql.DataAnnotations;
using System.ComponentModel;

namespace EasyAdminBlazor.Test.Products;

/// <summary>产品</summary>
[Table(Name = "product")]
public partial class Product : EntityFull
{
    [DisplayName("产品名称")]
    [Column(StringLength = 100)]
    public string Title { get; set; } = string.Empty;

    [DisplayName("产品图片")]
    [Column(StringLength = 400)]
    public string Image { get; set; } = string.Empty;

    [DisplayName("价格")]
    [Column(Precision = 10, Scale = 2)]
    public decimal Price { get; set; }

    [DisplayName("库存")]
    public int Stock { get; set; }

    [DisplayName("摘要")]
    [Column(StringLength = 500)]
    public string Excerpt { get; set; } = string.Empty;

    [DisplayName("产品详情")]
    [Column(StringLength = -2)]
    public string Content { get; set; } = string.Empty;

    [DisplayName("是否上架")]
    public bool IsOnSale { get; set; } = true;
}

基类怎么选:

基类 带什么 什么时候用
Entity 只有主键 纯关联表、字典表
EntityCreated 主键 + 创建人/时间 只增不改的日志类数据
EntityFull 主键 + 创建/修改人时间 + 软删除 业务实体的默认选择
ApprovalEntityFull EntityFull + 审批字段 需要审批的单据(第 9 步)

字段约定:

  • [DisplayName] 决定列头与表单标签,必须写;
  • 字符串写 StringLength,长文本用 -2,不要留默认;
  • 金额用 decimal + Precision/Scale;
  • 软删除靠 EntityFull,不需要自己加 IsDeleted。

第 2 步:生成或手写页面

方式 A:代码生成器

打开 /Admin/CrudGenerator,按 doc/代码生成.md 的说明配置:

配置区 关键项
基础设置 实体类型、路由路径、分页大小、编辑弹窗尺寸
基础设置 搜索 / 高级搜索 / 扩展按钮 / 导入 / 导出 / 多选
基础设置 草稿保存、保存不关闭、允许增删改、生成菜单
列配置 显示 / 筛选 / 搜索 / 编辑列宽
关系配置 多对一(下拉或弹框)、一对多/多对多(IncludeMany 或子表编辑)

生成器还能识别类型并给出对应控件:

属性类型 表格列 编辑控件
string 可筛选 + 可搜索 BootstrapInput
int / long / decimal 可筛选 BootstrapInput type="number"
bool / bool? 可筛选 Switch / NullSwitch
DateTime 可筛选 DateTimePicker
[Flags] 枚举 位运算筛选 MultiSelect
普通枚举 可筛选 Select
多对一外键 显示关联名 + 筛选 AdminSelectEntity
一对多 / 多对多 ------ 子表编辑 / IncludeMany

生成的文件会写进当前运行项目的 Components/ 下,子目录由路由路径决定。

方式 B:手写(演示项目的做法)

razor 复制代码
@page "/Admin/Product"
@using ProductEntity = EasyAdminBlazor.Test.Products.Product

<AdminTable TItem="ProductEntity" TKey="long"
            OnBeforeQuery="OnBeforeQuery"
            EditDialogSize="Size.ExtraLarge"
            EnableSaveWithoutClose EnableDraft
            ShowImportButton ShowExportButton ShowExtendButtons
            IsPagination ShowSearch ShowAdvancedSearch IsMultipleSelect>
    <TableColumns>
        <TableColumn @bind-Field="context.Title" Filterable="true" Searchable="true" />
        <TableColumn @bind-Field="context.Price" Filterable="true" />
        <TableColumn @bind-Field="context.Stock" Filterable="true" />
        <TableColumn @bind-Field="context.Image" Filterable="true">
            <Template Context="v">
                @if (!string.IsNullOrEmpty(v.Row.Image))
                {
                    <img src="@v.Row.Image" />
                }
            </Template>
        </TableColumn>
        <TableColumn @bind-Field="context.IsOnSale" Filterable="true" />
        <TableColumn @bind-Field="context.CreatedTime" Filterable="true" />
    </TableColumns>
    <EditTemplate>
        <ProductEdit item="context" />
    </EditTemplate>
</AdminTable>

@code {
    private void OnBeforeQuery(AdminQueryEventArgs<ProductEntity> e)
    {
        // 需要追加条件或 Include 时写这里
    }
}

页面组件与实体同名时记得加 @using ... = ... 别名,否则会撞类型。


第 3 步:建菜单 + 按钮

三种做法:

后台生成代码同时生成菜单

后台手工建 :系统管理 → 菜单 → 新增,类型选"增删改查"(会自动带 add / edit / remove 三个按钮),父级选一个分组。

代码补种(适合多环境一致交付,演示项目用的就是这种):

csharp 复制代码
List<SysMenu> cudButtons() => new[]
{
    new SysMenu { Label = "添加", Path = "add",    Sort = 10011, Type = SysMenuType.Button },
    new SysMenu { Label = "编辑", Path = "edit",   Sort = 10012, Type = SysMenuType.Button },
    new SysMenu { Label = "删除", Path = "remove", Sort = 10013, Type = SysMenuType.Button }
}.ToList();

repo.Insert(new[]
{
    new SysMenu
    {
        Label = "产品",
        Path = "Admin/Product",
        ParentId = demoRoot.Id,
        Sort = 10005,
        Type = SysMenuType.Menu,
        Childs = new List<SysMenu>(cudButtons())
    }
});

路径必须与 @page 一致(不区分大小写),按钮必须挂在目标菜单下(第 08 篇)。


第 4 步:角色授权

角色页 → 选中角色 → 右侧菜单树勾选 → 保存。保存会调用 InvalidatePermissionCacheAsync(),权限立即生效。

自检:用一个非管理员账号登录,确认只看到勾选的菜单和按钮。


第 5 步:数据权限(需要行级隔离时)

实体实现 IDataPermission(并提供 IEntityCreated),然后开启:

razor 复制代码
<AdminTable TItem="ProductEntity" TKey="long" UseDataPermission ...>

EntityFull 已经带了 CreatedUserId,所以只需要加一个组织字段:

csharp 复制代码
public partial class Order : EntityFull, IDataPermission
{
    [DisplayName("所属组织")]
    public long OrgId { get; set; }
    ...
}

剩下的四件事框架自动做(第 09 篇):

路径 行为
查询 自动加组织过滤条件
新增 AuditValue 自动写入当前用户的 OrgId
修改 / 删除 按主键回查权限范围
Excel 导入 逐行过滤越权数据

第 6 步:高级查询与关联

需求 做法
多列模糊搜索 列上标 Searchable="true"
高级搜索 表格加 ShowAdvancedSearch
列头筛选 列上标 Filterable="true"
关联名称显示 Template + v.Row.Classify?.ClassifyName
关联实体筛选 FilterProvider + AdminSelectEntityFilter(Generic)
固定查询条件 OnBeforeQuery 里 e.Select.Where(...)
关联数据加载 OnBeforeQuery 里 Include / IncludeMany
多选筛选 FilterProvider + AdminMultiSelectFilter

两个纪律:导航属性必须写进 IgnoreSearchColumns;导出场景用 IsExport 跳过 Include(第 05、06 篇)。


第 7 步:表单增强

字段类型 组件
图片 / 附件 AdminFileInput
富文本 AdminEditor(装 HtmlEditor 扩展后是 TinyMCE)
字典单选 / 多选 AdminDictSelect / AdminDictMultiSelect
关联实体单选 AdminSelectEntity / AdminSelectTable
关联实体多选 AdminMultiSelect
树形选择 AdminTree
复选框列表 AdminCheckboxListGeneric

校验靠实体的数据注解 + BootstrapBlazor 输入组件(保存前框架会执行 EditContextCapture.Validate(),第 07 篇)。


第 8 步:Excel 导入导出

razor 复制代码
<AdminTable ... ShowImportButton ShowExportButton>

加上这两个参数就获得:模板下载、5MB 上传、按可见列导入、按筛选条件导出。

需要业务校验时加回调:

csharp 复制代码
private async Task OnBeforeImport(AdminImportEventArgs<ProductEntity> e)
{
    var errors = e.Items.Where(x => x.Price < 0).Select(x => $"产品「{x.Title}」价格不能为负").ToList();
    if (errors.Count > 0)
    {
        await SwalService.Warning("导入校验失败", string.Join("<br/>", errors.Take(20)));
        e.Cancel = true;
    }
}

数据权限过滤、UpdateColumns 白名单、操作日志都由框架负责(第 12 篇)。


第 9 步:审批

三处改动:

csharp 复制代码
// 1) 实体换基类
public partial class Article : ApprovalEntityFull { ... }
csharp 复制代码
// 2) 注册流程
.AddEasyAdminBlazorApproval(o =>
{
    o.Flows.Add(new ApprovalFlowConfig
    {
        BillType = typeof(Article).FullName!,
        Levels =
        [
            new ApprovalLevelConfig { Level = 1, Name = "部门主管审批", Kind = ApproverKind.OrgLeader, Offset = 1 },
            new ApprovalLevelConfig { Level = 2, Name = "管理员审批", Kind = ApproverKind.Role, Value = "Administrator" }
        ]
    });
})
razor 复制代码
<!-- 3) 编辑模板加审批选项卡 + 列表加状态列 -->
<TabItem Text="@CommonLocalizer["审批"]">
    <ApprovalActions TItem="Article" Bill="Model" OnChanged="OnApprovalChanged" />
</TabItem>

<TableColumn @bind-Field="context.ApprovalStatus" Filterable="true">
    <Template Context="v">@v.Row.ApprovalStatusText</Template>
</TableColumn>

接入后自动获得:保存后提交/自动提交、审批中禁止修改删除、待办通知、审批中心、并发与事务保护(第 16--18 篇)。


第 10 步:日志与审计

需求 做法
程序日志 框架内置(DatabaseLogger,自动进错误日志页)
操作审计 方法上标 [OperationLog("修改了xx")]
登录日志 框架内置,登录/退出自动记录
排查问题 错误日志页按 TraceId / 用户 / 租户筛选

演示项目里连"暂停任务""恢复任务"这类操作都标了操作日志:

csharp 复制代码
[OperationLog("恢复了任务")]
async Task ResumeTask(SchedulerTaskData task)

第 11 步:部署上线

按第 28 篇的 IIS 清单执行,其中与模块相关的自检:

  • 新模块的菜单在生产库里也存在(用代码补种或导入菜单);
  • 角色权限在生产环境重新确认(不同环境的角色数据不一定同步);
  • 如果模块有文件字段,确认 uploads 目录可写;
  • 如果模块是审批单据,确认审批人(组织负责人/角色)已维护;
  • 生产环境 UseAutoSyncStructure 关闭时,新表/新列已由脚本或预发布同步。

交付前自检清单

每做完一个模块,对着这张表过一遍:

# 检查项
1 实体:基类正确、[DisplayName] 齐全、长度/精度明确
2 页面:列可见性、筛选、搜索符合业务预期
3 页面:编辑模板字段与实体字段一一对应
4 菜单:路径与 @page 一致,类型正确
5 权限:按钮节点存在(add/edit/remove)
6 权限:用非管理员账号验证过菜单与按钮
7 数据权限(如启用):另一个组织的数据确实看不到、改不动
8 查询:导航属性在 IgnoreSearchColumns 里
9 表单:必填/长度校验真的会拦
10 导入导出(如启用):导入的越权行被剔除、导出列正确
11 审批(如启用):提交/同意/驳回/撤回各走通一次,审批人配置正确
12 审计:关键操作能在操作日志里查到

小结

从一个 Entity 到一个完整模块,真正的工作量集中在三件事:

  1. 想清楚数据结构(第 1 步);
  2. 想清楚谁能看、谁能改(第 3--5 步);
  3. 想清楚业务规则(校验、导入、审批)。

其余部分------分页、排序、筛选、按钮权限、数据过滤、导入导出、事务并发、日志审计------都是"配置一下"或"写个回调"的事。


如果你正在用 .NET 10 + Blazor 做后台,可以把这篇当作新模块的开工清单。EasyAdminBlazor 的演示项目里有 6 个按这套流程做出来的真实模块,可以直接对照。

相关推荐
Bazingga1 小时前
AgentScope 到底是用来做什么的?一篇笔记说清楚
后端
威哥爱编程1 小时前
【AI全栈12-03】Spring Boot 用多模型路由把智能客服成本降下来
java·spring boot·后端
威哥爱编程1 小时前
【AI全栈12-02】Spring Boot 跑通第一个 AI 对话接口:HR 政策问答机器人实战
java·spring boot·后端
Bazingga1 小时前
Spring AI 还是 LangChain4j?我用同一套业务代码跑了一遍,答案出乎意料
后端
焦玉全1 小时前
动态代理学习笔记:JDK 与 CGLIB 的完整代码流程
后端
汝吾此便安好1 小时前
Axum 中间件执行顺序
后端
徐小黑ACG2 小时前
Golang 基础06 接口interface
开发语言·后端·golang
三小河2 小时前
从 Markdown 到 Generative UI:AI 如何从“生成答案”进化到“生成界面”?
前端·人工智能·后端
韩振方3 小时前
为什么加了 sudo,写配置文件还是 Permission denied?
后端