התחלה מהירה

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

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

היישום המארח חייב למקד זמן ריצה שתומך ב‑netstandard2.1 — .NET Core 3.0 או גרסה מאוחרת יותר. היישומים הרפרנסיים שנשלחו עם גרסה זו ממקדים net8.0.

הגדרת השרת

AddDoconut() רושמת את השירותים; UseDoconutResources() ו‑UseDoconut() מחברים את ה‑middleware. קריאת המשאבים חייבת לבוא ראשונה. קריאות הסשן נדרשות גם הן — האבטחה המוגדרת של 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(); // session state צריך מטמון (MVC/Razor Pages כבר מוסיף אחד)
builder.Services.AddSession(); // אבטחת המסמך של Doconut מתבססת על session state של ASP.NET

app.UseSession();          // קרא UseSession() לפני UseDoconut()
app.UseDoconutResources(); // חייב להיות רשום לפני UseDoconut()
app.UseDoconut();

היישום הרפרנסי משמר את צורת הבקשה ההיסטורית של DocImage.axd ומרכיב את ה‑middleware של המסמך על ענף תואם. זו הסידור שיש להעתיק כאשר אתה ממשיך עם יישום .NET Standard קיים, מכיוון שהלקוח ממשיך לקרוא לאותו URL שהוא תמיד השתמש בו:

csharp
builder.Services.AddDoconut(options =>
{
    options.UnsafeMode      = false;   // השאר את בדיקת הסשן של Doconut מופעלת
    options.ShowDoconutInfo = false;
});
builder.Services.AddDistributedMemoryCache(); // session state צריך מטמון (MVC/Razor Pages כבר מוסיף אחד)
builder.Services.AddSession(options =>
{
    options.IdleTimeout        = TimeSpan.FromSeconds(3600);
    options.Cookie.HttpOnly    = true;
    options.Cookie.IsEssential = true;
});

// נדרש לקידודים מרובי בתים בכמה פורמטים של מסמכים.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseDoconutResources();   // לפני ענף המסמך
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'.

הוספת הצופה לעמוד

הצופה הוא הליבה הנדרשת של העמוד. משטח הרינדור שלו משתמש בשניים מקוננים של divs:

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

התייחס לסרגל הכלים, הרכבות המודול, ומשטח הצופה כקומפוזיציית עמוד אחת. חיפוש והערה מכניסים את הרצועות המשובצות שלהם למקומות אופציונליים, אך מודולים אלה לעולם אינם עצמאיים: הם תמיד מצורפים לצופה באותו העמוד.

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>

הפנה למקורות הצופה

ב‑view של Razor, השירות המוזרק 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
}))

להשגת חבילת הצופה המלאה, בקש את משאבי הצופה והמודול יחד:

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 הם דגלים ליבתיים מחויבים. לעולם אל תפרסם דוגמת חיפוש או סרט אנוטציה ללא הם, הרכבת הצופה, ומופע של docViewer. ReferenceCss ו‑ReferenceScripts משמיטים משאבי מודול אופציונלי כאשר הרישיון הנוכחי אינו מעניק את היכולת; הצופה הליבה עדיין מתחיל.

קריאות אלו מחליפות קבצי סקריפט שהועתקו ידנית. אם היישום עדיין טוען את 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 () {
        // הדפים נראים; בטוח להסתיר כאן את סמן הטעינה
    },
    // העבר אירועי מחזור החיים של ההערה לריבון המוטמע כאשר הוא קיים
    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 מועברים באובייקט האפשרויות. הצופה הקודם ייצר שמות פונקציות גלובליות ממזהה המכולה וקרא להם; זה אינו עושה זאת. מטפל שהוגדר רק כפונקציה גלובלית לעולם לא ירוץ, ולא מדווח דבר.
  • ResPath מצביע על קידומת המשאבים, ולא על שורש היישום. עם ה‑ResourcesPath ברירת המחדל, הערך הוא doconut-res/images. אם שינית את ResourcesPath, השתמש בקידומת שלך במקום זאת.

שניהם נכשלים בשקט — ראה את פתרון בעיות ואת מדריך ההגירה.

הכתיב של האפשרויות הוא באמת מעורב — showThumbs, autoLoad ו‑pageZoom הם camelCase, אך FitType, BasePath ו‑ResPath הם PascalCase. אין כלל עקבי; אם תכתוב את ה‑casing בצורה שגויה, האפשרות תתעלם בשקט (הווידג'ט יחזור לברירת המחדל במקום לזרוק שגיאה).

הרכבת חבילת הצופה המלאה

שתי יישומי ההפניה מתקינים את החלקים הבאים יחד בדף אחד:

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

למרות שסרט הכלים הראשי של הצופה הוא סימון מארח, הוא מותקן לצד הצופה ולעולם לא צריך להיות מתועד כ‑control מבודד. זה שומר על הפריסה, התוויות, האייקונים, וכללי ההרשאה תחת שליטת היישום שלך בעוד שכל כפתור מניע את אותו מופע של הצופה:

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

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

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

פתיחת מסמך

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

csharp
app.MapPost("/api/open", async (Viewer viewer) =>
{
    // הטוקן אינו קריא — העבר אותו ל‑widget, אל תתעד או תשמר אותו.
    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

התייחס לאסימון כלאישור נושא: אל תתעד אותו, אל תשמר אותו, העבר אותו רק לווידג'ט. הוא מזהה סשן מסמך חי על השרת ומפסיק לעבוד כאשר הסשן פג — פתח את המסמך מחדש כדי לקבל אסימון חדש.

הפעלת המערכת

הציב קובץ PDF בwwwroot/files/Sample.pdf, הפעל dotnet run, ופתח את העמוד שמארח את הווידג'ט. העמוד הראשון מוצג בצופה, עם לוח תמונות ממוזערות בצד שמאל. אם זה לא קורה, ראה פתרון בעיות.

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

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

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