クイックスタート

数分で最初のドキュメントをレンダリング

このハンドブックは、空の 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();

本番環境向けのパスレイアウトでは、ドキュメントミドルウェアを明示的なブランチにマップし、4 つのパス設定を揃えておきます:

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' となります。両方とも 先頭スラッシュなし で記述してください:ビューアが自動でスラッシュを付加するため、'/doconut' と書くと //doconut/DocImage.axd になり、ブラウザは doconut というホスト名へのリクエストと解釈し、ビューアは「The viewer handler could not be reached」というエラーで失敗します。

ページにビューアを追加

Viewer はページに必須のコアです。その描画領域は 2 つの入れ子になった div を使用します:

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

ツールバー、モジュールマウント、Viewer の表面を 1 つのページ構成として扱います。Search と Annotation は埋め込みリボンをオプションのマウントに注入しますが、これらのモジュールは単体では使用されません。常に同じページ上の Viewer にアタッチされます。Doconut.TestApp と Doconut.TestApp.Distributed と同じ順序で使用してください:

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- Viewer のナビゲーション、ズーム、検索、注釈ボタン -->
</nav>

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

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

ビューア資産を参照

Razor ビューでは、注入された Viewer サービスが依存順序でビューアの <link> と <script> タグを出力します — ウィジェットは jQuery プラグインなので、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 は必須のコアフラグです。Search または Annotation リボンの例をそれらなしで公開しないでください。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 のリファレンスアプリケーションは、以下の部品を 1 ページにまとめてインストールします:

パッケージの部品要件接続方法
Viewer リソース、マウント、そして objViewer必須コア文書レンダラ
Viewer ツールバーリファレンス構成で必須ホストマークアップ;ボタンは同じ objViewer を呼び出す
Search リボンオプション、ライセンスモジュールdoconutSearchBar(...).attach(objViewer)
Annotation リボンオプション、ライセンスモジュール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 ツールバー、Search/Annotation のマウント、そして Viewer のマウントを一緒にレンダリングする。
  3. Viewer とライセンスモジュール用のスクリプトを出力する。
  4. ホストアプリケーションの viewerToolbar.js をロードする。
  5. docViewer を初期化し、生成された objViewer を保持する。
  6. 各ライセンスされた Search または Annotation リボンを初期化する。
  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>

両方の埋め込みコンポーネントはそれぞれ独自の Ribbon DOM を生成します。Search には Find、Options、Results のグループがあり、Annotation には作成ツール、スタイルコントロール、保存アクション、オプションのエクスポート/画像アクションが含まれます。各バーは open(), close(), reset(), isOpen() を公開しており、作成後は必ず一度だけ attach(objViewer) を呼び出してください。

上記の例は、起動を最小限に抑えるためにオプションのホストコールバックや Annotation のエクスポート/画像エンドポイントを省略しています。完全な機能別セットアップについては 検索 と アノテーション を参照するか、ホスト所有の Viewer ツールバーをスタイル変更または置き換える場合は カスタムテーマ をご覧ください。

ドキュメントを開く

サーバー側は 1 つのエンドポイントです。注入された 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

トークンはベアラークレデンシャルとして扱い、決してログに記録せず、永続化せず、ウィジェットにのみ渡してください。トークンはサーバー上のライブドキュメントセッションを識別し、そのセッションが期限切れになると機能しなくなります — 新しいトークンを取得するにはドキュメントを再度開いてください。

実行方法

wwwroot/files/Sample.pdf に PDF を配置し、dotnet run を実行して、ウィジェットをホストするページを開きます。最初のページがビューアに表示され、左側にサムネイルパネルが表示されます。表示されない場合は、トラブルシューティング を参照してください。

ライセンスなしで得られるもの

ライセンスがない場合でも例外は発生しません。ビューアは通常通りレンダリングしますが、すべてのページに評価用の透かしが表示されます。Doconut がライセンスを取得する方法と取得後の変更点については、ライセンス設定 を参照してください。

このページは役に立ちましたか?