快速入门

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

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

服务器设置

AddDoconut() 注册服务;UseDoconutResources() 和 UseDoconut() 连接中间件。资源调用必须先行。会话调用也必需——Doconut 的默认文档安全会根据 ASP.NET 会话状态验证每个页面请求。会话状态需要缓存:AddDistributedMemoryCache() 提供一个。MVC 或 Razor Pages 应用已注册它,但最小 API 应用没有,没有它时应用在启动时会因 Unable to resolve service for type 'IDistributedCache' 而停止。已经在安装期间注册了 Doconut?请跳到下一节。

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddDistributedMemoryCache(); // 会话状态需要缓存(MVC/Razor Pages 已经添加了一个)
builder.Services.AddSession(); // Doconut 文档安全依赖于 ASP.NET 会话状态

app.UseSession();          // 在调用 UseDoconut() 之前调用 UseSession()
app.UseDoconutResources(); // 必须在 UseDoconut() 之前注册
app.UseDoconut();

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

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = Path.Combine(AppContext.BaseDirectory, "wwwroot", "Doconut.Viewer.lic");
    options.MiddlewarePath = "/doconut";
    options.ResourcesPath = "/doconut-res";
    options.UnsafeMode = false;
});
builder.Services.AddDistributedMemoryCache();
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 会自行添加斜杠,所以 '/doconut' 将变为 //doconut/DocImage.axd,浏览器会将其解释为对名为 doconut 的主机的请求,导致 viewer 显示错误“无法访问 viewer 处理程序”。

将 viewer 添加到页面

Viewer 是页面必需的核心。其渲染表面使用两个嵌套的 div:

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

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

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- 查看器的导航、缩放、搜索和批注按钮 -->
</nav>

<div id="searchBarMount"></div>
<div id="annBarMount"></div>

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

引用 viewer 资源

在 Razor 视图中,注入的 Viewer 服务按依赖顺序输出 viewer 的 <link> 和 <script> 标记——该小部件是 jQuery 插件,因此必须在 viewer 脚本之前加载 jQuery:

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

IncludeViewerCss 和 IncludeViewerScripts 是必需的核心标志。切勿发布一个 搜索或注释功能区示例,若没有它们、Viewer 挂载点以及一个 docViewer 实例。ReferenceCss 和 ReferenceScripts 在当前许可证未授予该功能时会省略可选模块的资源;核心 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 () {
        // 页面可见;此处可以安全地隐藏加载指示器
    },
    // 当嵌入式功能区存在时,将注释生命周期事件转发给它
    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 实例:

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. 打开文档并保留其令牌,以供 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 服务打开文档并返回会话令牌。

csharp
app.MapPost("/api/open", async (Viewer viewer) =>
{
    // 令牌是不可见的 — 将其交给小部件,切勿记录或持久化它。
    string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
    return Results.Ok(new { 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) 会立即删除缓存的会话,释放渲染引擎,删除其安全标记,并撤销令牌。滑动过期最终会执行相同的清理,但对于大文档,建议显式关闭。

完整的请求流程如下:

text
AddDoconut + middleware
    -> render CSS/scripts and mount div
    -> initialize docViewer
    -> OpenDocumentAsync
    -> return opaque token
    -> objViewer.View(token)
    -> page/search/annotation requests
    -> Close / CloseDocument

将令牌视为持有者凭证:切勿记录、切勿持久化,只交给小部件。它标识服务器上的活跃文档会话,且在会话过期后失效——重新打开文档即可获取新的令牌。

运行

将 PDF 放置在 wwwroot/files/Sample.pdf,运行 dotnet run,并打开承载小部件的页面。第一页会在查看器中渲染,左侧带有缩略图面板。如果没有显示,请参阅 故障排除。

未授权时的功能

缺少许可证不会抛出错误。查看器仍会正常渲染,但每页都会带有评估水印。请参阅 许可证设置 了解 Doconut 如何查找许可证以及找到后会有哪些变化。

此页面对您有帮助吗?