
Viewer 是无状态的 — 如何构建最小化 .NET 8 开放端点
Viewer 是无状态的 — 设计如此
Viewer 不保留任何每请求的文档数据。它是一个 sealed 类,永不实现 IDisposable,并且您打开的每个文档都存在于框架缓存管理的独立会话中。因此,您可以在 ASP.NET Core 应用的任何位置注入单个 Viewer 实例,并安全地从多个并发请求中调用它。
无状态服务的后果
- 没有隐藏资源 – 服务本身从不分配需要显式清理的大缓冲区。
- 默认线程安全 – 因为没有存储请求特定的状态,同一个
Viewer实例可以在多个线程间使用而无需锁定。 - 可扩展部署 – 水平扩展开箱即用;您无需粘性会话或服务器亲和性,只需客户端持有的会话令牌。
所有文档特定的信息(页面、注释、搜索索引)都保存在由 OpenDocumentAsync 创建的 会话 中。会话存储在 IMemoryCache(或您配置的任何 IDistributedCache)中,并在一段不活动时间后自动过期(默认 60 分钟)。完成后,您可以调用 CloseDocument 提前释放缓存条目。
注册无状态 Viewer
因为 Viewer 没有可变状态,请在启动时注册一次:
builder.Services.AddDoconut(); // 注册 Viewer 和所需的中间件
builder.Services.AddSession(); // 用于令牌安全的必需项
builder.Services.AddDistributedMemoryCache(); // 演示用的简单缓存
AddDoconut 调用还会连接提供 Viewer 静态资源的中间件。服务就绪后,您可以在任何端点签名中请求 Viewer。
最小化打开文档端点(net‑8 示例)
以下代码是官方文档中的完整片段,可在 .NET 8 minimal‑API 项目中开箱即用:
app.MapPost("/api/open", async (string fileName, Viewer viewer) =>
{
var token = await viewer.OpenDocumentAsync($"files/{fileName}");
return Results.Content(token, "text/plain");
});
代码功能说明
- 接受文件名 – 客户端告诉服务器要打开
wwwroot/files下的哪个文件。 - 调用
OpenDocumentAsync– 无状态的Viewer创建新会话,将其存入缓存,并返回一个不透明令牌。 - 以纯文本返回令牌 – 客户端仅收到令牌;服务器在此响应中从不发送文档字节。
由于 Viewer 不持有文档数据,端点不需要 using 块、Dispose 调用或每请求的工厂。令牌是客户端唯一会返回给 viewer 小部件的凭证(前端的 objViewer.View(token))。
在客户端使用令牌
令牌返回后,前端小部件调用 objViewer.View(token)。随后小部件在每个后续的页面图像、搜索或注释操作请求中都携带相同的令牌。服务器会根据最初打开文档的会话验证该令牌。如果令牌来自不同的浏览器会话,viewer 将返回授权错误,这正是预期的安全行为。
提前关闭会话(可选)
如果您的工作流确定文档不会再被访问,可以在 Viewer 上调用 CloseDocument 来释放缓存条目。这会将会话从缓存中移除,并阻止滑动过期计时器保持其存活。
无状态流程回顾
| 步骤 | 发生了什么 | 状态存放位置 |
|---|---|---|
| 注入 Viewer | 在启动时创建单个 Viewer 实例。 | 位于 DI 容器(单例)中。 |
| OpenDocumentAsync | 检查许可证 → 创建会话 → 返回令牌。 | 会话存储在 IMemoryCache(或分布式缓存)中。 |
| 客户端渲染 | 小部件在每个请求中发送令牌。 | 令牌是客户端持有的唯一每文档数据。 |
| 会话过期 / 关闭文档 | 超时或显式关闭后移除缓存条目。 | Viewer 服务中没有残留资源。 |
由于服务本身从不存储文档数据,您可以安全地扩展 Web 农场、替换节点或重启应用而不会丢失任何用户可见的状态——只有令牌会失效,促使客户端重新打开文档。
在应用中部署无状态 Viewer
- 将
AddDoconut、AddSession和缓存提供程序添加到Program.cs。 - 将打开端点的代码片段粘贴到您的 minimal‑API 配置中。
- 将返回的令牌传递给前端的
objViewer.View(token)。
现在,您拥有一个完全无状态、可投入生产的文档打开 API,利用了 Doconut 的 Viewer 设计。