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

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

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

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

AddDoconut() registers the services; UseDoconutResources() and UseDoconut() wire the middleware. The resources call must come first. The session calls are required too — Doconut's default document security validates every page request against ASP.NET session state. Session state needs a cache: AddDistributedMemoryCache() provides one. An MVC or Razor Pages app already registers it, but a minimal-API app does not, and without it the app stops at startup with Unable to resolve service for type 'IDistributedCache'. Already registered Doconut during Installation? Skip ahead to the next section.

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();

For a production-style path layout, map the document middleware to an explicit branch and keep the four path settings aligned:

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 เป็นหัวใจหลักที่จำเป็นของหน้า พื้นผิวการเรนเดอร์ของมันใช้ div ซ้อนกันสองชั้น:

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

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

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 เป็นแฟล็กหลักที่จำเป็น อย่าเผยแพร่ตัวอย่างริบบิ้น 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 จะติดตั้งส่วนต่อไปนี้พร้อมกันในหนึ่งหน้า:

ส่วนของแพ็กเกจความต้องการวิธีการเชื่อมต่อ
ทรัพยากร Viewer, การเมานท์, และ objViewerจำเป็นCore document renderer
แถบเครื่องมือ Viewerจำเป็นในโครงสร้างอ้างอิงHost markup; buttons call the same 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. สร้าง CSS สำหรับ Viewer และโมดูลที่มีลิขสิทธิ์.
  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 เดียวกัน. ค่าการร้องขอ 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 มีกลุ่ม Find, Options, และ Results. 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) จะลบเซสชันที่แคชไว้ทันที, ปิดการทำงานของเอนจินการเรนเดอร์, ลบเครื่องหมายความปลอดภัย, และเพิกถอนโทเคน การหมดอายุแบบเลื่อนเวลา (sliding expiration) จะทำความสะอาดเช่นเดียวกันในภายหลัง, แต่แนะนำให้ปิดอย่างชัดเจนสำหรับเอกสารขนาดใหญ่

ขั้นตอนการร้องขอที่สมบูรณ์คือ:

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, แล้วเปิดหน้าที่โฮสต์วิดเจ็ต หน้าแรกจะเรนเดอร์ในตัวดูพร้อมแผงรูปย่อทางซ้าย หากไม่แสดงผล, ดูที่ Troubleshooting

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

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

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