Viewer 是无状态的 — 如何构建最小化 .NET 8 开放端点
← Back to Blog••2 min read

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");
});

代码功能说明

  1. 接受文件名 – 客户端告诉服务器要打开 wwwroot/files 下的哪个文件。
  2. 调用 OpenDocumentAsync – 无状态的 Viewer 创建新会话,将其存入缓存,并返回一个不透明令牌。
  3. 以纯文本返回令牌 – 客户端仅收到令牌;服务器在此响应中从不发送文档字节。

由于 Viewer 不持有文档数据,端点不需要 using 块、Dispose 调用或每请求的工厂。令牌是客户端唯一会返回给 viewer 小部件的凭证(前端的 objViewer.View(token))。


在客户端使用令牌

令牌返回后,前端小部件调用 objViewer.View(token)。随后小部件在每个后续的页面图像、搜索或注释操作请求中都携带相同的令牌。服务器会根据最初打开文档的会话验证该令牌。如果令牌来自不同的浏览器会话,viewer 将返回授权错误,这正是预期的安全行为。


提前关闭会话(可选)

如果您的工作流确定文档不会再被访问,可以在 Viewer 上调用 CloseDocument 来释放缓存条目。这会将会话从缓存中移除,并阻止滑动过期计时器保持其存活。


无状态流程回顾

步骤发生了什么状态存放位置
注入 Viewer在启动时创建单个 Viewer 实例。位于 DI 容器(单例)中。
OpenDocumentAsync检查许可证 → 创建会话 → 返回令牌。会话存储在 IMemoryCache(或分布式缓存)中。
客户端渲染小部件在每个请求中发送令牌。令牌是客户端持有的唯一每文档数据。
会话过期 / 关闭文档超时或显式关闭后移除缓存条目。Viewer 服务中没有残留资源。

由于服务本身从不存储文档数据,您可以安全地扩展 Web 农场、替换节点或重启应用而不会丢失任何用户可见的状态——只有令牌会失效,促使客户端重新打开文档。


在应用中部署无状态 Viewer

  1. 将 AddDoconut、AddSession 和缓存提供程序添加到 Program.cs。
  2. 将打开端点的代码片段粘贴到您的 minimal‑API 配置中。
  3. 将返回的令牌传递给前端的 objViewer.View(token)。

现在,您拥有一个完全无状态、可投入生产的文档打开 API,利用了 Doconut 的 Viewer 设计。

#dotnet8#viewer#stateless#api#doconut#查看器#无状态