شروع سریع

رندر سند اول خود را در عرض چند دقیقه

این راهنما یک برنامهٔ ASP.NET Core را از یک فایل خالی Program.cs به سندی که در مرورگر رندر می‌شود، می‌برد: ثبت‌نام سرور، بستهٔ کامل Viewer (نوار ابزار Viewer، سوار کردن Viewer و نوارهای اختیاری جستجو/حاشیه‌نویسی)، ارجاعات به دارایی‌ها، مقداردهی اولیهٔ کلاینت، باز کردن سند و اجرا.

تنظیم سرور

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 به صفحه

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>

ارجاع به دارایی‌های Viewer

در یک نمای Razor، سرویس تزریق‌شدهٔ 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 پرچم‌های هسته‌ای اجباری هستند. هرگز یک مثال نوار جستجو یا حاشیه‌نویسی را بدون آن‌ها، سوار Viewer و یک نمونهٔ docViewer منتشر نکنید. ReferenceCss و ReferenceScripts در صورتی که مجوز فعلی آن قابلیت را ندهد، منابع ماژول اختیاری را حذف می‌کنند؛ Viewer هسته‌ای همچنان شروع می‌شود.

مقداردهی اولیهٔ 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 8 بخش‌های زیر را به‌صورت همزمان در یک صفحه نصب می‌کنند:

بخش بستهنیازمندینحوهٔ اتصال
منابع Viewer، سوار و objViewerاجباریرندر کنندهٔ سند هسته‌ای
نوار ابزار Viewerاجباری در ترکیب مرجعمارکاپ میزبان؛ دکمه‌ها همان 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، جستجو و حاشیه‌نویسی را به‌صورت همزمان صادر کنید.
  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) را فراخوانی کنید.

مثال بالا فراخوانی‌های اختیاری میزبان و نقاط انتهایی خروجی/تصویر حاشیه‌نویسی را حذف کرده تا راه‌اندازی به‌حداقل برسد. برای تنظیم کامل ویژگی‑های خاص به جستجو و حاشیه‌نویسی مراجعه کنید، یا برای استایل یا جایگزینی نوار ابزار 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);
    });

بستن سند

زمانی که کاربر Viewer را ترک می‌کند یا سندی جایگزین می‌گیرد، objViewer.Close() را صدا بزنید. در جریان‌های کاری مبتنی بر سرور، viewer.CloseDocument(token) بلافاصله جلسهٔ کش‌شده را حذف، موتور رندر را آزاد، نشانگر امنیتی را پاک و توکن را منقضی می‌کند. انقضای Sliding به‌طور خودکار همان پاکسازی را انجام می‌دهد، اما بستن صریح برای اسناد بزرگ توصیه می‌شود.

جریان درخواست تکمیل‌شده به این شکل است:

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 را اجرا کنید و صفحهٔ میزبانی ویجت را باز کنید. صفحهٔ اول در Viewer رندر می‌شود و یک پنل تصویر بندانگشتی در سمت چپ نمایش داده می‌شود. اگر این‌طور نشد، به عیب‌یابی مراجعه کنید.

آنچه بدون لایسنس دریافت می‌کنید

یک لایسنس گمشده خطایی تولید نمی‌کند. Viewer به‌صورت عادی رندر می‌شود، اما هر صفحه یک واترمارک ارزیابی دارد. برای نحوهٔ یافتن لایسنس توسط Doconut و تغییراتی که پس از یافتن آن رخ می‌دهد، به راه‌اندازی لایسنس نگاه کنید.

آیا این صفحه مفید بود؟