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

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

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

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

AddDoconut() регистрирует сервисы; UseDoconutResources() и UseDoconut() подключают middleware. Вызов ресурсов должен быть первым. Вызовы сессии также обязательны — безопасность документов Doconut проверяет каждый запрос страницы против состояния сессии ASP.NET. Состоянию сессии нужен кэш: AddDistributedMemoryCache() его предоставляет. Приложение MVC или Razor Pages уже регистрирует его, но минимальное API‑приложение — нет, и без него приложение останавливается при запуске с ошибкой Unable to resolve service for type 'IDistributedCache'. Уже регистрировали Doconut во время Installation? Перейдите к следующему разделу.

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

Для продакшн‑стиля расположения путей сопоставьте middleware документов с явной веткой и синхронно задайте четыре параметра пути:

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 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, монтировать Search/Annotation и сам Viewer.
  3. Вывести скрипты для Viewer и лицензируемых модулей.
  4. Загрузить viewerToolbar.js из приложения‑хоста.
  5. Инициализировать docViewer и сохранить полученный objViewer.
  6. Инициализировать каждую лицензируемую ленту поиска или аннотаций.
  7. Вызвать attach(objViewer) у каждой ленты.
  8. Открыть документ и сохранить его токен для запросов Viewer и модулей.

Doconut.TestApp.Distributed сохраняет эту точную композицию UI и тот же вспомогательный скрипт панели. Его дополнительное значение запроса 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». Лента аннотаций включает инструменты авторинга, элементы управления стилем, действия сохранения и необязательные действия экспорта/изображения. Бар‑элементы предоставляют методы open(), close(), reset() и isOpen(); всегда вызывайте attach(objViewer) один раз после их создания.

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

Что вы получаете без лицензии

Отсутствие лицензии не вызывает ошибку. Просмотрщик работает нормально, но каждая страница содержит водяной знак оценки. См. раздел Настройка лицензии, чтобы узнать, как Doconut находит лицензию и какие изменения происходят после её обнаружения.

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