شروع سریع

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

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

تنظیمات سرور

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>

نوار ابزار، ماژول‌های mount و سطح Viewer را به‌عنوان یک ترکیب صفحه در نظر بگیرید. Search و Annotation نوارهای جاسازی‌شده خود را به mountهای اختیاری تزریق می‌کنند، اما این ماژول‌ها هرگز به‌صورت مستقل نیستند: همیشه به 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 view، سرویس تزریق‌شدهٔ 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 پرچم‌های هسته‌ای اجباری هستند. هرگز یک مثال نوار Search یا Annotation را بدون آن‌ها، mount 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 6 بخش‌های زیر را به‌صورت همزمان در یک صفحه نصب می‌کنند:

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

Doconut.TestApp.Distributed این ترکیب UI دقیق را حفظ می‌کند و کمکی نوار ابزار Viewer مشابه دارد. مقدار access درخواست و تنظیمات retry رندر غیرهمزمان به انتقال توزیعی تعلق دارد؛ آن‌ها نحوهٔ ترکیب 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) =>
{
    // 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

توکن را مانند یک اعتبار Bearer در نظر بگیرید: هرگز آن را لاگ نکنید، هرگز ذخیره نکنید، فقط به ویجت بدهید. این توکن یک جلسهٔ زندهٔ سند را در سرور شناسایی می‌کند و وقتی جلسه منقضی شود دیگر کار نمی‌کند — برای دریافت توکن جدید سند را مجدداً باز کنید.

اجرا

یک فایل PDF در wwwroot/files/Sample.pdf قرار دهید، dotnet run را اجرا کنید و صفحه‌ای که ویجت را میزبانی می‌کند باز کنید. صفحهٔ اول در Viewer رندر می‌شود و یک پنل تصویر بندانگشتی در سمت چپ نمایش داده می‌شود. اگر این‌طور نشد، به عیب‌یابی مراجعه کنید.

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

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

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