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

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

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

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

AddDoconut() ลงทะเบียนบริการ; UseDoconutResources() และ UseDoconut() เชื่อมต่อมิดเดิลแวร์. การเรียกใช้ resources ต้องมาก่อน. การเรียกใช้ session ก็จำเป็นเช่นกัน — ความปลอดภัยเอกสารเริ่มต้นของ Doconut ตรวจสอบคำขอแต่ละหน้าเทียบกับสถานะเซสชันของ ASP.NET. สถานะเซสชันต้องการแคช: AddDistributedMemoryCache() จะให้หนึ่งอัน. แอป MVC หรือ Razor Pages จะลงทะเบียนไว้แล้ว, แต่แอป minimal‑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();

สำหรับการจัดวางเส้นทางแบบการผลิต, ให้แมปมิดเดิลแวร์เอกสารไปยังสาขาเฉพาะและรักษาการตั้งค่าเส้นทางสี่ค่าให้สอดคล้องกัน:

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, และ viewer จะล้มเหลวด้วยข้อความ "The viewer handler could not be reached".

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

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

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

ให้ถือแถบเครื่องมือ, การเมานท์โมดูล, และพื้นผิว Viewer เป็นการจัดองค์ประกอบหน้าเดียวกัน. การค้นหาและบันทึกหมายเหตุจะฉีดริบบอนฝังของพวกเขาเข้าไปในการเมานท์แบบเลือก, แต่โมดูลเหล่านั้นไม่มีการทำงานแยกจากกัน: พวกมันจะผูกติดกับ 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>

อ้างอิงทรัพยากร Viewer

ใน Razor view, เซอร์วิส Viewer ที่ฉีดเข้ามาจะสร้างแท็ก <link> และ <script> ของ viewer ตามลำดับการพึ่งพา — วิดเจ็ตเป็นปลั๊กอิน jQuery, ดังนั้น jQuery ต้องโหลดก่อนสคริปต์ของ viewer:

csharp
@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 และโมดูลพร้อมกัน:

csharp
@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 8 จะติดตั้งส่วนต่อไปนี้พร้อมกันในหนึ่งหน้า:

ส่วนของแพคเกจความต้องการวิธีการเชื่อมต่อ
ทรัพยากร 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. ส่งออกทรัพยากร Viewer, Search, และ Annotation พร้อมกัน.
  2. เรนเดอร์แถบเครื่องมือ Viewer, การเมานท์ริบบิ้น, และการเมานท์ Viewer พร้อมกัน.
  3. เริ่มต้น docViewer ก่อน.
  4. สร้างริบบิ้นที่มีลิขสิทธิ์แต่ละอันและแนบเข้ากับ objViewer เดียวกันนั้น.
  5. เปิดเอกสารและเก็บโทเคนของมันสำหรับการร้องขอโมดูล.

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 ของริบบิ้นของตนเอง. ค้นหามีกลุ่ม Find, Options, และ Results. การอธิบายมีเครื่องมือสร้าง, ควบคุมสไตล์, การบันทึก, และการกระทำส่งออก/รูปภาพที่เป็นตัวเลือก. ริบบิ้นเหล่านี้เปิดเผยเมธอด open(), close(), reset(), และ isOpen(); ควรเรียก attach(objViewer) หนึ่งครั้งหลังจากสร้างมัน.

ตัวอย่างข้างต้นละเว้นการเรียกกลับของโฮสต์ที่เป็นตัวเลือกและจุดสิ้นสุดการส่งออก/รูปภาพของ Annotation เพื่อให้การเริ่มต้นเป็นอย่างน้อยที่สุด. ดูที่ ค้นหา และ การอธิบาย สำหรับการตั้งค่าฟีเจอร์ที่สมบูรณ์, หรือ ธีมที่กำหนดเอง เพื่อจัดรูปแบบหรือแทนที่แถบเครื่องมือ Viewer ที่เป็นของโฮสต์.

เปิดเอกสาร

ด้านเซิร์ฟเวอร์มีเพียงหนึ่งจุดเชื่อมต่อ: บริการ Viewer ที่ฉีดเข้ามาจะเปิดเอกสารและคืนค่า token ของเซสชัน

csharp
app.MapPost("/api/open", async (Viewer viewer) =>
{
    // Token นี้เป็นแบบไม่เปิดเผย — ส่งให้วิดเจ็ต, อย่าเก็บบันทึกหรือบันทึกลงฐานข้อมูล
    string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
    return Results.Ok(new { token });
});

ไคลเอนต์จะดึง 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) จะลบเซสชันที่แคชไว้ทันที, ปิดการทำงานของเอนจินการเรนเดอร์, ลบเครื่องหมายความปลอดภัย, และเพิกถอน 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

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

รันโปรเจกต์

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

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

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

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