拓冰建站拓冰建站
首页 / 资讯中心 / 正文

XAF Blazor 项目中集成 Web API:共存架构与实战指南

1. 先说结论Blazor 和 Web API 当然能共存1.1 为什么会有这个疑问做了几年 XAFDevExpress eXpressApp Framework开发的朋友应该都遇到过类似的对话项目里已经跑起来一套 XAF Blazor 应用业务对象、权限、审计日志都在里面突然产品提了个需求说要给移动端 App 提供数据接口或者让第三方系统拉取订单信息。这时候第一反应往往是“要不要单独建一个 Web API 项目”紧接着第二反应就是——“Blazor 和 API 到底能不能共存是不是同一个应用里只能选一个”这里我可以直接给答案能共存而且共存得很干净。Blazor Server 的交互模式虽然看起来是“页面里到处是 SignalR 推送”但它本质上是 ASP.NET Core 应用里的一个端点Endpoint不是整个应用的全部。一个 ASP.NET Core 进程里你可以同时拥有 Razor 页面、MVC Controller、最小 API 和 Blazor 组件它们共享同一个 HTTP 管线只是路由各管一段。XAF Blazor 同样如此它只是把你的业务模型托在 Blazor 的壳里底层依然是标准 ASP.NET Core完全可以再挂一层 Web API。1.2 在 XAF Blazor 里挂 API 的典型场景我梳理了实际项目中最常见的几种需求你可以对照看看自己属于哪一种。第一类移动端或外部系统需要访问业务数据。比如 OA 系统要显示项目进度但数据源在 XAF 应用里或者你写了一个小程序需要登录后查询客户列表。这时候 API 是唯一合理方式总不能叫第三方直接连数据库读表那样等于把数据模型和安全策略全暴露了。第二类同一应用内的扩展模块。有些功能用 Blazor 组件做非常别扭比如给运维脚本提供批量导入接口或者给定时任务提供触发入口。你用 Controller 写一个 10 行的 POST 接口比在页面里加一个隐藏按钮再靠自动化点击要靠谱得多。第三类为前后端分离做铺垫。公司战略上打算将来把 UI 换成桌面端或原生 App但后端业务规则短期内不想重写。先在现有 XAF Blazor 应用里把 API 稳定下来UI 层后续随意替换这是成本最低的演进路径。2. 方案选型先别急着多开一个项目2.1 三种常见做法集成 Web API 不是只有一条路我见过不少团队一上来就建独立 API 项目结果权限规则复制了一遍又一遍最后维护成本居高不下。这里先把三种主流做法摊开看。方案做法复杂度典型适用场景A在现有 XAF Blazor 项目里挂 Web API低内部接口、小规模对外服务、快速交付B独立 Web API 项目引用业务类库中团队分工明确API 需要独立部署扩缩容C使用官方 Web API Service 模板中高从零开始搭建、API 是产品主业务的场景方案 B 的思路是把业务对象和 XafApplication 代码抽到独立的类库Blazor 项目引用一份API 项目引用另一份。好处是 API 可以独立发布坏处是业务逻辑共享时你很快会发现“权限规则到底写在哪边”成了一个哲学问题而且每次业务模型变更两边都要重新部署验证。方案 C 是 DevExpress 官方提供的 Web API Service 模板适合全新项目。但如果你的业务逻辑已经从 Blazor 项目里长出来了强行迁移到独立模板改动面并不小。你不但要重新配置模块列表、数据库更新逻辑连 ObjectSpaceProvider 的初始化方式可能都要调整。2.2 为什么优先选方案 A我的建议是除非公司明确要求 API 必须部署在单独服务器上否则优先在当前 Blazor 项目里挂 API。原因很实在。第一XAF 的核心价值从来不只是“列表和表单界面”而是那套业务对象模型、权限体系、审计机制和验证规则。API 如果另起炉灶等于把业务模型硬生生切开。你写一个查询客户列表的接口很简单但你得保证这个 API 不会读出没有权限的数据不会绕过必填字段验证不会跳过审计记录——这些东西在方案 A 里天然继承。第二部署运维省事。一个进程里同时跑 UI 和 API发布时只发布一个站点环境变量、数据库连接串都只有一份。我见过独立部署的两个项目因为各自的 appsettings.json 里配置的数据库连接串不一致排查了整整半天才发现是环境配置漂移。第三调试体验好。接口和页面在同一个进程里你可以在 Controller 里直接打断点也能看到 XAF 的 ObjectSpace 上下文。如果分开两个项目来回切换调试端口、同步断点就已经消耗了不少精力。方案 A 当然也有缺点比如 API 流量大了之后Blazor 的 SignalR 长连接可能会和 API 请求争抢线程资源。但这是后续演进要考虑的事对大多数业务系统来说按日活几千、API 每秒几十次的负载一个进程完全顶得住。3. 实操把 Web API 挂进现有 XAF Blazor 项目3.1 安装 NuGet 包与版本锁定我以当前主流的 DevExpress 版本v23.x / v24.xAPI 命名基本一致为例。在现有 XAF Blazor 项目里需要新增两个包DevExpress.ExpressApp.WebApiDevExpress.ExpressApp.WebApi.Swashbuckle第二个包不只是 Swagger 生成器它内部同时引用了 WebApi 相关依赖建议两个都装避免缺依赖。这里有一个重要提醒DevExpress 包的版本必须和你项目里其他 DevExpress 包保持一致。如果 Blazor 项目已经在用 23.2.5那么这个新包也要装 23.2.5。混版本是我见过最常见的集成失败原因之一编译时可能不报错运行时会冒出各种离奇异常比如找不到模块、ObjectSpaceProvider 类型不匹配。批量安装 NuGet 包时注意检查版本号别手滑升级到 24.x 导致项目整个无法启动。3.2 Program.cs 中的服务注册打开现有 XAF Blazor 项目的 Program.cs在AddXafBlazor旁边增加AddXafWebApi。我贴一段典型配置var builder WebApplication.CreateBuilder(args); // 原有 Blazor 服务 builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); // 原有 XAF Blazor 应用注册 builder.Services.AddXafBlazor(builder.Configuration, options { options.ApplicationType typeof(MyBlazorApp.Blazor.BlazorApplication); }); // 新增Web API 服务注册 builder.Services.AddXafWebApi(builder.Configuration, options { options.ApplicationType typeof(MyBlazorApp.Blazor.BlazorApplication); options.Authentication.StandardAuthentication.IsEnabled true; }); builder.Services.AddAuthentication(); builder.Services.AddAuthorization(); var app builder.Build();这里的AddXafWebApi会做几件事注册IXafApplicationFactory服务读取 XAF 配置中的模块列表把 WebApi 模块加载进来并准备标准认证的 Token 端点。它不会帮你注册认证中间件所以AddAuthentication和AddAuthorization需要显式加上这两行常常被漏掉结果一调用[Authorize]就抛异常。选项里的ApplicationType指向同一款 XafApplication 子类这是方案 A 的关键。Blazor 页面和 Web API 共用同一个业务应用定义模块、数据库连接、安全策略都是同一套。3.3 使用 UseXafWebApi 中间件app构建完成后需要调用UseXafWebApi并确保MapControllers被注册。我给的完整管线参考var app builder.Build(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseXafWebApi(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages(); app.MapBlazorHub(); app.MapControllers(); app.MapFallbackToPage(/_Host); app.Run();路由为什么能共存关键在于 ASP.NET Core 的路由系统是按顺序匹配的。MapControllers负责把[ApiController]和[Route]特性标注的类映射到路由表MapBlazorHub注册的是 SignalR hub 路径MapFallbackToPage(_Host)是最后一层兜底专门处理非 API、非 Hub 的浏览器请求。三者各管一段互不干扰。有一点容易踩坑UseXafWebApi建议放在UseAuthentication之前。XAF Web API 的中间件内部要初始化 Token 端点等配置如果放错了位置后续请求可能 401。我习惯于把它和UseStaticFiles放在相邻位置这样意图清晰。3.4 写第一个 Controller服务注册和中间件都配好后在项目里新建一个 Controllers 文件夹写一个最基础的只读接口。我这里演示一个基于联系人的简单查询using DevExpress.ExpressApp.WebApi; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace MyBlazorApp.Blazor.Controllers; [Route(api/[controller])] [ApiController] [Authorize] public class ContactController : ControllerBase { private readonly IXafApplicationFactory _applicationFactory; public ContactController(IXafApplicationFactory applicationFactory) { _applicationFactory applicationFactory; } [HttpGet] public IActionResult Get() { var application _applicationFactory.CreateApplication(); using var objectSpace application.CreateObjectSpace(typeof(Contact)); var contacts objectSpace.GetObjectsContact().Select(c new { c.Id, c.FullName, c.Email }).ToList(); return Ok(contacts); } }注意几个细节。第一IXafApplicationFactory从构造函数注入不需要自己new。这个工厂是AddXafWebApi注册的目的是把 XafApplication 的创建和管理交给容器。你可以理解成它帮你维护了一个“共享的业务应用实例”你每次拿它创建 ObjectSpace 来操作数据。第二CreateApplication()返回的是 XafApplication 实例通常由工厂缓存复用不要轻易用using把它销毁。真正需要释放的是CreateObjectSpace得到的 ObjectSpace我这里用using var确保请求结束后释放连接资源。如果你在多个接口里频繁创建、销毁 XafApplication很可能引发性能问题因为 XafApplication 内部包含模块集合、数据库更新逻辑等重量级组件设计上并不是为“每请求一实例”准备的。第三返回数据用匿名对象或 DTO不要直接返回实体列表。XAF 的实体对象带有导航属性、延迟加载代理直接序列化可能触发循环引用也可能把一堆不该暴露的字段发给前端。4. 关键机制解读API 层如何复用 XAF 的能力4.1 IXafApplicationFactory 在 API 层做了什么很多第一次接触这个模式的人会问为什么不直接在 Controller 里 new 一个 XafApplication原因是 XafApplication 的初始化成本太高。它需要加载模块、解析数据库类型、初始化安全系统、执行DatabaseUpdateMode相关的更新检查。如果每个接口请求都走一遍应用等于一直处于启动状态性能惨不忍睹。IXafApplicationFactory的设计目标就是把这个初始化过程收拢到工厂里内部缓存 XafApplication 实例。你每次调用CreateApplication()拿到的很可能是同一个已经初始化好的实例然后在这个实例上CreateObjectSpace()获取独立的数据上下文。这个设计在 Blazor UI 侧其实也是类似的XAF Blazor 的中间件在启动时初始化一个应用实例之后页面操作都围绕这个实例展开。实操上还有一点工厂创建的 ObjectSpace 是线程安全的因为 XAF 的对象空间本身就是为“每用户操作独占上下文”设计的。Controller 是瞬时scoped注册的你完全可以在多个并发请求里各自创建、各自提交互不干扰。4.2 ObjectSpace 的获取、提交与释放对于写操作核心流程是创建 ObjectSpace → 查数据或NewObject()→ 修改属性 →CommitChanges()。我举例一个更新接口[HttpPut({id:guid})] public IActionResult Update(Guid id, [FromBody] ContactUpdateDto dto) { var application _applicationFactory.CreateApplication(); using var objectSpace application.CreateObjectSpace(typeof(Contact)); var contact objectSpace.GetObjectByKeyContact(id); if (contact null) { return NotFound(); } if (!string.IsNullOrWhiteSpace(dto.FullName)) { contact.FullName dto.FullName; } if (!string.IsNullOrWhiteSpace(dto.Email)) { contact.Email dto.Email; } objectSpace.CommitChanges(); return Ok(); }这里最关键的一行是objectSpace.CommitChanges()。XAF 的对象空间缓存了所有修改不调用提交数据不会落到数据库。这和 Blazor 页面里的ObjectSpace.CommitChanges()一样只是换成在 Controller 里调用。另外我建议在写入接口中用 DTO 而不是直接接受实体 JSON。否则客户端可能把你不想暴露的字段传过来比如 IsAdmin 这种权限字段直接置 true造成越权修改。XAF 的权限系统能拦截一部分但 Controller 层做一次白名单校验更有保障。4.3 权限和认证怎么打通这是方案 A 最大的优势API 和 Blazor 页面共用一套权限体系。AddXafWebApi选项里开启了Authentication.StandardAuthentication后会提供一个 Token 端点。客户端拿到 Token在请求头里带Authorization: Bearer tokenUseAuthentication中间件验明身份后XAF 的对象空间会基于当前用户创建安全上下文。也就是说一个用户如果只被授予了“查看联系人”的权限那么通过 API 查询联系人时objectSpace.GetObjectsContact()会自动过滤掉他没有权限看到的行甚至直接抛出安全性异常。这一点比普通 ASP.NET Core API 多了一层“域权限”屏障。普通 API 的[Authorize]只能校验“你是谁”XAF 的对象空间还能控制“你能看到哪些数据、改哪些字段”。所以你在 Controller 里写代码时可以很放心不需要自己重复实现行级权限过滤。5. 常见问题与避坑清单5.1 请求 404Blazor 的 Fallback 路由把 API 吃了不少人在集成后遇到一个问题API 请求返回 404而页面访问正常。第一反应是“Fallback 路由抢走了 API 请求”但这其实是误解。Blazor 的MapFallbackToPage(_Host)是低优先级兜底路由只有当没有其他路由匹配时才会触发。API 404 的常见原因通常是忘了在管线里调用app.MapControllers()。Controller 类没有[ApiController]或[Route]特性导致它没有被识别为 API 控制器。Controller 类不是 public 的ASP.NET Core 默认不注册非公开类。请求路径写错了比如路由设计为api/Contact但客户端请求了api/contact大小写不匹配导致 404。排查路径很直观用浏览器直接访问 Swagger 地址通常是{host}/swagger如果 Swagger 能列出接口说明 Controller 注册没有问题问题大概率出在客户端请求路径或认证配置上。5.2 跨域问题CORS如果你的前端不是 Blazor Server 页面而是独立的 Vue/React 应用或者小程序一定会遇到 CORS 问题。原因是浏览器同源策略限制了跨域请求。解决方式是在服务注册时添加 CORS 策略builder.Services.AddCors(options { options.AddPolicy(AllowSpecificOrigin, policy { policy.WithOrigins(https://your-app-domain.com) .AllowAnyHeader() .AllowAnyMethod(); }); });然后在中间件管线里app.UseCors(AllowSpecificOrigin)。这里要提醒一个安全细节不要把WithOrigins(*)写成通用允许尤其当你的 API 带有认证时。CORS 的*会允许任何来源访问这对需要 Token 的接口来说等于把攻击面交给浏览器端任意脚本。我见过内部系统因为图省事配置了AllowAnyOrigin结果被渗透测试直接判了高危。按域名白名单收口不用怕麻烦。5.3 Swagger 打不开或报错Swagger 打不开先确认几件事当前环境是否Development。很多项目只在开发环境启用 Swagger生产环境自动关闭。app.UseSwagger()和app.UseSwaggerUI()是否在app.UseAuthorization()之前或之后放在了正确位置。Swagger 的中间件不依赖认证顺序但如果你开了认证而 Swagger 页面本身受限需要额外放行。DevExpress 的 WebApi Swashbuckle 扩展包是否安装完整。如果只装了Swashbuckle.AspNetCore而没装DevExpress.ExpressApp.WebApi.Swashbuckle运行时会因为缺少 XAF 的文档处理器而失败。另外一个常见坑是 Swagger 页面能打开但显示“无法解析 API 定义”此时多半是启动时抛了异常。查看应用日志最常见的就是版本冲突或模块加载失败跟 3.1 里提到的版本问题直接相关。5.4 序列化循环引用和延迟加载爆炸XAF 的业务对象往往存在多层关联关系比如订单 - 订单明细 - 商品 - 分类。如果你在 API 里直接返回IObjectSpace.GetObjectsOrder()JSON 序列化器会尝试递归遍历所有导航属性结果可能是三层之后爆出循环引用异常或者把整个数据库都拖出来。我第一次踩这个坑时日志里报的是“Object graph contains circular reference”当时还以为是 XAF 的 Bug后来才发现是序列化层需要限制导航属性的遍历深度。解决方案有两个层面。第一层在 Program.cs 中配置 JSON 选项builder.Services.AddMvc().AddJsonOptions(options { options.JsonSerializerOptions.ReferenceHandler ReferenceHandler.IgnoreCycles; });这个配置能避免循环引用直接抛异常但它只是“不报错”该加载的数据还是会加载。第二层更推荐API 返回 DTOData Transfer Object。在查询时就只 Select 需要的字段不触碰导航属性。比如查订单接口只返回订单号、客户名称、总金额不把订单明细全量带出去。如果前端确实需要明细就单独提供一个明细接口。这样既能控制返回体大小也能避免延迟加载触发。5.5 大批量修改事务与性能问题如果你要批量导入或更新大量数据比如一次性传入几百条记录注意不要循环里提交。每CommitChanges()一次就是一次数据库往返几百条循环就是几百次性能直接崩掉。正确做法是在一个 ObjectSpace 里全部改完最后提交一次。示例[HttpPost(bulk-update)] public IActionResult BulkUpdate([FromBody] ListContactUpdateDto items) { var application _applicationFactory.CreateApplication(); using var objectSpace application.CreateObjectSpace(typeof(Contact)); foreach (var item in items) { var contact objectSpace.GetObjectByKeyContact(item.Id); if (contact ! null) { contact.FullName item.FullName; contact.Email item.Email; } } objectSpace.CommitChanges(); return Ok(); }这样数据库只会产生一次事务提交配合合理批次大小比如每次调用最多传 1000 条性能完全可控。如果仍然很慢先排查数据库索引和网络延迟别急着怀疑 XAF 本身。6. 版本差异与调试经验6.1 不同版本 API 名称有变化DevExpress 每年的主版本都会调整一些 API 名称。我写这篇内容时AddXafWebApi、UseXafWebApi、IXafApplicationFactory是当前主流的命名但你在老版本项目比如 v21.x里可能看到的是其他扩展类。遇到编译报错不要慌直接查版本对应的官方文档或升级指南。同时注意XAF 的 WebApi 模块在不同版本里对认证的默认行为也不完全一致。早期版本可能默认关闭标准认证需要手动开启新版本里开启方式更显式。每次升级后务必回归一遍“未登录访问受保护接口”的用例确认 401 逻辑符合预期。6.2 调试建议先只读后写入我建议第一次集成时不要一上来就写复杂的增删改接口。先做一个只读查询跑通全链路启动应用 → 打开 Swagger → 获取 Token → 调用接口 → 拿到数据。这条链路通了后面加写入只是复制粘贴的问题。获取 Token 的方式是请求 Token 端点传递用户名密码。XAF 标准认证的 Token 端点地址通常可以通过 Swagger UI 直接看到那里会列出认证相关的接口。拿到 Token 后在 Swagger UI 右上角点 Authorize把 Token 填进去之后就能调试受保护的接口了。6.3 生产环境关闭 SwaggerSwagger 是开发调试利器但在生产环境建议关闭。一方面是因为 Swagger 页面可能泄露接口结构和数据类型增加攻击者探测攻击面的机会另一方面Swagger UI 本身偶尔会因为扫描整个 API 树造成不必要的性能开销。常规做法是在app.Environment.IsDevelopment()条件下调用UseSwagger和UseSwaggerUI。7. 最后的一点心里话做了这么多年 XAF 项目我的体会是集成 Web API 这件事技术难点从来不在写 Controller而在于你想清楚“哪些能力要复用、哪些边界要隔离”。把 API 直接挂进现有 XAF Blazor 应用是成本最低、也最容易保持业务一致性的一条路。如果你是从零开始我建议先在现有项目里把只读接口跑通感受一下 ObjectSpace 在 API 层的用法等团队确实有了独立的 API 部署需求再考虑把业务类库抽出去那时候毕竟已经积累了接口层面的实践经验迁移起来心里有数。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门