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

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

Этот пошаговый гид переводит приложение 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">
    <!-- Навигация просмотрщика, масштабирование, кнопки Поиска и Аннотации -->
</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, монтирования лент и монтирование Viewer вместе.
  3. Инициализировать docViewer первым.
  4. Создать каждую лицензированную ленту и присоединить её к тому же objViewer.
  5. Открыть документ и сохранить его токен для запросов модулей.

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

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

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