首页
留言板
友链
统计
推荐
百度一下
腾讯视频
Search
1
芯宇宙Excel仓库管理系统 三种数据库 | 三种网络部署模式
49 阅读
2
Excel二维码标签插件 固定资产/设备铭牌/合格证批量生成工具支持Excel、WPS,一键批量生成千百个二维码、条形码标签
21 阅读
3
鞋服小店没有大单量,怎么找到低价寄快递渠道?
20 阅读
4
别再花高价寄快递!鞋服商家专属低价寄件小程序,全国 3 元起、大件 20kg 仅 30 元,亲测靠谱
17 阅读
5
散户商家低价寄快递渠道,鞋服发货专用寄件小程序
14 阅读
默认分类
资讯
技术
VBA
ASP
PHP
ASP.Core
分享
鞋运宝
VPS
阿里云
腾讯云
系统
windows
Linux
日常
登录
Search
标签搜索
寄快递
鞋服商家低价寄快递
低价寄快递渠道
VBA
鞋运宝
低价快递小程序
得物快递
EXCEL标签生成插件
鞋服电商
BootstrapBlazor
ASP.Core
芯宇宙技术社
累计撰写
13
篇文章
累计收到
2
条评论
首页
栏目
默认分类
资讯
技术
VBA
ASP
PHP
ASP.Core
分享
鞋运宝
VPS
阿里云
腾讯云
系统
windows
Linux
日常
页面
留言板
友链
统计
推荐
百度一下
腾讯视频
搜索到
4
篇与
的结果
2026-07-31
.NET8 Blazor Server + BootstrapBlazor + PostgreSQL + Identity【简易 DDD 架构】
定位:轻量 DDD,不搞重型 CleanArchitecture,适合中小型后台 / 博客系统,避免过度分层、样板代码爆炸原则:Domain 领域层:纯实体、领域事件、常量,无任何外部依赖Application 应用层:业务用例、DTO、接口,依赖 Domain,不依赖 EF/BlazorInfrastructure 基础设施:EF、Postgres、仓储实现、第三方服务Web 客户端层:Blazor Server、UI 组件、页面、Identity 配置、DI 入口Identity 用户实体纳入 Domain;数据库操作统一由仓储封装解决方案结构(4 个类库 + 1 个 Blazor Web 主项目)XyBlog.sln ├── XyBlog.Domain # 【领域层 · 无外部依赖】 │ ├── Entities # 领域实体(聚合根、实体) │ │ ├── AppUser.cs # Identity用户聚合根 │ │ ├── Article.cs │ │ └── Category.cs │ ├── Enums # 领域枚举 │ ├── Interfaces # 仓储【抽象】(重要!抽象放领域,实现放Infra) │ │ ├── IRepository.cs │ │ ├── IArticleRepository.cs │ │ └── IUserRepository.cs │ ├── Constants │ └── DomainEvents # 可选:领域事件 ├── XyBlog.Application # 【应用层 · 业务用例】 │ ├── Dtos # 入参、输出DTO │ │ ├── ArticleDto.cs │ │ └── LoginDto.cs │ ├── Services # 应用服务(业务逻辑,调用领域仓储接口) │ │ ├── Interfaces │ │ │ ├── IArticleAppService.cs │ │ │ └── IAuthAppService.cs │ │ └── Implementations │ │ ├── ArticleAppService.cs │ │ └── AuthAppService.cs │ └── Mappers # 实体 <=> DTO 映射(可选 Mapster) ├── XyBlog.Infrastructure # 【基础设施层 · 技术实现】 │ ├── Data │ │ ├── AppDbContext.cs # EF DbContext (继承IdentityDbContext) │ │ ├── Migrations # EF迁移文件(PostgreSQL) │ │ └── ModelConfigurations # EF实体配置 │ ├── Repositories # 仓储实现,实现Domain定义的IXXXRepository │ │ ├── EfRepository.cs │ │ └── ArticleRepository.cs │ ├── Extensions # Infra层DI扩展 │ └── Services # 第三方实现:文件存储、邮件服务等 └── XyBlog.Web # 【表现层 Blazor Server + BootstrapBlazor + Identity】 ├── Program.cs # 统一注册所有层服务 ├── App.razor ├── appsettings.json ├── wwwroot ├── Pages # Blazor路由页面 │ ├── Auth/ │ ├── Admin/ │ └── Index.razor ├── Shared │ ├── MainLayout.razor │ ├── NavMenu.razor │ └── Components/ # BootstrapBlazor封装组件 ├── Extensions # Web层扩展方法 └── Utils项目依赖关系(严格单向,DDD 核心约束,不能反向引用)XyBlog.Web ├─> XyBlog.Application └─> XyBlog.Infrastructure XyBlog.Application └─> XyBlog.Domain XyBlog.Infrastructure └─> XyBlog.Domain XyBlog.Domain ⚠️ 【零外部业务依赖】禁止引用 Application / Infrastructure / Web1. NuGet 包引用规划XyBlog.Domain只需要基础库,不要引入 EF、Identity UI<PackageReference Include="Microsoft.AspNetCore.Identity" Version="8.0.x" />XyBlog.Application<ProjectReference Include="..\XyBlog.Domain\XyBlog.Domain.csproj" /> <!-- 可选 Mapster 对象映射 --> <PackageReference Include="Mapster" Version="7.4.x" />XyBlog.Infrastructure<ProjectReference Include="..\XyBlog.Domain\XyBlog.Domain.csproj" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.x" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.x" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.x" />XyBlog.Web(Blazor Server 主项目)<ProjectReference Include="..\XyBlog.Application\XyBlog.Application.csproj" /> <ProjectReference Include="..\XyBlog.Infrastructure\XyBlog.Infrastructure.csproj" /> <PackageReference Include="BootstrapBlazor" /> <PackageReference Include="BootstrapBlazor.FontAwesome" /> <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.x" />关键代码样例① XyBlog.Domain/Entities/AppUser.cs(聚合根,扩展 Identity) using Microsoft.AspNetCore.Identity; namespace XyBlog.Domain.Entities; public class AppUser : IdentityUser<Guid> { public string NickName { get; set; } = string.Empty; public string Avatar { get; set; } = string.Empty; public DateTime CreateTime { get; set; } = DateTime.UtcNow; } ② XyBlog.Domain/Interfaces/IArticleRepository.cs(仓储抽象【放在领域!】) csharp 运行 using XyBlog.Domain.Entities; namespace XyBlog.Domain.Interfaces; public interface IArticleRepository { Task<List<Article>> GetListAsync(); Task<Article?> GetByIdAsync(Guid id); Task AddAsync(Article article); Task UpdateAsync(Article article); Task DeleteAsync(Guid id); }③ XyBlog.Infrastructure/Repositories/ArticleRepository.cs(实现仓储) using Microsoft.EntityFrameworkCore; using XyBlog.Domain.Entities; using XyBlog.Domain.Interfaces; using XyBlog.Infrastructure.Data; namespace XyBlog.Infrastructure.Repositories; public class ArticleRepository : IArticleRepository { private readonly AppDbContext _dbContext; public ArticleRepository(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<List<Article>> GetListAsync() { return await _dbContext.Articles.ToListAsync(); } public async Task<Article?> GetByIdAsync(Guid id) { return await _dbContext.Articles.FindAsync(id); } public async Task AddAsync(Article article) { await _dbContext.Articles.AddAsync(article); await _dbContext.SaveChangesAsync(); } public async Task UpdateAsync(Article article) { _dbContext.Articles.Update(article); await _dbContext.SaveChangesAsync(); } public async Task DeleteAsync(Guid id) { var entity = await GetByIdAsync(id); if (entity != null) { _dbContext.Articles.Remove(entity); await _dbContext.SaveChangesAsync(); } } }④ XyBlog.Application/Services/Interfaces/IArticleAppService.cs using XyBlog.Application.Dtos; namespace XyBlog.Application.Services.Interfaces; public interface IArticleAppService { Task<List<ArticleDto>> GetAllAsync(); Task<ArticleDto?> GetAsync(Guid id); Task CreateAsync(ArticleDto dto); }⑤ XyBlog.Application/Services/Implementations/ArticleAppService.cs应用服务:协调仓储、转换 DTO,不写原生 EF 查询 using Mapster; using XyBlog.Application.Dtos; using XyBlog.Application.Services.Interfaces; using XyBlog.Domain.Entities; using XyBlog.Domain.Interfaces; namespace XyBlog.Application.Services.Implementations; public class ArticleAppService : IArticleAppService { private readonly IArticleRepository _articleRepo; public ArticleAppService(IArticleRepository articleRepo) { _articleRepo = articleRepo; } public async Task<List<ArticleDto>> GetAllAsync() { var list = await _articleRepo.GetListAsync(); return list.Adapt<List<ArticleDto>>(); } public async Task<ArticleDto?> GetAsync(Guid id) { var entity = await _articleRepo.GetByIdAsync(id); return entity?.Adapt<ArticleDto>(); } public async Task CreateAsync(ArticleDto dto) { var article = dto.Adapt<Article>(); await _articleRepo.AddAsync(article); } }⑥ XyBlog.Infrastructure/Data/AppDbContext.cs using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using XyBlog.Domain.Entities; namespace XyBlog.Infrastructure.Data; public class AppDbContext : IdentityDbContext<AppUser, IdentityRole<Guid>, Guid> { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Article> Articles { get; set; } public DbSet<Category> Categories { get; set; } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // Postgres 小写表名 foreach (var entityType in builder.Model.GetEntityTypes()) { entityType.SetTableName(entityType.DisplayName().ToLower()); } } }Program.cs 注册代码(XyBlog.Web)建议封装扩展 InfrastructureModule.cs、ApplicationModule.cs 统一批量注册,保持 Program 整洁 using BootstrapBlazor; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using XyBlog.Domain.Entities; using XyBlog.Infrastructure.Data; var builder = WebApplication.CreateBuilder(args); // 1. Postgres EF var conn = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext<AppDbContext>(opt => opt.UseNpgsql(conn)); // 2. Identity builder.Services.AddDefaultIdentity<AppUser>(options => { options.Password.RequiredLength = 6; options.Password.RequireDigit = false; options.Password.RequireNonAlphanumeric = false; }) .AddRoles<IdentityRole<Guid>>() .AddEntityFrameworkStores<AppDbContext>(); // 3. 批量注册仓储、应用服务(推荐封装扩展方法) // builder.Services.AddInfrastructureServices(); // builder.Services.AddApplicationServices(); // 4. Blazor Server builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); // 5. BootstrapBlazor builder.Services.AddBootstrapBlazor(); var app = builder.Build(); // 自动迁移 using var scope = app.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); db.Database.Migrate(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.Run();Blazor 页面调用示例 Pages/Admin/ArticleList.razor @attribute [Authorize(Roles = "Admin")] @inject IArticleAppService ArticleAppService @inject ToastService ToastService <PageTitle>文章管理</PageTitle> <Table TItem="ArticleDto" DataSource="_articles" IsStriped="true"> <TableColumn Header="标题" Field="Title" /> <TableColumn Header="操作"> <Template Context="row"> <Button Color="Color.Primary" OnClick="() => Edit(row)">编辑</Button> </Template> </TableColumn> </Table> @code { private List<ArticleDto> _articles = []; protected override async Task OnInitializedAsync() { _articles = await ArticleAppService.GetAllAsync(); } private async Task Edit(ArticleDto dto) { // 打开弹窗编辑 } }5. 架构重要规范(避坑重点)仓储接口定义在 Domain,实现在 Infrastructure(DDD 经典,依赖倒置)Blazor 页面只依赖 Application 层 IAppService,绝对不要直接注入 IRepository、DbContextDomain 实体只包含属性、领域行为,禁止注入服务、不能引用 EFDTO 统一放在 Application,领域实体禁止直接返回前端Blazor Server 所有注入服务保持 Scoped,不要 SingletonEF Migrations 放在 Infrastructure 项目!shellAdd-Migration InitDb -Project XyBlog.Infrastructure -StartupProject XyBlog.WebUpdate-Database -Project XyBlog.Infrastructure -StartupProject XyBlog.Web可选升级方向引入MediatR,应用层改用 CQRS(查询 / 命令分离)增加领域事件增加单元测试项目 XyBlog.Application.UnitTests,Mock 仓储测试业务逻辑
2026年07月31日
3 阅读
1 评论
1 点赞
2026-07-31
.NET 8 Blazor Server + BootstrapBlazor + PostgreSQL + ASP.NET Core Identity 标准目录结构
说明:采用简约分层(适合单人 / 中小项目,不重型 DDD,避免过度拆分)技术栈:.NET 8 Blazor ServerBootstrapBlazor UI 组件库EntityFramework Core + PostgreSQL(Npgsql)ASP.NET Core Identity 身份认证(用户 / 角色 / 登录、权限)依赖注入、Repository 模式可选(下面提供两套方案:【最简版】、【分层整洁版】)方案一:整洁分层推荐(优先使用)项目名称:XyBlog.Web(单项目分层,Blazor Server 常用,不用拆多解决方案项目)XyBlog.Web/ ├── App.razor # 根组件 ├── Program.cs # 程序入口,服务注册(Identity、BootstrapBlazor、EF、Postgres) ├── appsettings.json # Postgres连接字符串、Identity配置 ├── appsettings.Development.json ├── Properties/ │ └── launchSettings.json ├── wwwroot/ # 静态资源 │ ├── css/ │ ├── js/ │ └── favicon.ico ├── Data/ # EF Core & Identity 数据层 │ ├── Migrations/ # EF迁移文件(PostgreSQL) │ ├── AppDbContext.cs # 数据库上下文,继承IdentityDbContext<TUser> │ ├── AppIdentityUser.cs # 扩展Identity用户实体(自定义字段:头像、昵称等) │ └── SeedData.cs # 初始化超级管理员种子数据 ├── Models/ # 实体、DTO、ViewModel │ ├── Entities/ # 业务数据库实体(文章、分类等) │ │ ├── Article.cs │ │ └── Category.cs │ └── Dtos/ # 页面交互DTO,不和数据库实体耦合 │ ├── ArticleDto.cs │ └── LoginDto.cs ├── Services/ # 业务逻辑层(接口+实现,DI注入) │ ├── Interfaces/ │ │ ├── IArticleService.cs │ │ └── ICommonService.cs │ └── Implementations/ │ ├── ArticleService.cs │ └── CommonService.cs ├── Repositories/【可选】如需ORM隔离可增加 │ ├── IRepository.cs │ └── EfRepository.cs ├── Shared/ # 全局共享Razor组件 │ ├── MainLayout.razor # 主布局(BootstrapBlazor布局容器、侧边菜单) │ ├── NavMenu.razor # 侧边导航 │ ├── LoginLayout.razor # 登录页独立布局 │ └── Components/ # 公共小组件(弹窗、分页、上传组件) │ ├── ConfirmDialog.razor │ └── AvatarUpload.razor ├── Pages/ # Blazor页面(路由页面) │ ├── Index.razor # 首页 │ ├── Error.razor │ ├── Auth/ # Identity认证页面 │ │ ├── Login.razor │ │ ├── Register.razor │ │ ├── Logout.razor │ │ └── Profile.razor # 用户资料修改 │ ├── Admin/ # 后台管理页面(增加Authorize权限标签) │ │ ├── ArticleList.razor │ │ ├── ArticleEdit.razor │ │ └── UserManage.razor │ └── Articles/ # 前台文章页面 │ ├── ArticleDetail.razor │ └── ArticleList.razor ├── Extensions/ # 扩展方法 │ ├── ServiceCollectionExtensions.cs # 统一注册:BootstrapBlazor、Identity、DbContext │ └── StringExtensions.cs ├── Utils/ # 工具类 │ ├── FileHelper.cs │ └── DateTimeHelper.cs └── Constants/ # 常量、角色名称 └── RoleNames.cs // Admin, Member方案二:极简结构(快速原型开发) XyBlog.Web/ ├── Data/ │ ├── Migrations/ │ ├── AppDbContext.cs │ └── AppIdentityUser.cs ├── Models/ ├── Shared/ ├── Pages/ │ ├── Auth/ │ ├── Admin/ │ └── Index.razor ├── Utils/ ├── wwwroot ├── Program.cs 关键核心代码片段NuGet 必备包<!-- EF PostgreSQL --> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.x" /> <!-- Identity --> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.x" /> <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.x" /> <!-- BootstrapBlazor --> <PackageReference Include="BootstrapBlazor" Version="最新稳定版" /> <PackageReference Include="BootstrapBlazor.FontAwesome" Version="最新稳定版" /> 扩展 Identity 用户实体 AppIdentityUser.csusing Microsoft.AspNetCore.Identity; namespace XyBlog.Web.Data; public class AppIdentityUser : IdentityUser<Guid> { public string NickName { get; set; } = string.Empty; public string Avatar { get; set; } = string.Empty; public DateTime CreateTime { get; set; } = DateTime.Now; } 数据库上下文 AppDbContext.cs using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using XyBlog.Web.Models.Entities; namespace XyBlog.Web.Data; public class AppDbContext : IdentityDbContext<AppIdentityUser, IdentityRole<Guid>, Guid> { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Article> Articles { get; set; } public DbSet<Category> Categories { get; set; } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // PostgreSQL 小写表名(推荐) foreach (var entity in builder.Model.GetEntityTypes()) { entity.SetTableName(entity.DisplayName().ToLower()); } } } appsettings.json Postgres 连接串 "ConnectionStrings": { "DefaultConnection": "Host=127.0.0.1;Port=5432;Database=xyblog;Username=postgres;Password=123456" } Program.cs 核心注册(重点:Blazor Server + Identity + BootstrapBlazor + Postgres)using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using BootstrapBlazor; using XyBlog.Web.Data; var builder = WebApplication.CreateBuilder(args); // Add DbContext PostgreSQL var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext<AppDbContext>(options => options.UseNpgsql(connectionString)); // Identity 配置 builder.Services.AddDefaultIdentity<AppIdentityUser>(options => { options.Password.RequireDigit = false; options.Password.RequiredLength = 6; options.Password.RequireNonAlphanumeric = false; }) .AddRoles<IdentityRole<Guid>>() .AddEntityFrameworkStores<AppDbContext>(); // Blazor Server builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); // BootstrapBlazor builder.Services.AddBootstrapBlazor(); // 业务服务批量注入 // builder.Services.AddScoped<IArticleService, ArticleService>(); var app = builder.Build(); // 自动迁移+初始化种子(开发环境可选) using var scope = app.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); dbContext.Database.Migrate(); // SeedData.Initialize(scope.ServiceProvider); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.Run();_Imports.razor 全局引用@using BootstrapBlazor @using BootstrapBlazor.Components @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Identity @inject AuthenticationStateProvider AuthStateProvider 权限控制示例页面 Admin/UserManage.razor@attribute [Authorize(Roles = "Admin")] @inject UserManager<AppIdentityUser> UserManager配套操作命令(EF 迁移 PostgreSQL) # 创建迁移 Add-Migration InitDb # 应用迁移到Postgres数据库 Update-Database 架构小建议Blazor Server 不要把业务逻辑写在 razor 页面代码块,全部抽入Services,方便单元测试;DTO 与数据库实体分离,页面绝对不直接返回数据库实体;Identity 尽量使用 Guid 主键(默认 string 可以改成 Guid,Postgres 存储更友好,上面代码已支持);BootstrapBlazor 弹窗、表格、分页统一封装到 Shared/Components 复用;Blazor Server 注意Scoped 服务生命周期,不要使用单例 DbContext。
2026年07月31日
3 阅读
0 评论
0 点赞
2026-07-29
Blazor 完整学习教程(分阶段,适配 BootstrapBlazor 企业开发)
Blazor 开发指南一、基础认知:Blazor 两种模式区别1. Blazor Server(你当前项目使用)运行机制:UI 在服务器渲染,客户端仅维持 SignalR 长连接优点:上手简单无需单独后端接口可直接注入 Identity、EF Core缺点:并发高时服务器压力大断网页面无交互适用场景:内部管理系统后台系统ERP 系统进销存系统(你的业务场景)2. Blazor WebAssembly(Wasm)运行机制:.NET 运行时下载到浏览器,前端独立运行优点:离线可用服务器负载低缺点:首次加载慢不能直接访问数据库,必须写后端 API适用场景:面向公网的轻量化页面注意:你的项目是 Blazor Server + BootstrapBlazor + Identity,教程全部围绕 Server 模式讲解。二、环境搭建(零基础)1. 必备工具Visual Studio 2022(17.5+)安装组件:ASP.NET 和 Web 开发.NET SDK.NET 8(推荐,长期支持).NET 92. NuGet 包配置<!-- BootstrapBlazor UI组件库 --> <PackageReference Include="BootstrapBlazor" Version="8.3.0" /> <!-- 身份认证 --> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" /> <!-- 数据库EF Core --> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />3. 创建项目步骤VS 创建新项目 → ASP.NET Core 空 / Blazor Server 应用配置项目名称,框架选择 .NET 8集成 BootstrapBlazor 关键两步:// Program.cs builder.Services.AddBootstrapBlazor();<!-- App.razor --> <BootstrapBlazorRoot> <Routes /> </BootstrapBlazorRoot>三、Blazor 核心基础语法(Razor 组件)1. 组件文件结构 .razor@page "/路由地址" <!-- 页面路由 --> @inject 类型 变量名 <!-- 依赖注入 --> @using 命名空间 <!-- 引入类 --> @code {} <!-- C# 逻辑、属性、方法 --> <!-- HTML 混合 C# 代码 -->2. 数据绑定单向绑定:<p>用户名:@UserName</p> @code { private string UserName = "admin"; }双向绑定(登录页使用):<BootstrapInput @bind-Value="LoginModel.UserName" PlaceHolder="账号"/>3. 条件渲染@if (IsLogin) { <span>欢迎 @UserName</span> } else { <a href="/login">去登录</a> } @code { private bool IsLogin = false; private string UserName = ""; }4. 循环渲染@foreach (var item in UserList) { <Card Title="@item.UserName"></Card> } @code { private List<User> UserList = new(); }5. 事件绑定<Button OnClick="SubmitLogin">登录</Button> @code { private async Task SubmitLogin() { // 登录逻辑 } }四、依赖注入(项目核心)1. 三种生命周期AddScoped:每次页面请求创建实例(最常用,AuthHelper、DbContext)AddSingleton:全局单例,程序启动创建(配置、通用工具)AddTransient:每次注入都新建(轻量工具类)2. Program.cs 注册服务// 作用域服务,每个用户会话独立实例 builder.Services.AddScoped<AuthHelper>(); // BootstrapBlazor全局UI服务 builder.Services.AddBootstrapBlazor(); // Identity认证 builder.Services.AddDefaultIdentity<AppUser>() .AddRoles<IdentityRole>() .AddEntityFrameworkStores<AppDbContext>();3. 页面/组件注入使用@inject AuthHelper AuthHelper @inject ToastService Toast @inject NavigationManager Nav五、表单验证(登录页面 ValidateForm)1. 数据注解验证public class LoginDto { [Required(ErrorMessage = "请输入账号")] public string UserName { get; set; } = ""; [Required(ErrorMessage = "请输入密码")] public string Password { get; set; } = ""; }2. BootstrapBlazor ValidateForm 完整模板<ValidateForm Model="@LoginModel" OnValidSubmit="SubmitLogin"> <BootstrapInput @bind-Value="LoginModel.UserName" PlaceHolder="账号"/> <BootstrapPassword @bind-Value="LoginModel.Password" PlaceHolder="密码"/> <Button ButtonType="ButtonType.Submit" Text="登录"/> </ValidateForm>六、ASP.NET Core Identity 身份认证(AuthHelper)1. 核心三个类UserManager:用户增删改查、注册、改密码SignInManager:登录、退出、密码校验、账号锁定RoleManager:角色管理(管理员/普通用户)2. 封装通用 AuthHelperpublic class AuthHelper { private readonly SignInManager<AppUser> _signInManager; private readonly UserManager<AppUser> _userManager; public AuthHelper(SignInManager<AppUser> signInManager, UserManager<AppUser> userManager) { _signInManager = signInManager; _userManager = userManager; } // 登录 public async Task<SignInResult> LoginAsync(string username, string password, bool remember) { return await _signInManager.PasswordSignInAsync( username, password, remember, lockoutOnFailure: true); } // 注册+分配角色 public async Task<IdentityResult> RegisterAsync(AppUser user, string password) { var res = await _userManager.CreateAsync(user, password); if (res.Succeeded) await _userManager.AddToRoleAsync(user, "User"); return res; } // 退出登录 public async Task LogoutAsync() { await _signInManager.SignOutAsync(); } }3. 获取当前登录用户信息@inject AuthenticationStateProvider AuthStateProvider @inject UserManager<AppUser> UserManager @code { private AppUser? CurrentUser; protected override async Task OnInitializedAsync() { var state = await AuthStateProvider.GetAuthenticationStateAsync(); var user = state.User; if (user.Identity != null && user.Identity.IsAuthenticated) { CurrentUser = await UserManager.FindByNameAsync(user.Identity.Name); } } }七、BootstrapBlazor UI 组件库教程1. 必备基础组件BootstrapInput/BootstrapPassword:输入框、密码框(登录页)ValidateForm:表单校验ToastService:轻量弹窗提示(登录成功/失败)Card:卡片容器(登录布局)Dialog:弹窗窗口Table:数据表格(进销存、库存列表)2. Toast 弹窗使用前置条件:Program.cs 注册 AddBootstrapBlazor()App.razor 全局包裹 <BootstrapBlazorRoot>@inject ToastService Toast @code { // 成功提示 await Toast.Success("登录成功"); // 错误提示 await Toast.Error("账号密码错误"); }八、路由与页面跳转1. 页面路由声明@page "/login" @page "/user/list"2. 代码跳转 NavigationManager@inject NavigationManager Nav // 跳转首页 Nav.NavigateTo("/"); // 强制刷新页面(刷新登录认证状态) Nav.NavigateTo("/", forceLoad: true);九、分层架构(DDD,企业级规范)推荐四层标准结构:1. Domain.Abstractions - 接口定义 IAuthHelper - 数据库实体 AppUser 2. Application.Services - 业务逻辑实现 AuthHelper 3. Infrastructure - EF Core 数据库上下文 - 第三方接口对接 4. WebApp.Server - Blazor 页面、组件、布局 - Program 入口十、实战完整案例:登录功能@page "/login" @using BootstrapBlazor.Components @using System.ComponentModel.DataAnnotations @using BootstrapBlazorApp1.Server.Services @inject NavigationManager Nav @inject ToastService Toast @inject AuthHelper AuthHelper @layout TutorialsLoginLayout <div class="login-item login-item-gitee"> <div class="login-header"> <span>登录</span> </div> <ValidateForm Model="@LoginModel" OnValidSubmit="SubmitLogin"> <BootstrapInput TValue="string" @bind-Value="LoginModel.UserName" ShowLabel="false" PlaceHolder="请输入账号" IsAutoFocus="true" /> <BootstrapPassword TValue="string" @bind-Value="LoginModel.Password" ShowLabel="false" PlaceHolder="请输入密码" /> <Checkbox @bind-Checked="LoginModel.RememberMe" Text="记住我" /> <Button Text="登录" Color="Color.Primary" ButtonType="ButtonType.Submit" Class="mt-3 w-100"></Button> </ValidateForm> </div> @code { public class LoginDto { [Required(ErrorMessage = "请输入账号")] public string UserName { get; set; } = ""; [Required(ErrorMessage = "请输入密码")] public string Password { get; set; } = ""; public bool RememberMe { get; set; } } private LoginDto LoginModel { get; set; } = new(); private async Task SubmitLogin(EditContext context) { var result = await AuthHelper.LoginAsync( LoginModel.UserName, LoginModel.Password, LoginModel.RememberMe); if (result.Succeeded) { await Toast.Success("登录成功"); Nav.NavigateTo("/", true); } else if (result.IsLockedOut) { await Toast.Error("账号已锁定,请稍后重试"); } else { await Toast.Error("账号或密码错误"); } } }十一、官方学习文档(权威参考)微软官方 Blazor 文档: https://learn.microsoft.com/zh-cn/aspnet/core/blazor/BootstrapBlazor 官方文档: https://www.blazor.zone/docsBootstrapBlazor 在线演示: https://blazor.app1.es/十二、学习路线(循序渐进)阶段 1:基础语法Razor 语法双向绑定事件处理循环、条件渲染阶段 2:核心概念依赖注入生命周期方法(OnInitializedAsync)阶段 3:UI 组件库BootstrapBlazor 组件库表单校验Toast 弹窗阶段 4:身份认证Identity 身份认证登录/注册功能角色权限管理
2026年07月29日
5 阅读
0 评论
1 点赞
2026-07-29
.NET8 Blazor Server + BootstrapBlazor + PostgreSQL + Identity,同时兼容后续小程序 API复用业务代码,完全解耦
一、整体解决方案项目总览基于DDD 领域驱动设计分层架构,技术栈:.NET8 + Blazor Server + BootstrapBlazor + PostgreSQL + AspNetCore Identity + Redis缓存,支持后台管理、微信小程序 API 业务代码完全复用,低耦合易扩展。plaintextERP.Solution├─ 1. ERP.Domain.Shared # 领域共享层(最底层,无外部依赖)├─ 2. ERP.Application # 应用业务逻辑层├─ 3. ERP.Infrastructure # 基础设施实现层├─ 4. ERP.Web.Server # Blazor Server后台管理端(BootstrapBlazor)├─ 5. ERP.Web.Api # 小程序/移动端接口项目(可选扩展)测试项目├─ ERP.UnitTest # 单元测试(领域、应用层逻辑)└─ ERP.IntegrationTest # 集成测试(数据库完整流程)二、各分层完整目录、职责、依赖关系ERP.Domain.Shared 领域共享层定位:整个解决方案最底层,仅存放纯粹业务定义,无数据库、UI、第三方 SDK 依赖,所有上层项目统一引用复用。项目依赖:无任何内部项目引用plaintextERP.Domain.Shared├─ Entities # 数据库实体(PG表对应实体)│ ├─ Goods.cs│ ├─ Stock.cs│ ├─ SaleOrder.cs│ ├─ PurchaseOrder.cs│ ├─ Customer.cs│ └─ Supplier.cs├─ Abstractions # 抽象接口:仓储、领域服务(仅定义,不实现)│ ├─ IStockRepository.cs│ ├─ ISaleOrderRepository.cs│ ├─ IPurchaseRepository.cs│ └─ IUserRepository.cs├─ Dtos # 全局传输对象(入参/返回值,前后端、小程序通用)│ ├─ Stock│ │ ├─ StockQueryDto.cs│ │ └─ StockResultDto.cs│ ├─ Sale│ │ ├─ SaleCreateDto.cs│ │ └─ SaleListDto.cs│ └─ Purchase│ ├─ PurchaseInDto.cs│ └─ PurchaseResultDto.cs├─ Enums # 业务枚举:单据状态、库存变动类型、订单来源│ ├─ OrderStatus.cs│ └─ StockChangeType.cs├─ DomainServices # 纯领域计算逻辑(无DB依赖,单元测试核心)│ └─ StockCalculateService.cs # 库存扣减、加权平均成本计算└─ Common # 全局通用工具、统一返回模型├─ ApiResult.cs # 全局接口统一返回格式└─ ConstConfig.cs # 业务常量ERP.Application 应用业务逻辑层定位:封装全部业务流程,Blazor 后台、小程序 API 统一调用此层,业务代码仅编写一次,实现代码复用。项目依赖:仅引用 ERP.Domain.SharedplaintextERP.Application├─ Abstractions # 应用服务抽象接口│ ├─ IStockAppService.cs│ ├─ ISaleAppService.cs│ ├─ IPurchaseAppService.cs│ └─ IAuthAppService.cs # 登录、注册、权限校验业务接口├─ Services # 应用服务接口实现│ ├─ Stock│ │ └─ StockAppService.cs│ ├─ Sale│ │ └─ SaleAppService.cs│ ├─ Purchase│ │ └─ PurchaseAppService.cs│ └─ Auth│ ├─ AuthAppService.cs│ └─ AuthHelper.cs # 账号密码、JWT工具封装└─ Common└─ ServiceBase.cs # 应用服务基类,封装公共方法ERP.Infrastructure 基础设施层定位:所有外部依赖的实现层,隔离数据库、缓存、第三方 SDK,上层业务仅依赖抽象接口,不感知具体实现。项目依赖:引用 ERP.Domain.Shared + ERP.ApplicationplaintextERP.Infrastructure├─ Data│ ├─ AppDbContext.cs # PostgreSQL EF Core数据库上下文│ ├─ Migrations # EF数据库迁移文件│ └─ SeedData # 系统初始化种子数据(管理员账号、商品分类)├─ Repositories # 仓储接口实现,实现Domain层IxxxRepository│ ├─ StockRepository.cs│ ├─ SaleOrderRepository.cs│ ├─ PurchaseRepository.cs│ └─ UserRepository.cs├─ Identity # AspNetCore Identity 用户角色扩展│ └─ AppUser.cs├─ Cache # Redis缓存实现(库存缓存、基础数据缓存)│ └─ RedisCacheService.cs└─ ExternalServices # 第三方外部服务对接├─ WeChatMini # 微信小程序SDK(登录、支付)└─ ExcelExport # Excel单据导出工具类ERP.Web.Server Blazor Server 后台管理端定位:企业后台管理系统,基于 BootstrapBlazor 组件库,提供完整进销存可视化页面、权限管理。项目依赖:引用 ERP.Domain.Shared + ERP.Application + ERP.InfrastructureplaintextERP.Web.Server├─ Components # 页面、公共组件(Blazor核心目录)│ ├─ Pages # 所有业务页面│ │ ├─ Account # 账号相关页面│ │ │ ├─ Login.razor│ │ │ └─ Register.razor│ │ ├─ Stock # 库存管理页面│ │ │ ├─ StockIndex.razor│ │ │ └─ StockWarning.razor│ │ ├─ Sale # 销售开单、销售记录│ │ ├─ Purchase # 采购入库、采购单据│ │ ├─ CustomerSupplier # 客户、供应商档案管理│ │ └─ System # 系统设置、角色权限、参数配置│ ├─ Layouts # 全局布局母版│ │ ├─ MainLayout.razor # 后台主布局(侧边栏菜单+顶部导航)│ │ ├─ LoginLayout.razor # 登录页面独立布局│ │ └─ AuthorizeLayout.razor # 全局权限拦截布局│ ├─ Shared # 全局复用弹窗、公共组件│ │ ├─ StockDialog.razor│ │ └─ SaleOrderDialog.razor│ └─ App.razor # 全局外壳,内置BootstrapBlazorRoot根组件├─ wwwroot # 静态资源文件(仅Web.Server存放静态资源)│ ├─ css # 编译后样式文件│ ├─ scss # 原始SCSS样式源码│ ├─ images # 图片资源│ │ ├─ bg # 页面背景图│ │ └─ icon # 功能图标│ └─ js # 自定义JS脚本├─ Services # Blazor页面专属辅助服务│ └─ GlobalStateService.cs # 全局登录用户状态管理└─ Program.cs # 项目入口:DI依赖注入、中间件、BootstrapBlazor配置ERP.Web.Api 小程序 / 移动端接口项目(可选)定位:对外提供 RESTful 接口,供微信小程序、APP、H5 调用,底层完全复用 Domain、Application、Infrastructure,无需重复编写业务逻辑。项目依赖:引用 ERP.Domain.Shared + ERP.Application + ERP.InfrastructureplaintextERP.Web.Api├─ Controllers│ ├─ MiniProgramController.cs # 小程序登录、商品查询、下单接口│ └─ StockController.cs└─ Program.cs # API项目DI、中间件、JWT鉴权配置三、测试项目目录ERP.UnitTest 单元测试仅测试领域层、应用层纯逻辑,使用 Moq 模拟仓储,无需真实数据库。plaintextERP.UnitTest├─ DomainTests # 领域逻辑测试(库存加权成本计算)└─ ApplicationTests # 应用服务业务流程测试(销售、采购、登录逻辑)ERP.IntegrationTest 集成测试使用内存数据库完成完整链路测试,验证仓储、数据库、业务流程联动。plaintextERP.IntegrationTest├─ Factory # TestServer测试工厂,统一构建测试服务容器└─ BusinessTests # 完整业务流程集成测试(采购入库、销售出库全链路)四、项目依赖单向约束(禁止反向依赖)markdownERP.Domain.Shared:无内部项目引用(最底层)ERP.Application:仅引用 ERP.Domain.SharedERP.Infrastructure:引用 ERP.Domain.Shared + ERP.ApplicationERP.Web.Server / ERP.Web.Api:引用全部下层三层测试项目:引用 ERP.Domain.Shared + ERP.Application + ERP.Infrastructure五、分层架构核心优势业务代码完全复用库存、销售、账号登录逻辑仅编写一次,Blazor 后台、微信小程序 API 同步复用,无重复代码,降低维护成本。职责单一,分层解耦数据库操作仅在 Infrastructure;业务流程、业务规则仅在 Application/Domain;UI 页面、交互逻辑仅在 Blazor Web 项目。单元测试友好Domain、Application 层无数据库、UI 强耦合依赖,可快速编写单元测试,覆盖库存计算、单据核心业务逻辑。横向扩展能力强后续新增 APP、H5、第三方对接接口,仅需新建 WebApi 项目,底层领域、业务、基础设施代码无需改动。完美适配 BootstrapBlazor所有页面、布局、弹窗组件统一规范存放,静态资源集中管理,适配 Blazor Server 组件化开发模式。六、开发规范 & 避坑清单禁止在 Blazor 页面直接编写库存计算、单据业务逻辑,全部抽离至 Application 应用服务;禁止页面直接操作 DbContext 数据库上下文,所有数据操作通过 Domain 层仓储接口完成;数据库实体统一存放于 Domain.Shared,Web/Api 项目禁止重复定义实体类;图片、样式、JS 静态资源仅允许放置在 ERP.Web.Server/wwwroot,其他项目不存放静态资源;所有入参、返回 DTO 统一在 Domain.Shared 定义,后台页面、小程序接口共用一套传输模型;外部第三方依赖(数据库、Redis、微信 SDK)全部封装在 Infrastructure 层,上层业务仅依赖抽象接口。
2026年07月29日
6 阅读
0 评论
0 点赞