เริ่มต้นอย่างเร็ว

แสดงเอกสารแรกของคุณในไม่กี่นาที

ขั้นตอนนี้จะพาแอป ASP.NET Core จากไฟล์ Program.cs ว่างเปล่า ไปสู่การแสดงเอกสารในเบราว์เซอร์: การลงทะเบียนเซิร์ฟเวอร์, แพ็คเกจ Viewer ครบชุด (แถบเครื่องมือ Viewer, การเมานท์ Viewer, และริบบิ้น Search/Annotation ทางเลือก), การอ้างอิงทรัพยากร, การเริ่มต้นไคลเอนต์, การเปิดเอกสาร, และการดำเนินการ

แอปโฮสต์ต้องกำหนดเป้าหมายเป็นรันไทม์ที่รองรับ netstandard2.1 — .NET Core 3.0 หรือใหม่กว่า แอปอ้างอิงที่มาพร้อมกับรุ่นนี้กำหนดเป้าหมายเป็น net8.0

การตั้งค่าเซิร์ฟเวอร์

AddDoconut() ลงทะเบียนบริการ; UseDoconutResources() และ UseDoconut() เชื่อมต่อมิดเดิลแวร์ การเรียกใช้ทรัพยากรต้องมาก่อน การเรียกใช้เซสชันก็จำเป็นเช่นกัน — ความปลอดภัยเอกสาร Doconut ตรวจสอบทุกคำขอหน้าโดยอ้างอิงสถานะเซสชันของ ASP.NET สถานะเซสชันต้องการแคช (MVC/Razor Pages มีการเพิ่มไว้แล้ว) AddDistributedMemoryCache() จะจัดให้แล้ว แอป MVC หรือ Razor Pages จะลงทะเบียนให้โดยอัตโนมัติแล้ว แต่แอป minimal‑API จะไม่ได้ลงทะเบียน หากไม่มีแคช แอปจะหยุดทำงานที่การเริ่มต้นด้วยข้อความ Unable to resolve service for type 'IDistributedCache' ได้ลงทะเบียน Doconut แล้วหรือยังในส่วน Installation? ข้ามไปยังส่วนต่อไป

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddDistributedMemoryCache(); // session state needs a cache (MVC/Razor Pages already add one)
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();

แอปอ้างอิงยังคงรักษารูปแบบการร้องขอ DocImage.axd แบบดั้งเดิมและเมานท์มิดเดิลแวร์เอกสารบนสาขาที่ตรงกัน นี่คือการจัดเรียงที่ควรคัดลอกเมื่อคุณกำลังย้ายแอป .NET Standard ที่มีอยู่ต่อไป เนื่องจากไคลเอนต์ยังคงเรียก URL เดิมเสมอ:

csharp
builder.Services.AddDoconut(options =>
{
    options.UnsafeMode      = false;   // keep Doconut's session check enabled
    options.ShowDoconutInfo = false;
});
builder.Services.AddDistributedMemoryCache(); // session state needs a cache (MVC/Razor Pages already add one)
builder.Services.AddSession(options =>
{
    options.IdleTimeout        = TimeSpan.FromSeconds(3600);
    options.Cookie.HttpOnly    = true;
    options.Cookie.IsEssential = true;
});

// Required for multi-byte encodings in some document formats.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

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

UseDoconut() ไม่รับพารามิเตอร์ใดในรุ่นนี้ — ทุกอย่างย้ายไปที่ AddDoconut() MiddlewarePath เป็นค่าประสานงาน; มันไม่ได้แมปสาขา ASP.NET Core ด้วยตัวเอง ResourcesPath ให้บริการบันเดิลฝังที่ /doconut-res และเส้นทางทรัพยากรรูปภาพของวิดเจ็ตจึงเป็น ResPath: 'doconut-res/images'

เพิ่ม Viewer ลงในหน้า

Viewer เป็นหัวใจที่จำเป็นของหน้า พื้นผิวการเรนเดอร์ของมันใช้ div ซ้อนกันสองระดับ:

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

ให้ถือว่าแถบเครื่องมือ, การเมานท์โมดูล, และพื้นผิว Viewer เป็นการประกอบหน้าเดียวกัน Search และ Annotation จะฉีดริบบิ้นฝังของพวกมันลงในการเมานท์ทางเลือก แต่โมดูลเหล่านั้นไม่เคยทำงานแยกจากกัน: พวกมันจะผูกติดกับ Viewer บนหน้าเดียวกันเสมอ

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 view, บริการ 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 เป็นแฟล็กหลักที่จำเป็นต้องมี อย่าเผยแพร่ตัวอย่างการค้นหา หรือริบบอนการอธิบายโดยไม่มีพวกมัน, การเมานท์ Viewer, และอินสแตนซ์ docViewer ReferenceCss และ ReferenceScripts จะละเว้นทรัพยากรของโมดูลที่เป็นตัวเลือกเมื่อใบอนุญาตปัจจุบันไม่ได้ให้ความสามารถนั้น; Viewer หลักยังคงทำงานได้

การเรียกเหล่านี้แทนที่ไฟล์สคริปต์ที่คัดลอกด้วยมือ หากแอปพลิเคชันยังโหลด docViewer.js, documentLinks.js หรือ docViewer.UI.js ของตนเอง, ให้ลบแท็กเหล่านั้นออกเมื่อหน้าเว็บทำงานจากทรัพยากรที่ถูกสร้าง — ดู คู่มือการย้าย

เริ่มต้นตัวดู

วิดเจ็ตฝั่งไคลเอนต์เป็นปลั๊กอิน jQuery นี่คือตัวอย่างชุดตัวเลือกการเริ่มต้นที่ใช้งานได้จริง (ไม่ใช่โค้ดตัวอย่าง):

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 () {
        // หน้าแสดงผลแล้ว; สามารถซ่อนสปินเนอร์การโหลดได้ที่นี่
    },
    // ส่งต่อเหตุการณ์วงจรชีวิตของ annotation ไปยังริบบอนที่ฝังอยู่เมื่อมีอยู่
    onAnnLoaded:    () => annBar?.handleAnnLoaded(),
    onAnnSaved:     () => annBar?.handleAnnSaved(),
    onAnnSaveError: () => annBar?.handleAnnSaveError(),
    onAnnClosed:    () => annBar?.handleAnnClosed(),
    onError: function (message) {
        console.error('Doconut viewer error:', message);
    }
});

สองรายละเอียดที่กำหนดว่าหน้านี้จะทำงานหรือไม่:

  • การเรียกกลับคือ ตัวเลือก onViewerReady, onViewerBusy, onCopy, และ onAutoLoadStatus จะถูกส่งเข้าในอ็อบเจ็กต์ตัวเลือก ตัวก่อนหน้านี้ของ Viewer ดึงชื่อฟังก์ชันระดับโลกจาก id ของคอนเทนเนอร์และเรียกใช้; ตัวนี้ไม่ทำเช่นนั้น ตัวจัดการที่ประกาศเป็นฟังก์ชันระดับโลกจะไม่ทำงานและไม่มีการรายงานใด ๆ
  • ResPath ชี้ไปที่คำนำหน้าทรัพยากร, ไม่ใช่รากของแอปพลิเคชัน ด้วยค่าเริ่มต้น ResourcesPath ค่าจะเป็น doconut-res/images หากคุณเปลี่ยน ResourcesPath ให้ใช้คำนำหน้าของคุณเองแทน

ทั้งสองกรณีจะล้มเหลวแบบเงียบ — ดู การแก้ไขปัญหา และ คู่มือการย้าย

การตั้งค่าตัวพิมพ์ใหญ่‑เล็กของตัวเลือกผสมกันจริง ๆ — showThumbs, autoLoad, และ pageZoom เป็น camelCase, แต่ FitType, BasePath, และ ResPath เป็น PascalCase. ไม่มีกฎที่สอดคล้อง; หากตั้งค่าตัวพิมพ์ใหญ่‑เล็กผิด ตัวเลือกจะถูกละเว้นโดยเงียบ (วิดเจ็ตจะกลับไปใช้ค่าเริ่มต้นแทนการโยนข้อผิดพลาด)

ประกอบแพคเกจ Viewer ที่สมบูรณ์

ทั้งสองแอปอ้างอิงติดตั้งส่วนต่อไปนี้พร้อมกันในหน้าเดียว:

ส่วนของแพคเกจความต้องการวิธีการเชื่อมต่อ
ทรัพยากร 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>

รักษาลำดับการเริ่มต้นแพคเกจที่ใช้โดยทั้งสองแอปอ้างอิง:

  1. ส่งออก CSS สำหรับ Viewer และโมดูลที่มีใบอนุญาต
  2. แสดงแถบเครื่องมือ Viewer, การเมานท์ริบบอนการค้นหา/การอธิบาย, และการเมานท์ Viewer พร้อมกัน
  3. ส่งออกสคริปต์สำหรับ Viewer และโมดูลที่มีใบอนุญาต
  4. โหลดสคริปต์ช่วยเหลือแถบเครื่องมือของแอปโฮสต์เอง
  5. เริ่มต้น docViewer และเก็บ objViewer ที่ได้
  6. เริ่มต้นแต่ละริบบอนการค้นหา หรือการอธิบาย ที่มีใบอนุญาต
  7. เรียก attach(objViewer) บนทุกริบบอน
  8. เปิดเอกสารและเก็บ token สำหรับการร้องขอ 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 ของริบบอนของตนเอง การค้นหามีกลุ่ม Find, Options, และ Results ส่วนการอธิบายมีเครื่องมือเขียน, ควบคุมสไตล์, การบันทึก, และการส่งออก/รูปภาพแบบตัวเลือก แถบเหล่านี้เปิดเผยเมธอด open(), close(), reset(), และ isOpen(); ควรเรียก attach(objViewer) หนึ่งครั้งหลังจากสร้างเสร็จ

ตัวอย่างข้างต้นละเว้นคอลแบ็กโฮสต์แบบตัวเลือกและจุดสิ้นสุดการส่งออก/รูปภาพของการอธิบาย เพื่อให้การเริ่มต้นเป็นอย่างน้อยที่สุด ดู การค้นหา และ การอธิบาย สำหรับการตั้งค่าฟีเจอร์เฉพาะอย่างสมบูรณ์, หรือ ธีมแบบกำหนดเอง เพื่อจัดรูปแบบหรือแทนที่แถบเครื่องมือ 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);
    });

ไม่มีสิ่งใดต้องเตรียมล่วงหน้าก่อนการเปิดครั้งแรก หากคุณกำลังพัฒนาแอปพลิเคชันต่อไป ให้ลบการเรียก InitCache() ใด ๆ และแฟล็กที่ป้องกันมันออก — วิธีการนี้ไม่มีอยู่แล้ว

ปิดเอกสาร

เรียก 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

ปฏิบัติโทเค็นเหมือนกับข้อมูลประจำตัวแบบ bearer: อย่าบันทึกลงบันทึก, อย่าจัดเก็บไว้, ส่งให้วิดเจ็ตเท่านั้น มันระบุเซสชันเอกสารที่กำลังทำงานบนเซิร์ฟเวอร์และจะหยุดทำงานเมื่อเซสชันนั้นหมดอายุ — เปิดเอกสารใหม่เพื่อรับโทเค็นใหม่

เรียกใช้

วางไฟล์ PDF ที่ wwwroot/files/Sample.pdf, รัน dotnet run, แล้วเปิดหน้าที่โฮสต์วิดเจ็ต หน้าแรกจะแสดงในตัวดูพร้อมแผงภาพย่อทางด้านซ้าย หากไม่แสดง, ดูที่ การแก้ไขปัญหา.

สิ่งที่คุณจะได้รับโดยไม่มีใบอนุญาต

การไม่มีใบอนุญาตจะไม่ทำให้เกิดข้อผิดพลาด ตัวดูจะแสดงผลตามปกติ แต่ทุกหน้าจะมีลายน้ำการประเมิน ดูที่ การตั้งค่าใบอนุญาต เพื่อเรียนรู้ว่า Doconut ค้นหาใบอนุญาตอย่างไรและจะมีการเปลี่ยนแปลงอะไรบ้างเมื่อพบแล้ว

หน้านี้เป็นประโยชน์หรือไม่?