Быстрый старт

Отобразите ваш первый документ за несколько минут

Это руководство переводит приложение ASP.NET Core от пустого Program.cs к отображению документа в браузере: регистрация сервера, полный пакет Viewer (панель инструментов Viewer, монтирование Viewer и необязательные ленты Поиска/Аннотаций), ссылки на ресурсы, инициализация клиента, открытие документа и выполнение.

Хост‑приложение должно использовать среду выполнения, поддерживающую netstandard2.1 — .NET Core 3.0 или новее. Справочные приложения, поставляемые с этим релизом, нацелены на net8.0.

Настройка сервера

AddDoconut() регистрирует сервисы; UseDoconutResources() и UseDoconut() подключают middleware. Вызов ресурсов должен быть первым. Вызовы сессии также обязательны — стандартная безопасность документов Doconut проверяет каждый запрос страницы против состояния сессии ASP.NET. Состоянию сессии нужен кэш: AddDistributedMemoryCache() предоставляет его. Приложение MVC или Razor Pages уже регистрирует его, но минимальное 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 и монтирует middleware документа на соответствующей ветке. Это конфигурацию следует скопировать, когда вы переносите существующее приложение .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‑представлении внедренный сервис 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 являются обязательными базовыми флагами. Никогда не публикуйте пример ленты Search или Annotation без них, без монтирования 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);
    }
});

Две детали определяют, будет ли эта страница работать вообще:

  • Обратные вызовы являются параметрами. onViewerReady, onViewerBusy, onCopy и onAutoLoadStatus передаются в объекте параметров. Предыдущий просмотрщик выводил имена глобальных функций из идентификатора контейнера и вызывал их; этот этого не делает. Обработчик, объявленный только как глобальная функция, никогда не выполнится, и ничего не будет сообщено.
  • ResPath указывает префикс ресурсов, а не корень приложения. При значении по умолчанию ResourcesPath это doconut-res/images. Если вы изменили ResourcesPath, используйте свой собственный префикс.

Обе эти ошибки игнорируются — см. Устранение неполадок и руководство по миграции.

Регистрозависимость параметров действительно смешанная — showThumbs, autoLoad и pageZoom записаны в camelCase, а FitType, BasePath и ResPath — в PascalCase. Нет единого правила; неверный регистр приводит к тихому игнорированию параметра (виджет переходит к значению по умолчанию вместо ошибки).

Сборка полного пакета Viewer

Обе референсные программы устанавливают следующие части вместе на одной странице:

Часть пакетаТребованиеКак подключено
Ресурсы Viewer, монтирование и objViewerОбязательноОсновной рендерер документов
Панель инструментов ViewerОбязательно в референсной композицииРазметка хоста; кнопки вызывают тот же objViewer
Лента SearchОпциональный, лицензированный модульdoconutSearchBar(...).attach(objViewer)
Лента AnnotationОпциональный, лицензированный модуль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, монтирования Search/Annotation и монтирование Viewer вместе.
  3. Вывести скрипты для Viewer и лицензированных модулей.
  4. Загрузить вспомогательный скрипт панели инструментов собственного приложения.
  5. Инициализировать docViewer и сохранить полученный objViewer.
  6. Инициализировать каждую лицензированную ленту Search или Annotation.
  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 ленты. Search содержит группы Find, Options и Results. Annotation включает инструменты авторинга, элементы управления стилем, действия сохранения и опциональные действия экспорта/изображения. Ленты предоставляют методы open(), close(), reset() и isOpen(); всегда вызывайте attach(objViewer) один раз после их создания.

Приведённый выше пример опускает необязательные обратные вызовы хоста и конечные точки экспорта/изображения Annotation, чтобы минимизировать стартовую загрузку. См. Search и Annotations для полной настройки конкретных функций, либо Custom Themes для стилизации или замены панели инструментов 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 ищет лицензию и какие изменения происходят после её обнаружения.

Была ли эта страница полезной?