شروع سریع

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

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

راه‌اندازی سرور

AddDoconut() سرویس‌ها را ثبت می‌کند؛ UseDoconutResources() و UseDoconut() میانه‌افزار را وصل می‌کنند. فراخوانی منابع باید اول باشد. فراخوانی‌های جلسه نیز ضروری‌اند — امنیت سند 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(); // وضعیت جلسه به یک کش نیاز دارد (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» شکست می‌خورد.

افزودن نمایشگر به یک صفحه

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، همزمان منابع 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 هسته‌ای همچنان شروع می‌شود.

مقداردهی اولیه نمایشگر

ویجت سمت کلاینت یک افزونه 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. قاعدهٔ ثابتی وجود ندارد؛ اگر قالب‌بندی را اشتباه بدهید، گزینه به‌صورت ساکت نادیده گرفته می‌شود (ویجت به مقدار پیش‌فرض خود باز می‌گردد به‌جای این‌که خطا بدهد).

ترکیب بستهٔ کامل Viewer

هر دو برنامهٔ مرجع .NET 6 بخش‌های زیر را به‌صورت همزمان در یک صفحه نصب می‌کنند:

بخش بستهنیازمندینحوهٔ اتصال
منابع 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. CSS مربوط به Viewer و ماژول‌های دارای مجوز را تولید کنید.
  2. نوار ابزار Viewer، سوارسازی‌های جستجو/حاشیه‌نویسی و سوارسازی Viewer را به‌صورت همزمان رندر کنید.
  3. اسکریپت‌های مربوط به Viewer و ماژول‌های دارای مجوز را تولید کنید.
  4. فایل viewerToolbar.js برنامهٔ میزبان را بارگذاری کنید.
  5. docViewer را مقداردهی کنید و objViewer حاصل را نگه دارید.
  6. هر ریبن جستجو یا حاشیه‌نویسی دارای مجوز را مقداردهی کنید.
  7. روی هر ریبن، attach(objViewer) را فراخوانی کنید.
  8. سند را باز کنید و توکن آن را برای درخواست‌های Viewer و ماژول حفظ کنید.

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 است. حاشیه‌نویسی شامل ابزارهای نویسندگی، کنترل‌های سبک، عملیات ذخیره و عملیات اختیاری export/image می‌شود. نوارها متدهای open(), close(), reset() و isOpen() را افشا می‌کنند؛ همیشه پس از ایجاد، یک‌بار attach(objViewer) را فراخوانی کنید.

مثال بالا فراخوانی‌های اختیاری میزبان و نقاط انتهایی export/image حاشیه‌نویسی را حذف کرده تا راه‌اندازی به‌حداقل ممکن باشد. برای تنظیم کامل ویژگی‑محور به جستجو و حاشیه‌نویسی مراجعه کنید، یا برای استایل یا جایگزینی نوار ابزار Viewer متعلق به میزبان به تم‌های سفارشی سر بزنید.

باز کردن یک سند

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

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

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

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