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.
La aplicación host debe dirigirse a un tiempo de ejecución que soporte netstandard2.1 — .NET Core 3.0 o posterior. Las aplicaciones de referencia enviadas con esta versión apuntan a net8.0.
Configuración del servidor
AddDoconut() registra los servicios; UseDoconutResources() y UseDoconut() conectan el middleware. La llamada a los recursos debe ser primero. Las llamadas a la sesión también son necesarias — la seguridad 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.
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 depende del estado de sesión de ASP.NET
app.UseSession(); // llame a UseSession() antes de UseDoconut()
app.UseDoconutResources(); // debe registrarse antes de UseDoconut()
app.UseDoconut();La aplicación de referencia mantiene la forma histórica de la solicitud DocImage.axd y monta el middleware de documento en una rama coincidente. Esta es la disposición a copiar cuando estás trasladando una aplicación .NET Standard existente, porque el cliente sigue llamando a la misma URL que siempre utilizó:
builder.Services.AddDoconut(options =>
{
options.UnsafeMode = false; // mantenga Doconut's verificación de sesión habilitada
options.ShowDoconutInfo = false;
});
builder.Services.AddDistributedMemoryCache(); // el estado de sesión necesita una caché (MVC/Razor Pages ya añaden una)
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(3600);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
// Requerido para codificaciones multibyte en algunos formatos de documento.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseDoconutResources(); // ANTES de la rama del documento
app.MapWhen(
context => context.Request.Path.Value?.EndsWith("DocImage.axd", StringComparison.OrdinalIgnoreCase) == true,
branch => branch.UseDoconut());UseDoconut() no toma opciones en esta versión — todo se trasladó a AddDoconut().
MiddlewarePath es un valor de coordinación; no asigna una rama de ASP.NET Core por sí mismo. 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'.
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 div anidados:
<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.
<nav id="toolbar" aria-label="Document viewer controls">
<!-- Botones de navegación, zoom, búsqueda y anotación del visor -->
</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 inyectado Viewer emite 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:
@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 Visor, solicite los recursos del Visor y del módulo juntos:
@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 los indicadores centrales obligatorios. Nunca publique un
ejemplo de Search o Annotation Ribbon sin ellos, el montaje del Visor y una instancia docViewer
instancia. ReferenceCss y ReferenceScripts omiten los recursos de un módulo opcional cuando
la licencia actual no concede esa capacidad; el Visor central aún se inicia.
Estas llamadas reemplazan los archivos de script copiados a mano. Si la aplicación aún carga sus propios
docViewer.js, documentLinks.js o docViewer.UI.js, elimine esas etiquetas una vez que la página funcione con los recursos emitidos — vea la guía de migración.
Inicializar el visor
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):
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 () {
// las páginas son visibles; es seguro ocultar un spinner de carga aquí
},
// Reenviar eventos del ciclo de vida de la 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);
}
});Dos detalles determinan si esta página funciona en absoluto:
- Los callbacks son opciones.
onViewerReady,onViewerBusy,onCopy, yonAutoLoadStatusse pasan en el objeto de opciones. El visor anterior derivaba nombres de funciones globales del id del contenedor y los llamaba; este no lo hace. Un manejador declarado solo como función global nunca se ejecuta, y no se informa nada. ResPathapunta al prefijo de recursos, no a la raíz de la aplicación. Con el predeterminadoResourcesPath, ese valor esdoconut-res/images. Si cambióResourcesPath, use su propio prefijo en su lugar.
Ambos fallan silenciosamente — vea la Resolución de problemas y la guía de migración.
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 equivoca la capitalización, la opción se ignora silenciosamente (el widget vuelve a su valor predeterminado en lugar de lanzar un error).
Ensamblar el paquete completo del Visor
Ambas aplicaciones de referencia instalan las siguientes partes juntas en una página:
| Parte del paquete | Requisito | Cómo se conecta |
|---|---|---|
Recursos del Visor, montaje y objViewer | Requerido | Renderizador de documento central |
| Barra de herramientas del Visor | Requerido en la composición de referencia | Marcado del host; los botones llaman al mismo objViewer |
| Cinta de búsqueda | Opcional, módulo licenciado | doconutSearchBar(...).attach(objViewer) |
| Cinta de anotación | Opcional, módulo licenciado | doconutAnnotationBar(...).attach(objViewer) |
Aunque la barra de herramientas principal del Visor es marcado del host, se instala junto al Visor y nunca debe documentarse como un control aislado. Esto mantiene su diseño, etiquetas, íconos y las reglas de autorización bajo el control de su aplicación mientras cada botón controla el mismo instancia del Visor:
<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>Mantenga el orden de inicialización del paquete usado por ambas aplicaciones de referencia:
- Emitir CSS para el Visor y los módulos licenciados.
- Renderizar la barra de herramientas del Visor, los montajes de Search/Annotation y el montaje del Visor juntos.
- Emitir scripts para el Visor y los módulos licenciados.
- Cargar el script de ayuda de la barra de herramientas propio de la aplicación host.
- Inicializar
docViewery conservar elobjViewerresultante. - Inicializar cada cinta de Search o Annotation licenciada.
- Llamar a
attach(objViewer)en cada cinta. - Abrir el documento y conservar su token para solicitudes del Visor y del módulo.
Los guardias 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.
<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. Search contiene los grupos Buscar, Opciones,
y Resultados. Annotation contiene sus herramientas de autoría, controles de estilo, acciones de guardado,
y acciones opcionales de exportar/imagen. Las barras exponen open(), close(), reset(), y
isOpen(); siempre llame a attach(objViewer) una vez después de crearlos.
El ejemplo anterior omite los callbacks opcionales del host y los puntos finales de exportación/imagen de Annotation para mantener el inicio al mínimo. Vea 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 Visor propiedad del host.
Abrir un documento
El lado del servidor es un punto final: el servicio inyectado Viewer abre el documento y devuelve un token de sesión. La apertura es asíncrona en esta versión.
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 recupera ese token y lo pasa al widget con objViewer.View(token):
fetch('/api/open', { method: 'POST' })
.then(resp => resp.json())
.then(data => {
currentToken = data.token;
objViewer.View(currentToken);
});No hay nada que calentar antes de la primera apertura. Si estás llevando una aplicación
hacia adelante, elimina cualquier llamada InitCache() y la bandera que la protege — el método ya no
existe.
Cerrar el documento
Llama a objViewer.Close() cuando el usuario abandona el visor o abre un documento de reemplazo. En flujos de trabajo impulsados 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:
AddDoconut + middleware
-> render CSS/scripts and mount div
-> initialize docViewer
-> OpenDocumentAsync
-> return opaque token
-> objViewer.View(token)
-> page/search/annotation requests
-> Close / CloseDocumentTrata el token como una credencial portadora: nunca lo registres, nunca lo persistas, pásalo solo al widget. Identifica una sesión de documento activa en el servidor y deja de funcionar cuando esa sesión expira — vuelve a abrir el documento para obtener uno nuevo.
Ejecutarlo
Coloca un PDF en wwwroot/files/Sample.pdf, ejecuta dotnet run, y abre 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, consulta 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. Consulta 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?