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

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

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

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

AddDoconut() ลงทะเบียนบริการ; UseDoconutResources() และ UseDoconut() เชื่อมต่อมิดเดิลแวร์. การเรียกใช้ทรัพยากรต้องมาก่อน. การเรียกใช้เซสชันก็จำเป็นเช่นกัน — ระบบความปลอดภัยเอกสารของ Doconut ตรวจสอบคำขอแต่ละหน้าโดยอ้างอิงสถานะเซสชันของ ASP.NET. ได้ลงทะเบียน Doconut แล้วในส่วน การติดตั้ง? ข้ามไปยังส่วนถัดไปได้เลย.

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
});
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();

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

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = Path.Combine(AppContext.BaseDirectory, "Doconut.Viewer.lic");
    options.MiddlewarePath = "/doconut";
    options.ResourcesPath = "/doconut-res";
    options.UnsafeMode = false;
});
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 ลงในหน้า

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>

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

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

เริ่มต้น Viewer

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

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 () {
        // pages are visible; safe to hide a loading spinner here
    },
    // Forward annotation lifecycle events to the embedded ribbon when it is present.
    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จำเป็นตัวเรนเดอร์เอกสารหลัก
แถบเครื่องมือ Viewerจำเป็นในโครงสร้างอ้างอิงมาร์กอัปโฮสต์; ปุ่มเรียก objViewer เดียวกัน
ริบบิ้น Searchทางเลือก, โมดูลที่มีใบอนุญาตdoconutSearchBar(...).attach(objViewer)
ริบบิ้น Annotationทางเลือก, โมดูลที่มีใบอนุญาต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. เปิดเอกสารและเก็บ token เพื่อใช้ในคำขอโมดูล.

Doconut.TestApp.Distributed รักษาการจัดวาง UI นี้อย่างแม่นยำพร้อมตัวช่วยแถบเครื่องมือ Viewer เดียวกัน. ค่าการร้องขอ access เพิ่มเติมและการตั้งค่า retry การเรนเดอร์แบบอะซิงโครนัสเป็นของการส่งผ่านแบบกระจาย; พวกมันไม่เปลี่ยนแปลงวิธีการประกอบ 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) หนึ่งครั้งหลังจากสร้างเสมอ.

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

เปิดเอกสาร

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

csharp
app.MapPost("/api/open", async (Viewer viewer) =>
{
    // The token is opaque — hand it to the widget, never log or persist it.
    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 หรือเปิดเอกสารทดแทน. ในเวิร์กโฟลว์ที่ขับเคลื่อนโดยเซิร์ฟเวอร์, 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, แล้วเปิดหน้าที่โฮสต์วิดเจ็ต. หน้าแรกจะเรนเดอร์ใน viewer พร้อมแผงรูปย่อทางซ้าย. หากไม่แสดง, ดู การแก้ไขปัญหา.

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

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

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