התחלה מהירה

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

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

הגדרת השרת

AddDoconut() רושמת את השירותים; UseDoconutResources() ו‑UseDoconut() מחברות את המידלוור. קריאת המשאבים חייבת לבוא ראשונה. קריאות הסשן נדרשות גם הן — אבטחת המסמכים של Doconut פועלת על מצב סשן של ASP.NET. מצב סשן דורש מטמון: AddDistributedMemoryCache() מספק אחד. אפליקציית MVC או Razor Pages כבר רושמת אותו, אך אפליקציית מינימל‑API לא עושה זאת, וללא מטמון האפליקציה תפסיק ב‑startup עם Unable to resolve service for type 'IDistributedCache'. כבר רשמת את Doconut במהלך Installation? דלג לחלק הבא.

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'. כתבו את שניהם בלי מקף מוביל: הצופה מוסיף אחד בעצמו, ולכן '/doconut' היה הופך ל‑//doconut/DocImage.axd, שהדפדפן קורא זאת כ‑בקשה למארח בשם doconut, והצופה נכשל עם "The viewer handler could not be reached".

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

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

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

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

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

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 משאירים חוץ משאבי מודול אופציונלי כאשר הרישיון הנוכחי אינו מעניק את היכולת; הצופה הליבה עדיין יתחיל.

אתחול הצופה

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

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

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

חלק מהחבילהדרישהאיך זה מחובר
משאבי הצופה, הרכבה, ו‑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>

סרגל הכלים המלא של ההפניה גם מעתיק את wwwroot/js/viewerToolbar.js לתוך אפליקציית המארח לצורך סיבוב, תמונות ממוזערות, הדפסה, מסך מלא, פריסה ועזרים למצב כפתורים. טען קובץ מארח זה אחרי Viewer.ReferenceScripts(...). שמור את העזר ואת סימון <nav id="toolbar"> יחד כאשר מעתיקים את מימוש ההדגמה המלא.

שמור על סדר האתחול של החבילה כפי שמשתמשים בו שתי יישומי ההפניה:

  1. הפק את משאבי הצופה, החיפוש וההערות יחדיו.
  2. רנדר את סרגל הכלים של הצופה, הריבונים וההרכבה של הצופה יחדיו.
  3. אתחל את docViewer ראשון.
  4. צור כל ריבון מורשה וצרף אותו לאותו objViewer.
  5. פתח את המסמך ושמור את האסימון שלו לבקשות מודול.

Doconut.TestApp.Distributed שומר על הרכבה מדויקת זו של UI ועל אותו עזר של סרגל הצופה. ערך הבקשה access הנוסף והגדרות הניסיון החוזר של רינדור אסינכרוני שייכים לתחבורה המפוזרת; הם אינם משנים את אופן הרכבת הצופה, סרגל הכלים או הריבונים.

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

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

פתיחת מסמך

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

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

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 מוצא רישיון ומה משתנה כאשר הוא נמצא.

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