Memulai Cepat

Render dokumen pertama Anda dalam hitungan menit

Walkthrough ini membawa aplikasi ASP.NET Core dari Program.cs yang kosong hingga dokumen yang dirender di peramban: pendaftaran server, paket Viewer lengkap (toolbar Viewer, mount Viewer, dan pita Search/Annotation opsional), referensi aset, inisialisasi klien, pembukaan dokumen, dan eksekusi.

Aplikasi host harus menargetkan runtime yang mendukung netstandard2.1 — .NET Core 3.0 atau lebih baru. Aplikasi referensi yang disertakan dengan rilis ini menargetkan net8.0.

Server setup

AddDoconut() mendaftarkan layanan; UseDoconutResources() dan UseDoconut() menghubungkan middleware. Panggilan sumber daya harus datang pertama. Panggilan sesi juga diperlukan — keamanan dokumen Doconut memvalidasi setiap permintaan halaman terhadap status sesi ASP.NET. Status sesi membutuhkan cache: AddDistributedMemoryCache() menyediakan satu. Aplikasi MVC atau Razor Pages sudah mendaftarkannya, tetapi aplikasi minimal-API tidak, dan tanpa itu aplikasi berhenti saat startup dengan Unable to resolve service for type 'IDistributedCache'. Sudah mendaftarkan Doconut selama Installation? Lewati ke bagian berikutnya.

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddDistributedMemoryCache(); // status sesi membutuhkan cache (MVC/Razor Pages sudah menambahkannya)
builder.Services.AddSession(); // keamanan dokumen Doconut bergantung pada status sesi ASP.NET

app.UseSession();          // panggil UseSession() sebelum UseDoconut()
app.UseDoconutResources(); // harus didaftarkan sebelum UseDoconut()
app.UseDoconut();

Aplikasi referensi mempertahankan bentuk permintaan historis DocImage.axd dan memasang middleware dokumen pada cabang yang cocok. Ini adalah susunan yang harus disalin ketika Anda membawa aplikasi .NET Standard yang ada ke depan, karena klien terus memanggil URL yang sama seperti sebelumnya:

csharp
builder.Services.AddDoconut(options =>
{
    options.UnsafeMode      = false;   // pertahankan pemeriksaan sesi Doconut tetap aktif
    options.ShowDoconutInfo = false;
});
builder.Services.AddDistributedMemoryCache(); // status sesi membutuhkan cache (MVC/Razor Pages sudah menambahkannya)
builder.Services.AddSession(options =>
{
    options.IdleTimeout        = TimeSpan.FromSeconds(3600);
    options.Cookie.HttpOnly    = true;
    options.Cookie.IsEssential = true;
});

// Diperlukan untuk enkoding multi-byte dalam beberapa format dokumen.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseDoconutResources();   // SEBELUM cabang dokumen
app.MapWhen(
    context => context.Request.Path.Value?.EndsWith("DocImage.axd", StringComparison.OrdinalIgnoreCase) == true,
    branch => branch.UseDoconut());

UseDoconut() tidak mengambil opsi apa pun dalam rilis ini — semuanya dipindahkan ke AddDoconut(). MiddlewarePath adalah nilai koordinasi; ia tidak memetakan cabang ASP.NET Core secara mandiri. ResourcesPath menyajikan bundel tersemat di /doconut-res, dan jalur sumber daya gambar widget oleh karena itu ResPath: 'doconut-res/images'.

Add the viewer to a page

Viewer adalah inti yang diperlukan dari halaman. Permukaan render-nya menggunakan dua div bersarang:

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

Perlakukan toolbar, mount modul, dan permukaan Viewer sebagai satu komposisi halaman. Search dan Annotation menyuntikkan pita tersemat mereka ke dalam mount opsional, tetapi modul-modul tersebut tidak pernah berdiri sendiri: mereka selalu terpasang pada Viewer di halaman yang sama.

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>

Referensi aset Viewer

Di dalam tampilan Razor, layanan Viewer yang disuntikkan menghasilkan tag <link> dan <script> penampil dalam urutan ketergantungan — widget adalah plugin jQuery, sehingga jQuery harus dimuat sebelum skrip penampil:

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

Untuk paket Viewer lengkap, minta sumber daya Viewer dan modul secara bersamaan:

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 dan IncludeViewerScripts adalah flag inti yang wajib. Jangan pernah mempublikasikan contoh Search atau Annotation Ribbon tanpa mereka, mount Viewer, dan sebuah instance docViewer. ReferenceCss dan ReferenceScripts menghilangkan sumber daya modul opsional ketika lisensi saat ini tidak memberikan kemampuan tersebut; core Viewer tetap berjalan.

Pemanggilan ini menggantikan file skrip yang disalin secara manual. Jika aplikasi masih memuat docViewer.js, documentLinks.js, atau docViewer.UI.js miliknya, hapus tag tersebut setelah halaman berfungsi dengan sumber daya yang dihasilkan — lihat panduan migrasi.

Inisialisasi Viewer

Widget sisi klien adalah plugin jQuery. Ini adalah sekumpulan opsi inisialisasi nyata yang minimal (bukan pseudocode):

javascript
let searchBar = null;
let annBar = null;

const objViewer = $('#div_ctlDoc').docViewer({
    showThumbs: true,
    autoLoad:   false,
    pageZoom:   100,
    FitType:    'width',
    BasePath:   '/',
    ResPath:    'doconut-res/images',
    onViewerReady: function () {
        // halaman terlihat; aman untuk menyembunyikan spinner pemuatan di sini
    },
    // Meneruskan peristiwa siklus hidup anotasi ke ribbon yang disematkan ketika ada.
    onAnnLoaded:    () => annBar?.handleAnnLoaded(),
    onAnnSaved:     () => annBar?.handleAnnSaved(),
    onAnnSaveError: () => annBar?.handleAnnSaveError(),
    onAnnClosed:    () => annBar?.handleAnnClosed(),
    onError: function (message) {
        console.error('Doconut viewer error:', message);
    }
});

Dua detail menentukan apakah halaman ini berfungsi sama sekali:

  • Callbacks adalah opsi. onViewerReady, onViewerBusy, onCopy, dan onAutoLoadStatus diteruskan dalam objek opsi. Viewer sebelumnya menurunkan nama fungsi global dari id kontainer dan memanggilnya; yang ini tidak. Penangan yang dideklarasikan hanya sebagai fungsi global tidak pernah dijalankan, dan tidak ada yang dilaporkan.
  • ResPath menunjuk ke prefiks sumber daya, bukan akar aplikasi. Dengan ResourcesPath default, nilai tersebut adalah doconut-res/images. Jika Anda mengubah ResourcesPath, gunakan prefiks Anda sendiri.

Kedua hal ini gagal secara diam-diam — lihat Pemecahan Masalah dan panduan migrasi.

Penulisan huruf pada opsi memang campur — showThumbs, autoLoad, dan pageZoom menggunakan camelCase, tetapi FitType, BasePath, dan ResPath menggunakan PascalCase. Tidak ada aturan konsisten; jika penulisan huruf salah, opsi akan diabaikan secara diam-diam (widget akan kembali ke nilai default alih-alih melempar error).

Susun paket Viewer lengkap

Kedua aplikasi referensi memasang bagian-bagian berikut bersama-sama pada satu halaman:

Bagian paketKebutuhanCara terhubung
Sumber daya Viewer, mount, dan objViewerWajibRenderer dokumen inti
Toolbar ViewerWajib dalam komposisi referensiMarkup host; tombol memanggil objViewer yang sama
Ribbon PencarianOpsional, modul berlisensidoconutSearchBar(...).attach(objViewer)
Ribbon AnotasiOpsional, modul berlisensidoconutAnnotationBar(...).attach(objViewer)

Walaupun toolbar Viewer utama adalah markup host, ia dipasang bersamaan dengan Viewer dan tidak boleh pernah didokumentasikan sebagai kontrol terisolasi. Ini menjaga tata letak, label, ikon, dan aturan otorisasi di bawah kontrol aplikasi Anda sementara setiap tombol mengendalikan instance Viewer yang sama:

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>

Pertahankan urutan inisialisasi paket yang digunakan oleh kedua aplikasi referensi:

  1. Hasilkan CSS untuk Viewer dan modul berlisensi.
  2. Render toolbar Viewer, mount Pencarian/Anotasi, dan mount Viewer secara bersamaan.
  3. Hasilkan skrip untuk Viewer dan modul berlisensi.
  4. Muat skrip bantuan toolbar milik aplikasi host.
  5. Inisialisasi docViewer dan simpan objViewer yang dihasilkan.
  6. Inisialisasi setiap Ribbon Pencarian atau Anotasi yang berlisensi.
  7. Panggil attach(objViewer) pada setiap Ribbon.
  8. Buka dokumen dan simpan tokennya untuk permintaan Viewer dan modul.

Penjaga sisi server penting: ketika kemampuan opsional tidak tersedia, skripnya tidak dihasilkan, sehingga fungsi plug-in jQuery-nya tidak ada.

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>

Kedua komponen yang disematkan menghasilkan DOM Ribbon masing-masing. Pencarian berisi grup Find, Options, dan Results. Anotasi berisi alat penulisan, kontrol gaya, aksi simpan, dan aksi ekspor/gambar opsional. Bar menampilkan open(), close(), reset(), dan isOpen(); selalu panggil attach(objViewer) sekali setelah membuatnya.

Contoh di atas menghilangkan callback host opsional dan endpoint ekspor/gambar Anotasi untuk menjaga startup minimal. Lihat Pencarian dan Anotasi untuk pengaturan fitur lengkap, atau Tema Kustom untuk menata atau mengganti toolbar Viewer milik host.

Membuka dokumen

Sisi server memiliki satu endpoint: layanan Viewer yang disuntik membuka dokumen dan mengembalikan token sesi. Membuka bersifat asynchronous pada rilis ini.

csharp
app.MapPost("/api/open", async (Viewer viewer) =>
{
    // Token bersifat opak — serahkan ke widget, jangan pernah mencatat atau menyimpannya.
    string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
    return Results.Ok(new { token });
});

Klien mengambil token tersebut dan menyerahkannya ke widget dengan objViewer.View(token):

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

Tidak ada yang perlu dipanaskan sebelum pembukaan pertama. Jika Anda melanjutkan aplikasi, hapus semua pemanggilan InitCache() dan flag yang melindunginya — metode tersebut tidak lagi ada.

Panggil objViewer.Close() ketika pengguna meninggalkan viewer atau membuka dokumen pengganti. Pada alur kerja yang dikendalikan server, viewer.CloseDocument(token) segera menghapus sesi yang di‑cache, membuang mesin rendering, menghapus penanda keamanannya, dan mencabut token. Kedaluwarsa bergulir pada akhirnya melakukan pembersihan yang sama, tetapi penutupan eksplisit disarankan untuk dokumen berukuran besar.

Alur permintaan yang selesai adalah:

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

Perlakukan token seperti kredensial bearer: jangan pernah mencatatnya, jangan pernah menyimpannya, serahkan hanya ke widget. Token mengidentifikasi sesi dokumen yang aktif di server dan tidak berfungsi lagi ketika sesi tersebut kedaluwarsa — buka kembali dokumen untuk mendapatkan token baru.

Jalankan

Letakkan file PDF di wwwroot/files/Sample.pdf, jalankan dotnet run, dan buka halaman yang menampung widget. Halaman pertama akan dirender di viewer, dengan panel thumbnail di sebelah kiri. Jika tidak muncul, lihat Pemecahan Masalah.

Apa yang Anda dapatkan tanpa lisensi

Lisensi yang hilang tidak menyebabkan pengecualian. Viewer merender secara normal, tetapi setiap halaman menampilkan watermark evaluasi. Lihat Pengaturan Lisensi untuk cara Doconut menemukan lisensi dan apa yang berubah setelahnya.

Apakah halaman ini membantu?