快速入门
在几分钟内渲染您的第一份文档
本指南将一个空的 Program.cs 的 ASP.NET Core 应用程序,逐步转化为在浏览器中渲染文档的完整过程:服务器注册、完整的 Viewer 包(Viewer 工具栏、Viewer 挂载点以及可选的搜索/批注功能区)、资源引用、客户端初始化、文档打开以及执行。
服务器设置
AddDoconut() 注册服务;UseDoconutResources() 和 UseDoconut() 连接中间件。资源调用必须先于其他调用。会话调用也是必需的 —— Doconut 的默认文档安全会对每个页面请求进行 ASP.NET 会话状态验证。已经在安装期间注册了 Doconut?请直接跳到下一节。
builder.Services.AddDoconut(options =>
{
options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddSession(); // Doconut document security rides on ASP.NET session state
app.UseSession(); // call UseSession() before UseDoconut()
app.UseDoconutResources(); // must be registered before UseDoconut()
app.UseDoconut();对于生产环境的路径布局,将文档中间件映射到显式分支,并保持四个路径设置一致:
builder.Services.AddDoconut(options =>
{
options.LicensePath = Path.Combine(AppContext.BaseDirectory, "Doconut.Viewer.lic");
options.MiddlewarePath = "/doconut";
options.ResourcesPath = "/doconut-res";
options.UnsafeMode = false;
});
builder.Services.AddSession();
app.UseRouting();
app.UseSession();
app.UseDoconutResources();
app.Map("/doconut", branch => branch.UseDoconut());MiddlewarePath 是一个协同值;它本身并不会映射 ASP.NET Core 分支。在本例中,主机映射了 /doconut,因此客户端必须使用 BasePath: '/doconut'。ResourcesPath 在 /doconut-res 提供嵌入式包,部件的图像资源路径因此为 ResPath: '/doconut-res/images'。
将查看器添加到页面
Viewer 是页面的必需核心。它的渲染表面使用两个嵌套的 div:
<div id="divDocViewer">
<div id="div_ctlDoc"></div>
</div>将工具栏、模块挂载点以及 Viewer 表面视为一个页面组合。搜索和批注会将它们的嵌入式功能区注入到可选挂载点,但这些模块永远不是独立的:它们始终附加在同一页面的 Viewer 上。使用与 Doconut.TestApp 和 Doconut.TestApp.Distributed 相同的顺序:
<nav id="toolbar" aria-label="Document viewer controls">
<!-- Viewer navigation, zoom, Search, and Annotation buttons -->
</nav>
<div id="searchBarMount"></div>
<div id="annBarMount"></div>
<div id="divDocViewer">
<div id="div_ctlDoc"></div>
</div>引用查看器资源
在 Razor 视图中,注入的 Viewer 服务会按依赖顺序输出 Viewer 的 <link> 和 <script> 标签 —— 该部件是 jQuery 插件,因此必须先加载 jQuery 再加载 Viewer 脚本:
@inject Doconut.Viewer Viewer
@Html.Raw(Viewer.ReferenceCss(new CssConfig
{
IncludeBootstrapCss = true,
IncludeViewerCss = true
}))
@Html.Raw(Viewer.ReferenceScripts(new ScriptConfig
{
IncludeJQuery = true,
IncludeBootstrap = true,
IncludeViewerScripts = true
}))若需要完整的 Viewer 包,请一起请求 Viewer 与模块资源:
@Html.Raw(Viewer.ReferenceCss(new CssConfig
{
IncludeBootstrapCss = true,
IncludeViewerCss = true,
IncludeSearchCss = true,
IncludeAnnotationCss = true
}))
@Html.Raw(Viewer.ReferenceScripts(new ScriptConfig
{
IncludeJQuery = true,
IncludeBootstrap = true,
IncludeViewerScripts = true,
IncludeSearchScripts = true,
IncludeSearchBar = true,
IncludeAnnotationScripts = true,
IncludeAnnotationBar = true
}))IncludeViewerCss 和 IncludeViewerScripts 是必需的核心标记。不要在没有它们的情况下发布搜索或批注功能区示例、Viewer 挂载点以及 docViewer 实例。ReferenceCss 与 ReferenceScripts 会在当前许可证不具备相应功能时省略可选模块的资源;核心 Viewer 仍能启动。
初始化查看器
客户端部件是 jQuery 插件。这是一组最小的真实初始化选项(非伪代码):
let searchBar = null;
let annBar = null;
const objViewer = $('#div_ctlDoc').docViewer({
showThumbs: true,
autoLoad: false,
pageZoom: 100,
FitType: 'width',
BasePath: '/doconut',
ResPath: '/doconut-res/images',
onViewerReady: function () {
// pages are visible; safe to hide a loading spinner here
},
// Forward annotation lifecycle events to the embedded ribbon when it is present.
onAnnLoaded: () => annBar?.handleAnnLoaded(),
onAnnSaved: () => annBar?.handleAnnSaved(),
onAnnSaveError: () => annBar?.handleAnnSaveError(),
onAnnClosed: () => annBar?.handleAnnClosed(),
onError: function (message) {
console.error('Doconut viewer error:', message);
}
});选项的大小写确实混合——showThumbs、autoLoad、pageZoom 为 camelCase,FitType、BasePath、ResPath 为 PascalCase。没有统一规则;大小写写错会导致选项被静默忽略(部件会回退到默认值而不是抛错)。
组装完整的 Viewer 包
两个 .NET 6 参考应用都在同一页面上一起安装以下部分:
| 包的组成部分 | 必要性 | 连接方式 |
|---|---|---|
Viewer 资源、挂载点和 objViewer | 必需 | 核心文档渲染器 |
| Viewer 工具栏 | 在参考组合中必需 | 主机标记;按钮调用相同的 objViewer |
| 搜索功能区 | 可选,需许可证模块 | doconutSearchBar(...).attach(objViewer) |
| 批注功能区 | 可选,需许可证模块 | doconutAnnotationBar(...).attach(objViewer) |
虽然主 Viewer 工具栏是主机标记,但它与 Viewer 一起安装,绝不能作为独立控件单独文档化。这样可以让其布局、标签、图标和授权规则始终受您的应用程序控制,同时每个按钮都驱动同一个 Viewer 实例:
<nav id="toolbar" aria-label="Document viewer controls">
<button type="button" onclick="objViewer.GotoPage(1)">First</button>
<button type="button" onclick="objViewer.Next(false)">Previous</button>
<button type="button" onclick="objViewer.Next(true)">Next</button>
<button type="button" onclick="objViewer.GotoPage(objViewer.TotalPages())">Last</button>
<button type="button" onclick="objViewer.Zoom(false)">Zoom out</button>
<button type="button" onclick="objViewer.Zoom(true)">Zoom in</button>
<button type="button" onclick="objViewer.FitType('width')">Fit width</button>
<button type="button" onclick="objViewer.FitType('height')">Fit height</button>
<button type="button" id="openSearch">Search</button>
<button type="button" id="openAnnotations">Annotations</button>
</nav>完整的参考工具栏还会把 wwwroot/js/viewerToolbar.js 复制到主机应用中,用于旋转、缩略图、打印、全屏、布局以及按钮状态的辅助功能。请在 Viewer.ReferenceScripts(...) 之后加载该主机文件。复制完整演示实现时,请保持辅助脚本与其 <nav id="toolbar"> 标记一起。
保持两套参考应用使用的包初始化顺序:
- 输出 Viewer 与已授权模块的 CSS。
- 同时渲染 Viewer 工具栏、搜索/批注挂载点以及 Viewer 挂载点。
- 输出 Viewer 与已授权模块的脚本。
- 加载主机应用的
viewerToolbar.js。 - 初始化
docViewer并保留得到的objViewer。 - 初始化每个已授权的搜索或批注功能区。
- 对每个功能区调用
attach(objViewer)。 - 打开文档并保留其 token,以供 Viewer 与模块请求使用。
Doconut.TestApp.Distributed 保持完全相同的 UI 组合和相同的 Viewer‑toolbar 辅助脚本。其额外的 access 请求值以及异步渲染重试设置属于分布式传输,不会改变 Viewer、工具栏或功能区的组装方式。
服务器端的防护很重要:当可选功能不可用时,其脚本不会被输出,从而其 jQuery 插件函数不存在。
<script>
let currentToken = '';
const refitViewer = () =>
requestAnimationFrame(() => objViewer.Refit());
@if (Viewer.IsSearchEnabled)
{
<text>
searchBar = $('#searchBarMount').doconutSearchBar({
docId: 'ctlDoc',
getRequestParams: () => ({ token: currentToken }),
onLayout: refitViewer
});
searchBar.attach(objViewer);
</text>
}
@if (Viewer.IsAnnotationEnabled)
{
<text>
annBar = $('#annBarMount').doconutAnnotationBar({
docId: 'ctlDoc',
getRequestParams: () => ({ token: currentToken }),
onLayout: refitViewer
});
annBar.attach(objViewer);
</text>
}
document.getElementById('openSearch').addEventListener('click', () => {
if (!searchBar) return;
searchBar.isOpen() ? searchBar.close() : searchBar.open();
});
document.getElementById('openAnnotations').addEventListener('click', () => {
if (!annBar) return;
annBar.isOpen() ? annBar.close() : annBar.open();
});
</script>两个嵌入组件各自生成自己的功能区 DOM。Search 包含“查找”“选项”“结果”组;Annotation 包含作者工具、样式控制、保存操作以及可选的导出/图像操作。它们提供 open()、close()、reset() 与 isOpen() 方法;创建后务必一次性调用 attach(objViewer)。
上面的示例省略了可选的主机回调以及 Annotation 的导出/图像端点,以保持启动最小化。请参阅搜索和批注获取完整的功能特定设置,或查看自定义主题以对主机拥有的 Viewer 工具栏进行样式或替换。
打开文档
服务器端只有一个端点:注入的 Viewer 服务打开文档并返回会话 token。
app.MapPost("/api/open", async (Viewer viewer) =>
{
// The token is opaque — hand it to the widget, never log or persist it.
string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
return Results.Ok(new { token });
});客户端获取该 token 并通过 objViewer.View(token) 交给部件:
fetch('/api/open', { method: 'POST' })
.then(resp => resp.json())
.then(data => {
currentToken = data.token;
objViewer.View(currentToken);
});关闭文档
当用户离开查看器或打开替换文档时,调用 objViewer.Close()。在服务器驱动的工作流中,viewer.CloseDocument(token) 会立即移除缓存的会话、释放渲染引擎、删除安全标记并撤销 token。滑动过期也会最终执行相同的清理,但对于大文档建议显式关闭。
完整的请求流程如下:
AddDoconut + middleware
-> render CSS/scripts and mount div
-> initialize docViewer
-> OpenDocumentAsync
-> return opaque token
-> objViewer.View(token)
-> page/search/annotation requests
-> Close / CloseDocument将 token 视为持有者凭证:绝不要记录、持久化,仅交给部件使用。它标识服务器上活跃的文档会话,会在会话过期后失效 —— 需要重新打开文档以获取新的 token。
运行示例
在 wwwroot/files/Sample.pdf 放置一个 PDF,运行 dotnet run,然后打开承载部件的页面。第一页面会在查看器中渲染,并在左侧显示缩略图面板。如果没有显示,请参阅故障排除。
未授权时的表现
缺少许可证不会抛错。查看器仍会正常渲染,但每页都会带有评估水印。请参阅许可证设置了解 Doconut 如何查找许可证以及找到后会有哪些变化。
此页面有帮助吗?