クイックスタート

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

このハウツーでは、空の 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();          // UseSession() を UseDoconut() の前に呼び出す
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' となります。先頭スラッシュなしで両方を書いてください:Viewer が自動でスラッシュを付加するため、'/doconut' と書くと //doconut/DocImage.axd となり、ブラウザは doconut というホスト名へのリクエストと解釈し、"The viewer handler could not be reached" というエラーで Viewer が失敗します。

ページにビューアを追加

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

IncludeViewerCss と IncludeViewerScripts は必須のコアフラグです。Search または Annotation Ribbon の例をこれらなしで公開しないでください。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 です。一貫した規則はなく、ケースを間違えるとオプションは黙って無視されます(ウィジェットは例外を投げずにデフォルトにフォールバックします)。

完全なビューアパッケージの組み立て

.NET 8 のリファレンスアプリケーションは、以下のパーツを 1 ページにまとめてインストールします。

パッケージの部品要件接続方法
ビューアリソース、マウント、および objViewer必須コアドキュメントレンダラ
ビューアツールバーリファレンス構成で必須ホストマークアップ;ボタンは同じ objViewer を呼び出す
検索リボンオプション、ライセンスモジュールdoconutSearchBar(...).attach(objViewer)
注釈リボンオプション、ライセンスモジュールdoconutAnnotationBar(...).attach(objViewer)

メインのビューアツールバーはホストマークアップですが、ビューアと一緒にインストールされ、単独のコントロールとして文書化してはなりません。これにより、レイアウト、ラベル、アイコン、認可ルールがアプリケーション側で管理され、すべてのボタンが同じビューアインスタンスを操作します:

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. ビューア、検索、注釈のリソースをまとめて出力する。
  2. ビューアツールバー、リボンマウント、ビューアマウントを一緒にレンダリングする。
  3. docViewer を最初に初期化する。
  4. 各ライセンスリボンを作成し、同じ objViewer にアタッチする。
  5. ドキュメントを開き、モジュールリクエスト用にトークンを保持する。

Doconut.TestApp.Distributed はこの正確な UI 構成と同じビューアツールバーヘルパーを保持しています。追加の access リクエスト値と非同期レンダーリトライ設定は分散トランスポートに属し、ビューア、ツールバー、リボンの組み立て方法を変更するものではありません。

サーバー側のガードは重要です。オプション機能が利用できない場合、そのスクリプトは出力されず、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 を生成します。検索リボンは Find、Options、Results のグループを含みます。注釈リボンは作成ツール、スタイルコントロール、保存アクション、オプションのエクスポート/画像アクションを含みます。各バーは open()、close()、reset()、isOpen() を提供します;作成後は必ず一度だけ attach(objViewer) を呼び出してください。

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

ドキュメントを開く

サーバー側は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 がライセンスを取得する方法と取得後の変化については、ライセンス設定 を参照してください。

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