快速入门

在几分钟内渲染您的第一份文档

本指南将一个空的 Program.cs 的 ASP.NET Core 应用程序,逐步转化为在浏览器中渲染文档的完整过程:服务器注册、完整的 Viewer 包(Viewer 工具栏、Viewer 挂载点以及可选的搜索/批注功能区)、资源引用、客户端初始化、文档打开以及执行。

服务器设置

AddDoconut() 注册服务;UseDoconutResources()UseDoconut() 连接中间件。资源调用必须先于其他调用。会话调用也是必需的 —— Doconut 的默认文档安全会对每个页面请求进行 ASP.NET 会话状态验证。已经在安装期间注册了 Doconut?请直接跳到下一节。

csharp
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();

对于生产环境的路径布局,将文档中间件映射到显式分支,并保持四个路径设置一致:

csharp
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

html
<div id="divDocViewer">
    <div id="div_ctlDoc"></div>
</div>

将工具栏、模块挂载点以及 Viewer 表面视为一个页面组合。搜索和批注会将它们的嵌入式功能区注入到可选挂载点,但这些模块永远不是独立的:它们始终附加在同一页面的 Viewer 上。使用与 Doconut.TestAppDoconut.TestApp.Distributed 相同的顺序:

html
<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 脚本:

html
@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
@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
}))

IncludeViewerCssIncludeViewerScripts 是必需的核心标记。不要在没有它们的情况下发布搜索或批注功能区示例、Viewer 挂载点以及 docViewer 实例。ReferenceCssReferenceScripts 会在当前许可证不具备相应功能时省略可选模块的资源;核心 Viewer 仍能启动。

初始化查看器

客户端部件是 jQuery 插件。这是一组最小的真实初始化选项(非伪代码):

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

选项的大小写确实混合——showThumbsautoLoadpageZoom 为 camelCase,FitTypeBasePathResPath 为 PascalCase。没有统一规则;大小写写错会导致选项被静默忽略(部件会回退到默认值而不是抛错)。

组装完整的 Viewer 包

两个 .NET 6 参考应用都在同一页面上一起安装以下部分:

包的组成部分必要性连接方式
Viewer 资源、挂载点和 objViewer必需核心文档渲染器
Viewer 工具栏在参考组合中必需主机标记;按钮调用相同的 objViewer
搜索功能区可选,需许可证模块doconutSearchBar(...).attach(objViewer)
批注功能区可选,需许可证模块doconutAnnotationBar(...).attach(objViewer)

虽然主 Viewer 工具栏是主机标记,但它与 Viewer 一起安装,绝不能作为独立控件单独文档化。这样可以让其布局、标签、图标和授权规则始终受您的应用程序控制,同时每个按钮都驱动同一个 Viewer 实例:

html
<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"> 标记一起。

保持两套参考应用使用的包初始化顺序:

  1. 输出 Viewer 与已授权模块的 CSS。
  2. 同时渲染 Viewer 工具栏、搜索/批注挂载点以及 Viewer 挂载点。
  3. 输出 Viewer 与已授权模块的脚本。
  4. 加载主机应用的 viewerToolbar.js
  5. 初始化 docViewer 并保留得到的 objViewer
  6. 初始化每个已授权的搜索或批注功能区。
  7. 对每个功能区调用 attach(objViewer)
  8. 打开文档并保留其 token,以供 Viewer 与模块请求使用。

Doconut.TestApp.Distributed 保持完全相同的 UI 组合和相同的 Viewer‑toolbar 辅助脚本。其额外的 access 请求值以及异步渲染重试设置属于分布式传输,不会改变 Viewer、工具栏或功能区的组装方式。

服务器端的防护很重要:当可选功能不可用时,其脚本不会被输出,从而其 jQuery 插件函数不存在。

html
<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。

csharp
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) 交给部件:

javascript
fetch('/api/open', { method: 'POST' })
    .then(resp => resp.json())
    .then(data => {
        currentToken = data.token;
        objViewer.View(currentToken);
    });

关闭文档

当用户离开查看器或打开替换文档时,调用 objViewer.Close()。在服务器驱动的工作流中,viewer.CloseDocument(token) 会立即移除缓存的会话、释放渲染引擎、删除安全标记并撤销 token。滑动过期也会最终执行相同的清理,但对于大文档建议显式关闭。

完整的请求流程如下:

text
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 如何查找许可证以及找到后会有哪些变化。

此页面有帮助吗?