Inicio rápido

Renderiza tu primer documento en minutos

Esta guía lleva una aplicación ASP.NET Core desde un Program.cs vacío hasta un documento renderizado en el navegador: registro del servidor, el paquete completo del Viewer (barra de herramientas del Viewer, montaje del Viewer y cintas opcionales de Búsqueda/Anotación), referencias de recursos, inicialización del cliente, apertura del documento y ejecución.

Configuración del servidor

AddDoconut() registra los servicios; UseDoconutResources() y UseDoconut() conectan el middleware. La llamada a los recursos debe ser la primera. Las llamadas de sesión también son necesarias — la seguridad de documentos predeterminada de Doconut valida cada solicitud de página contra el estado de sesión de ASP.NET. El estado de sesión necesita una caché: AddDistributedMemoryCache() proporciona una. Una aplicación MVC o Razor Pages ya la registra, pero una aplicación minimal-API no lo hace, y sin ella la aplicación se detiene al iniciar con Unable to resolve service for type 'IDistributedCache'. ¿Ya registraste Doconut durante la Instalación? Avanza a la siguiente sección.

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddDistributedMemoryCache(); // el estado de sesión necesita una caché (MVC/Razor Pages ya añaden una)
builder.Services.AddSession(); // La seguridad de documentos Doconut se basa en el estado de sesión de ASP.NET

app.UseSession();          // llame a UseSession() antes de UseDoconut()
app.UseDoconutResources(); // debe registrarse antes de UseDoconut()
app.UseDoconut();

Para una estructura de rutas estilo producción, mapea el middleware de documentos a una rama explícita y mantén alineadas las cuatro configuraciones de ruta:

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 es un valor de coordinación; no asigna una rama de ASP.NET Core por sí mismo. En este ejemplo el host asigna /doconut, por lo que el cliente usa BasePath: 'doconut'. ResourcesPath sirve el paquete incrustado en /doconut-res, y la ruta del recurso de imagen del widget es por lo tanto ResPath: 'doconut-res/images'. Escribe ambos sin una barra inicial: el visor agrega una por sí mismo, de modo que '/doconut' se convertiría en //doconut/DocImage.axd, que el navegador interpreta como una solicitud a un host llamado doconut, y el visor falla con "The viewer handler could not be reached".

Añadir el visor a una página

El Viewer es el núcleo requerido de la página. Su superficie de renderizado utiliza dos divs anidados:

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

Trata la barra de herramientas, los montajes de módulos y la superficie del Viewer como una única composición de página. Búsqueda y Anotación inyectan sus cintas incrustadas en montajes opcionales, pero esos módulos nunca son independientes: siempre se adjuntan al Viewer en la misma página. Usa el mismo orden que Doconut.TestApp y 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>

Referenciar los recursos del visor

En una vista Razor, el servicio Viewer inyectado genera las etiquetas <link> y <script> del visor en orden de dependencia — el widget es un plugin de jQuery, por lo que jQuery debe cargarse antes de los scripts del visor:

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
}))

Para el paquete completo del Viewer, solicita los recursos del Viewer y los módulos juntos:

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 y IncludeViewerScripts son las banderas centrales obligatorias. Nunca publiques un ejemplo de cinta de Búsqueda o Anotación sin ellas, el montaje del Viewer y una instancia docViewer. ReferenceCss y ReferenceScripts omiten los recursos de un módulo opcional cuando la licencia actual no concede esa capacidad; el Viewer central aún se inicia.

Inicializar el visualizador

El widget del lado del cliente es un plugin de jQuery. Este es un conjunto mínimo de opciones reales de inicialización (no pseudocódigo):

javascript
let searchBar = null;
let annBar = null;

const objViewer = $('#div_ctlDoc').docViewer({
    showThumbs: true,
    autoLoad:   false,
    pageZoom:   100,
    FitType:    'width',
    BasePath:   'doconut',             // sin '/' inicial
    ResPath:    'doconut-res/images',  // sin '/' inicial
    onViewerReady: function () {
        // las páginas son visibles; es seguro ocultar un spinner de carga aquí
    },
    // Reenviar eventos del ciclo de vida de anotación a la cinta incrustada cuando está presente.
    onAnnLoaded:    () => annBar?.handleAnnLoaded(),
    onAnnSaved:     () => annBar?.handleAnnSaved(),
    onAnnSaveError: () => annBar?.handleAnnSaveError(),
    onAnnClosed:    () => annBar?.handleAnnClosed(),
    onError: function (message) {
        console.error('Doconut viewer error:', message);
    }
});

La capitalización de las opciones es realmente mixta — showThumbs, autoLoad y pageZoom están en camelCase, pero FitType, BasePath y ResPath están en PascalCase. No hay una regla consistente; si se escribe la capitalización incorrecta, la opción se ignora silenciosamente (el widget vuelve a su valor predeterminado en lugar de lanzar un error).

Ensamblar el paquete completo del Visualizador

Ambas aplicaciones de referencia .NET 6 instalan las siguientes partes juntas en una página:

Parte del paqueteRequisitoCómo se conecta
Recursos del visualizador, montaje y objViewerObligatorioRenderizador central de documentos
Barra de herramientas del visualizadorObligatorio en la composición de referenciaMarcado del host; los botones llaman al mismo objViewer
Cinta de búsquedaOpcional, módulo con licenciadoconutSearchBar(...).attach(objViewer)
Cinta de anotaciónOpcional, módulo con licenciadoconutAnnotationBar(...).attach(objViewer)

Aunque la barra de herramientas principal del Visualizador es un marcado del host, se instala junto al Visualizador y nunca debe documentarse como un control aislado. Esto mantiene su diseño, etiquetas, íconos y reglas de autorización bajo el control de su aplicación mientras cada botón controla la misma instancia del Visualizador:

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 barra de herramientas completa de referencia también copia wwwroot/js/viewerToolbar.js en la aplicación host para rotación, miniaturas, impresión, pantalla completa, diseño y asistentes de estado de botones. Cargue ese archivo host después de Viewer.ReferenceScripts(...). Mantenga el asistente y su marcado <nav id="toolbar"> juntos al copiar la implementación completa de la demostración.

Mantenga el orden de inicialización del paquete usado por ambas aplicaciones de referencia:

  1. Emitir CSS para el Visualizador y los módulos con licencia.
  2. Renderizar la barra de herramientas del Visualizador, los montajes de Búsqueda/Anotación y el montaje del Visualizador juntos.
  3. Emitir scripts para el Visualizador y los módulos con licencia.
  4. Cargar el viewerToolbar.js de la aplicación host.
  5. Inicializar docViewer y conservar el objViewer resultante.
  6. Inicializar cada cinta de Búsqueda o Anotación con licencia.
  7. Llamar a attach(objViewer) en cada cinta.
  8. Abrir el documento y conservar su token para las solicitudes del Visualizador y los módulos.

Doconut.TestApp.Distributed mantiene esta composición exacta de la UI y el mismo asistente de barra de herramientas del Visualizador. Su valor de solicitud access adicional y la configuración de reintentos de renderizado asíncrono pertenecen al transporte distribuido; no cambian cómo se ensamblan el Visualizador, la barra de herramientas o las cintas.

Los guardas del lado del servidor son importantes: cuando una capacidad opcional no está disponible, su script no se emite, por lo que su función de plugin de jQuery no existe.

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>

Ambos componentes incrustados generan su propio DOM de cinta. Búsqueda contiene los grupos Encontrar, Opciones y Resultados. Anotación contiene sus herramientas de autoría, controles de estilo, acciones de guardado y acciones opcionales de exportación/imagen. Las barras exponen open(), close(), reset() e isOpen(); siempre llame a attach(objViewer) una vez después de crearlas.

El ejemplo anterior omite los callbacks opcionales del host y los puntos finales de exportación/imagen de Anotación para mantener el inicio al mínimo. Consulte Búsqueda y Anotaciones para la configuración completa específica de la característica, o Temas personalizados para estilizar o reemplazar la barra de herramientas del Visualizador propiedad del host.

Abrir un documento

El lado del servidor tiene un único endpoint: el servicio Viewer inyectado abre el documento y devuelve un token de sesión.

csharp
app.MapPost("/api/open", async (Viewer viewer) =>
{
    // El token es opaco — pásalo al widget, nunca lo registres ni lo persistas.
    string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
    return Results.Ok(new { token });
});

El cliente obtiene ese token y lo pasa al widget con objViewer.View(token):

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

Cerrar el documento

Llame a objViewer.Close() cuando el usuario abandone el visor o abra un documento de reemplazo. En flujos de trabajo dirigidos por el servidor, viewer.CloseDocument(token) elimina inmediatamente la sesión en caché, dispone del motor de renderizado, borra su marcador de seguridad y revoca el token. La expiración deslizante eventualmente realiza la misma limpieza, pero se recomienda cerrar explícitamente para documentos grandes.

El flujo de solicitud completado es:

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

Trate el token como una credencial portadora: nunca lo registre, nunca lo persista, páselo solo al widget. Identifica una sesión de documento activa en el servidor y deja de funcionar cuando esa sesión expira — vuelva a abrir el documento para obtener uno nuevo.

Ejecutarlo

Coloque un PDF en wwwroot/files/Sample.pdf, ejecute dotnet run y abra la página que aloja el widget. La primera página se renderiza en el visor, con un panel de miniaturas a la izquierda. Si no lo hace, consulte Solución de problemas.

Qué obtienes sin una licencia

Una licencia faltante no genera una excepción. El visor se renderiza normalmente, pero cada página lleva una marca de agua de evaluación. Consulte Configuración de licencia para saber cómo Doconut encuentra una licencia y qué cambia una vez que lo hace.

¿Fue útil esta página?