شروع سریع

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

این راهنما یک برنامهٔ 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();          // قبل از UseDoconut()، UseSession() را فراخوانی کنید
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، سرویس 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 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، Search و Annotation را به‌صورت همزمان صادر کنید.
  2. نوار ابزار Viewer، سوارسازی‌های Ribbon و سوارسازی Viewer را به‌صورت همزمان رندر کنید.
  3. ابتدا docViewer را مقداردهی اولیه کنید.
  4. هر Ribbon دارای مجوز را ایجاد کنید و به همان objViewer الصاق کنید.
  5. سند را باز کنید و توکن آن را برای درخواست‌های ماژول نگه دارید.

Doconut.TestApp.Distributed این ترکیب دقیق UI و همان کمک‌کنندهٔ Viewer‑toolbar را حفظ می‌کند. مقدار درخواست access اضافی آن و تنظیمات تلاش مجدد رندر ناهمزمان متعلق به انتقال توزیع‌شده هستند؛ آن‌ها نحوهٔ ترکیب Viewer، نوار ابزار یا Ribbonها را تغییر نمی‌دهند.

حفاظت‌های سمت‑سرور مهم هستند: وقتی یک قابلیت اختیاری در دسترس نیست، اسکریپت آن صادر نمی‌شود، بنابراین تابع افزونهٔ 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 مخصوص خود را برای Ribbon تولید می‌کنند. Search شامل گروه‌های Find، Options و Results است. Annotation شامل ابزارهای نویسندگی، کنترل‌های سبک، عملیات ذخیره و عملیات اختیاری 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 مراجعه کنید.

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