Démarrage rapide

Rendez votre premier document en quelques minutes

Ce guide fait passer une application ASP.NET Core d'un Program.cs vide à un document rendu dans le navigateur : enregistrement du serveur, le package complet du Viewer (barre d'outils du Viewer, montage du Viewer et rubans de recherche/annotation optionnels), les références d'actifs, l'initialisation du client, l'ouverture du document et l'exécution.

Configuration du serveur

AddDoconut() enregistre les services ; UseDoconutResources() et UseDoconut() connectent le middleware. L'appel aux ressources doit être effectué en premier. Les appels de session sont également requis — la sécurité de document par défaut de Doconut valide chaque requête de page contre l'état de session ASP.NET. L'état de session nécessite un cache : AddDistributedMemoryCache() en fournit un. Une application MVC ou Razor Pages l'enregistre déjà, mais une application minimal‑API ne le fait pas, et sans cela l'application s'arrête au démarrage avec Unable to resolve service for type 'IDistributedCache'. Vous avez déjà enregistré Doconut lors de l'Installation ? Passez à la section suivante.

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddDistributedMemoryCache(); // l'état de session nécessite un cache (MVC/Razor Pages en ajoute déjà un)
builder.Services.AddSession(); // la sécurité de document Doconut repose sur l'état de session ASP.NET

app.UseSession();          // appelez UseSession() avant UseDoconut()
app.UseDoconutResources(); // doit être enregistré avant UseDoconut()
app.UseDoconut();

Pour une structure de chemins de type production, mappez le middleware de document vers une branche explicite et maintenez les quatre paramètres de chemin alignés :

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 est une valeur de coordination ; elle ne mappe pas une branche ASP.NET Core à elle seule. Dans cet exemple, l'hôte mappe /doconut, donc le client utilise BasePath: 'doconut'. ResourcesPath sert le bundle intégré à /doconut-res, et le chemin de ressource d'image du widget est donc ResPath: 'doconut-res/images'. Écrivez les deux sans slash initial : le viewer en ajoute un lui‑même, ainsi '/doconut' deviendrait //doconut/DocImage.axd, que le navigateur interprète comme une requête vers un hôte nommé doconut, et le viewer échoue avec « The viewer handler could not be reached ».

Ajouter le viewer à une page

Le Viewer est le cœur requis de la page. Sa surface de rendu utilise deux div imbriqués :

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

Traitez la barre d'outils, les montages de modules et la surface du Viewer comme une composition unique de page. La recherche et l'annotation injectent leurs rubans intégrés dans des montages optionnels, mais ces modules ne sont jamais autonomes : ils s'attachent toujours au Viewer sur la même page. Utilisez le même ordre que Doconut.TestApp et Doconut.TestApp.Distributed :

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- Navigation du visualiseur, zoom, boutons de recherche et d'annotation -->
</nav>

<div id="searchBarMount"></div>
<div id="annBarMount"></div>

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

Référencer les ressources du viewer

Dans une vue Razor, le service Viewer injecté émet les balises <link> et <script> du viewer dans l'ordre de dépendance — le widget est un plugin jQuery, donc jQuery doit être chargé avant les scripts du 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
}))

Pour le package complet du Viewer, demandez les ressources du Viewer et du module ensemble :

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 et IncludeViewerScripts sont les indicateurs de base obligatoires. Ne publiez jamais un exemple de ruban Search ou Annotation sans eux, le montage du Viewer, et une instance docViewer. ReferenceCss et ReferenceScripts omettent les ressources d'un module optionnel lorsque la licence actuelle ne le permet pas ; le Viewer de base démarre tout de même.

Initialise le visualiseur

Le widget côté client est un plugin jQuery. Voici un jeu minimal d’options d’initialisation réelles (et non du pseudocode) :

javascript
let searchBar = null;
let annBar = null;

const objViewer = $('#div_ctlDoc').docViewer({
    showThumbs: true,
    autoLoad:   false,
    pageZoom:   100,
    FitType:    'width',
    BasePath:   'doconut',             // pas de '/' initial
    ResPath:    'doconut-res/images',  // pas de '/' initial
    onViewerReady: function () {
        // les pages sont visibles ; il est sûr de masquer un indicateur de chargement ici
    },
    // Transmettre les événements du cycle de vie des annotations au ruban intégré lorsqu'il est présent.
    onAnnLoaded:    () => annBar?.handleAnnLoaded(),
    onAnnSaved:     () => annBar?.handleAnnSaved(),
    onAnnSaveError: () => annBar?.handleAnnSaveError(),
    onAnnClosed:    () => annBar?.handleAnnClosed(),
    onError: function (message) {
        console.error('Doconut viewer error:', message);
    }
});

Le style de casse des options est réellement mixte — showThumbs, autoLoad et pageZoom sont en camelCase, mais FitType, BasePath et ResPath sont en PascalCase. Il n’existe aucune règle cohérente ; une casse incorrecte entraîne l’ignorance silencieuse de l’option (le widget revient à sa valeur par défaut au lieu de lever une exception).

Assembler le package complet du visualiseur

Les deux applications de référence .NET 6 installent les parties suivantes ensemble sur une même page :

Partie du packageExigenceComment c’est connecté
Ressources du visualiseur, montage, et objViewerObligatoireMoteur de rendu de document principal
Barre d'outils du visualiseurObligatoire dans la composition de référenceBalise hôte ; les boutons appellent le même objViewer
Ruban de rechercheOptionnel, module sous licencedoconutSearchBar(...).attach(objViewer)
Ruban d'annotationOptionnel, module sous licencedoconutAnnotationBar(...).attach(objViewer)

Bien que la barre d’outils principale du visualiseur soit du balisage hôte, elle est installée à côté du visualiseur et ne doit jamais être documentée comme un contrôle isolé. Cela maintient sa disposition, ses libellés, ses icônes et ses règles d’autorisation sous le contrôle de votre application tout en faisant en sorte que chaque bouton pilote la même instance du visualiseur :

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>

La barre d’outils de référence complète copie également wwwroot/js/viewerToolbar.js dans l’application hôte pour la rotation, les miniatures, l’impression, le plein écran, la mise en page et les assistants d’état des boutons. Chargez ce fichier hôte après Viewer.ReferenceScripts(...). Conservez l’assistant et son balisage <nav id="toolbar"> ensemble lors de la copie de l’implémentation de démonstration complète.

Conservez l’ordre d’initialisation du package utilisé par les deux applications de référence :

  1. Émettre le CSS pour le visualiseur et les modules sous licence.
  2. Rendre la barre d’outils du visualiseur, les montages Recherche/Annotation, et le montage du visualiseur ensemble.
  3. Émettre les scripts pour le visualiseur et les modules sous licence.
  4. Charger le viewerToolbar.js de l’application hôte.
  5. Initialiser docViewer et conserver le objViewer résultant.
  6. Initialiser chaque ruban de recherche ou d’annotation sous licence.
  7. Appeler attach(objViewer) sur chaque ruban.
  8. Ouvrir le document et retenir son jeton pour les requêtes du visualiseur et des modules.

Doconut.TestApp.Distributed conserve exactement cette composition d’interface et le même assistant de barre d’outils du visualiseur. Sa valeur de requête supplémentaire access ainsi que les paramètres de nouvelle tentative de rendu asynchrone appartiennent au transport distribué ; ils ne modifient pas la façon dont le visualiseur, la barre d’outils ou les rubans sont assemblés.

Les garde-fous côté serveur sont importants : lorsqu’une capacité optionnelle n’est pas disponible, son script n’est pas émis, de sorte que la fonction du plugin jQuery n’existe pas.

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>

Les deux composants intégrés génèrent leur propre DOM de ruban. La recherche contient les groupes Trouver, Options et Résultats. L’annotation comprend ses outils d’auteur, ses contrôles de style, ses actions d’enregistrement et ses actions d’exportation/image optionnelles. Les barres exposent open(), close(), reset() et isOpen() ; appelez toujours attach(objViewer) une fois après les avoir créées.

L’exemple ci‑dessus omet les rappels d’hôte optionnels ainsi que les points de terminaison d’exportation/image d’annotation afin de garder le démarrage minimal. Consultez Search et Annotations pour la configuration complète spécifique aux fonctionnalités, ou Custom Themes pour styliser ou remplacer la barre d’outils du visualiseur détenue par l’hôte.

Ouvrir un document

Le côté serveur possède un seul point de terminaison : le service Viewer injecté ouvre le document et renvoie un jeton de session.

csharp
app.MapPost("/api/open", async (Viewer viewer) =>
{
    // Le jeton est opaque — transmettez‑le au widget, ne le consignez jamais et ne le persistez pas.
    string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
    return Results.Ok(new { token });
});

Le client récupère ce jeton et le transmet au widget avec objViewer.View(token) :

javascript
fetch('/api/open', { method: 'POST' })
    .then(resp => resp.json())
    .then(data => {
        currentToken = data.token;
        objViewer.View(currentToken);
    });

Fermer le document

Appelez objViewer.Close() lorsque l'utilisateur quitte le visualiseur ou ouvre un document de remplacement. Dans les flux de travail pilotés par le serveur, viewer.CloseDocument(token) supprime immédiatement la session mise en cache, libère le moteur de rendu, supprime son marqueur de sécurité et révoque le jeton. L’expiration glissante effectue finalement le même nettoyage, mais une fermeture explicite est recommandée pour les documents volumineux.

Le flux de requête complet est :

text
AddDoconut + middleware
    -> render CSS/scripts and mount div
    -> initialize docViewer
    -> OpenDocumentAsync
    -> return opaque token
    -> objViewer.View(token)
    -> page/search/annotation requests
    -> Close / CloseDocument

Traitez le jeton comme un identifiant porteur : ne le consignez jamais, ne le persistez jamais, transmettez‑le uniquement au widget. Il identifie une session de document active sur le serveur et cesse de fonctionner lorsque cette session expire — rouvrez le document pour en obtenir un nouveau.

Lancer l'application

Placez un PDF dans wwwroot/files/Sample.pdf, exécutez dotnet run, puis ouvrez la page qui héberge le widget. La première page s’affiche dans le visualiseur, avec un panneau de vignettes à gauche. Si ce n’est pas le cas, consultez Dépannage.

Ce que vous obtenez sans licence

Une licence manquante ne génère pas d’exception. Le visualiseur rend normalement, mais chaque page porte un filigrane d’évaluation. Consultez Configuration de la licence pour savoir comment Doconut trouve une licence et ce qui change une fois qu’elle est détectée.

Cette page était-elle utile ?