شروع سریع

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

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

برنامهٔ میزبان باید به زمان‌اجرایی که از netstandard2.1 پشتیبانی می‌کند هدف‌گذاری شود — .NET Core 3.0 یا بالاتر. برنامه‌های مرجع ارائه‌شده با این نسخه هدف‌گذاری net8.0 را دارند.

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

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();

برنامهٔ مرجع شکل تاریخی درخواست DocImage.axd را حفظ می‌کند و میانه‌افزار سند را بر روی یک شاخهٔ منطبق نصب می‌کند. این ترتیب را وقتی که یک برنامهٔ .NET Standard موجود را به جلو می‌برید، کپی کنید، زیرا کلاینت همچنان همان URL را که همیشه استفاده کرده است، فراخوانی می‌کند:

csharp
builder.Services.AddDoconut(options =>
{
    options.UnsafeMode      = false;   // بررسی جلسه Doconut فعال بماند
    options.ShowDoconutInfo = false;
});
builder.Services.AddDistributedMemoryCache(); // وضعیت جلسه به یک کش نیاز دارد (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' است.

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

Viewer هستهٔ الزامی صفحه است. سطح رندر آن از دو div تو در تو استفاده می‌کند:

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

نوار ابزار، نصب ماژول‌ها و سطح Viewer را به‌عنوان یک ترکیب صفحه در نظر بگیرید. جستجو و حاشیه‌نویسی نوارهای جاسازی‌شدهٔ خود را به نصب‌های اختیاری تزریق می‌کنند، اما این ماژول‌ها هرگز به‌صورت مستقل نیستند: همیشه به Viewer در همان صفحه متصل می‌شوند.

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- ناوبری نمایشگر، بزرگ‌نمایی، دکمه‌های جستجو و حاشیه‌نویسی -->
</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 اصلی همچنان راه‌اندازی می‌شود.

این فراخوانی‌ها جایگزین فایل‌های اسکریپت کپی‑دستی می‌شوند. اگر برنامه هنوز 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);
    }
});

دو جزئیات تعیین می‌کنند که آیا این صفحه به‌طور کلی کار می‌کند یا نه:

  • Callbackها گزینه‌ها هستند. onViewerReady، onViewerBusy، onCopy و onAutoLoadStatus در شیء گزینه‌ها پاس داده می‌شوند. نمایشگر قبلی نام توابع سراسری را از شناسهٔ کانتینر استخراج می‌کرد و آن‌ها را فراخوانی می‌کرد؛ این نسخه این کار را نمی‌کند. یک هندلر که فقط به‌عنوان تابع سراسری تعریف شده باشد هرگز اجرا نمی‌شود و هیچ‌چیزی گزارش نمی‌شود.
  • ResPath به پیشوند منابع اشاره می‌کند، نه ریشهٔ برنامه. با مقدار پیش‌فرض ResourcesPath، این مقدار doconut-res/images است. اگر ResourcesPath را تغییر داده‌اید، به‌جای آن از پیشوند خودتان استفاده کنید.

هر دو این موارد به‌صورت ساکت شکست می‌خورند — به عیب‌یابی و راهنمای مهاجرت مراجعه کنید.

حروف‌گذاری گزینه‌ها به‌طور واقعی ترکیبی است — showThumbs، autoLoad و pageZoom به‌صورت camelCase هستند، اما FitType، BasePath و ResPath به‌صورت PascalCase. قاعدهٔ ثابتی وجود ندارد؛ اگر حروف‌گذاری را اشتباه کنید، گزینه به‌صورت ساکت نادیده گرفته می‌شود (ویجت به مقدار پیش‌فرض خود باز می‌گردد به‌جای اینکه خطا بدهد).

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

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

بخش بستهنیازمندینحوهٔ اتصال
منابع 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>

دستورالعمل ترتیب مقداردهی اولیه بسته را که هر دو برنامه مرجع استفاده می‌کنند، حفظ کنید:

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

قبل از اولین باز کردن نیازی به گرم‌کردن نیست. اگر برنامه‌ای را به‌روزرسانی می‌کنید، هر فراخوانی 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 و تغییراتی که پس از یافتن رخ می‌دهد، به بخش راه‌اندازی لایسنس مراجعه کنید.

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