התחלה מהירה

הצגת המסמך הראשון שלך בדקות

ההדרכה הזו מובילה אפליקציית ASP.NET Core מקובץ Program.cs ריק עד למסמך שמוצג בדפדפן: רישום השרת, חבילת הצופה המלאה (סרגל כלים של הצופה, הרכבה של הצופה, ורצועות חיפוש/הערות אופציונליות), הפניות למשאבים, אתחול הלקוח, פתיחת המסמך והפעלה.

הגדרת השרת

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

לפריסה בסגנון ייצור, מיפוי המידלוור של המסמך לסניף מפורש ושמירת ארבעת הגדרות הנתיב מתואמות:

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 הוא הליבה הדרושה של העמוד. משטח הרינדור שלו משתמש בשני 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 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 המלאה, בקש יחד את משאבי הצופה והמודול:

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, ו‑instance של 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 () {
        // 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 6 מתקינים את החלקים הבאים יחד בעמוד אחד:

חלק מהחבילהדרישהאיך זה מחובר
משאבי Viewer, הרכבה, ו‑objViewerחובהמנוע רינדור המסמך הליבה
סרגל כלים של Viewerחובה בהרכבת ההפניהסימון מארח; כפתורים קוראים לאותו objViewer
רצועת חיפושמודול רישיון אופציונליdoconutSearchBar(...).attach(objViewer)
רצועת הערותמודול רישיון אופציונליdoconutAnnotationBar(...).attach(objViewer)

למרות שסרגל הכלים הראשי של Viewer הוא סימון מארח, הוא מותקן יחד עם ה‑Viewer וצריך לעולם לא להיות מתועד כ‑control מבודד. כך נשמרים הפריסה, התוויות, האייקונים, וכללי האימות תחת שליטת היישום שלך, בעוד שכל כפתור מניע את אותה מופע 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, הרכבות חיפוש/הערות, והרכבת Viewer יחד.
  3. הפק סקריפטים עבור ה‑Viewer והמודולים המורשים.
  4. טען את viewerToolbar.js של יישום המארח.
  5. אתחל docViewer ושמור את ה‑objViewer שנוצר.
  6. אתחל כל רצועת חיפוש או הערות מורשית.
  7. קרא attach(objViewer) על כל רצועה.
  8. פתח את המסמך ושמור את האסימון שלו עבור בקשות Viewer והמודול.

Doconut.TestApp.Distributed שומר על הרכב UI זהה ועוזר סרגל כלים זהה. ערך הבקשה 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. הערות מכילות את כלי הכתיבה, בקרות סגנון, פעולות שמירה, ו‑endpoints אופציונליים לייצוא/תמונות. הברים מציגים open(), close(), reset(), ו‑isOpen(); תמיד קרא attach(objViewer) פעם אחת לאחר יצירתם.

הדוגמה שלמעלה משאירה ללא תוספות קריאות חזרה של המארח ו‑endpoints לייצוא/תמונות של הערות כדי לשמור על אתחול מינימלי. ראה את חיפוש ואת הערות עבור ההגדרה המלאה של תכונה ספציפית, או את ערכות נושא מותאמות כדי לעצב או להחליף את סרגל הכלים של Viewer שבבעלות המארח.

פתיחת מסמך

צד השרת הוא נקודת קצה אחת: שירות Viewer המוזרק פותח את המסמך ומחזיר אסימון סשן.

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

הלקוח משיג את האסימון הזה ומעביר אותו לווידג'ט עם 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) מסיר מיידית את הסשן המוטמן, משחרר את מנוע הרינדור, מוחק את סימן האבטחה שלו, ומבטל את האסימון. פקיעת תוקף משולשת מבצעת את הניקוי באותו אופן, אך סגירה מפורשת מומלצת למסמכים גדולים.

זרימת הבקשה המלאה היא:

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, ופתח את העמוד שמארח את הווידג'ט. העמוד הראשון יוצג בצופה, עם פאנל תמונות ממוזערות משמאל. אם זה לא קורה, ראה את פתרון בעיות.

מה שמקבלים ללא רישיון

חוסר רישיון אינו גורם לשגיאה. הצופה מציג כרגיל, אך כל עמוד מציג סימן מים של הערכה. ראה את הגדרת רישיון כדי להבין איך Doconut מוצא רישיון ומה משתנה ברגע שהוא נמצא.

האם דף זה היה מועיל?