빠른 시작
몇 분 안에 첫 번째 문서를 렌더링하세요
이 단계별 가이드는 빈 Program.cs 파일에서 시작하는 ASP.NET Core 앱을 브라우저에 문서를 렌더링하는 단계까지 안내합니다: 서버 등록, 전체 Viewer 패키지(Viewer 툴바, Viewer 마운트 및 선택적 Search/Annotation 리본), 자산 참조, 클라이언트 초기화, 문서 열기 및 실행.
호스트 애플리케이션은 netstandard2.1을 지원하는 런타임을 대상으로 해야 합니다 — .NET Core 3.0 이상. 이번 릴리스와 함께 제공되는 참조 애플리케이션은 net8.0을 대상으로 합니다.
서버 설정
AddDoconut()는 서비스를 등록합니다; UseDoconutResources()와 UseDoconut()는 미들웨어를 연결합니다. 리소스 호출이 먼저 와야 합니다. 세션 호출도 필요합니다 — Doconut의 기본 문서 보안은 모든 페이지 요청을 ASP.NET 세션 상태와 비교하여 검증합니다. 세션 상태에는 캐시가 필요합니다: AddDistributedMemoryCache()가 이를 제공합니다. MVC 또는 Razor Pages 앱은 이미 이를 등록하지만, 최소 API 앱은 등록하지 않으며, 이 캐시가 없으면 Unable to resolve service for type 'IDistributedCache' 오류와 함께 시작이 중단됩니다. 이미 설치 단계에서 Doconut을 등록했습니까? 다음 섹션으로 넘어가세요.
builder.Services.AddDoconut(options =>
{
options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddDistributedMemoryCache(); // 세션 상태에 캐시가 필요합니다 (MVC/Razor Pages는 이미 추가합니다)
builder.Services.AddSession(); // Doconut 문서 보안은 ASP.NET 세션 상태에 의존합니다
app.UseSession(); // UseDoconut() 호출 전에 UseSession()을 호출합니다
app.UseDoconutResources(); // UseDoconut() 호출 전에 등록되어야 합니다
app.UseDoconut();참조 애플리케이션은 기존의 DocImage.axd 요청 형태를 유지하고 일치하는 브랜치에 문서 미들웨어를 마운트합니다. 기존 .NET Standard 애플리케이션을 계속 진행할 때 복사해야 할 구성이며, 클라이언트는 항상 동일한 URL을 호출하기 때문입니다:
builder.Services.AddDoconut(options =>
{
options.UnsafeMode = false; // Doconut의 세션 검사를 활성화된 상태로 유지합니다
options.ShowDoconutInfo = false;
});
builder.Services.AddDistributedMemoryCache(); // 세션 상태에 캐시가 필요합니다 (MVC/Razor Pages는 이미 추가합니다)
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(3600);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
// 일부 문서 형식에서 다중 바이트 인코딩을 위해 필요합니다.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseDoconutResources(); // 문서 브랜치 이전에
app.MapWhen(
context => context.Request.Path.Value?.EndsWith("DocImage.axd", StringComparison.OrdinalIgnoreCase) == true,
branch => branch.UseDoconut());UseDoconut()는 이번 릴리스에서 옵션을 받지 않으며, 모든 설정은 AddDoconut()로 이동되었습니다. MiddlewarePath는 조정용 값이며, 자체적으로 ASP.NET Core 브랜치를 매핑하지 않습니다. ResourcesPath는 /doconut-res에서 포함된 번들을 제공하고, 위젯의 이미지 리소스 경로는 ResPath: 'doconut-res/images'가 됩니다.
페이지에 뷰어 추가
Viewer는 페이지에 필수적인 핵심 요소입니다. 렌더링 표면은 두 개의 중첩된 div 요소를 사용합니다:
<div id="divDocViewer">
<div id="div_ctlDoc"></div>
</div>툴바, 모듈 마운트 및 Viewer 표면을 하나의 페이지 구성으로 취급합니다. Search와 Annotation은 선택적 마운트에 자체 리본을 삽입하지만, 해당 모듈은 독립적으로 존재하지 않으며 항상 같은 페이지의 Viewer에 연결됩니다.
<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가 뷰어 스크립트보다 먼저 로드되어야 합니다:
@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.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는 필수 핵심 플래그입니다. Search 또는 Annotation Ribbon 예제를 이 플래그와 Viewer 마운트, docViewer 인스턴스 없이 게시하지 마세요. 현재 라이선스가 해당 기능을 허용하지 않을 경우 ReferenceCss와 ReferenceScripts는 선택 모듈의 리소스를 생략합니다; 핵심 Viewer는 여전히 시작됩니다.
이 호출들은 손으로 복사한 스크립트 파일을 대체합니다. 애플리케이션이 여전히 자체 docViewer.js, documentLinks.js 또는 docViewer.UI.js를 로드한다면, 페이지가 출력된 리소스로 정상 동작할 때 해당 태그들을 제거하세요 — 마이그레이션 가이드를 참고하십시오.
뷰어 초기화
클라이언트‑사이드 위젯은 jQuery 플러그인입니다. 아래는 실제 초기화 옵션의 최소 집합이며(의사코드 아님)입니다:
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 () {
// 페이지가 표시되었습니다; 여기서 로딩 스피너를 숨겨도 안전합니다
},
// 리본이 존재할 때 주석 라이프사이클 이벤트를 임베디드 리본으로 전달합니다.
onAnnLoaded: () => annBar?.handleAnnLoaded(),
onAnnSaved: () => annBar?.handleAnnSaved(),
onAnnSaveError: () => annBar?.handleAnnSaveError(),
onAnnClosed: () => annBar?.handleAnnClosed(),
onError: function (message) {
console.error('Doconut viewer error:', message);
}
});이 페이지가 전혀 동작하지 않을지 여부를 결정하는 두 가지 세부 사항이 있습니다:
- 콜백은 옵션입니다.
onViewerReady,onViewerBusy,onCopy,onAutoLoadStatus는 옵션 객체에 전달됩니다. 이전 Viewer는 컨테이너 ID에서 전역 함수 이름을 유도해 호출했지만, 이번에는 그렇지 않습니다. 전역 함수로만 선언된 핸들러는 절대 실행되지 않으며, 아무런 보고도 이루어지지 않습니다. ResPath는 리소스 접두사를 가리킵니다, 애플리케이션 루트가 아닙니다. 기본ResourcesPath값은doconut-res/images입니다.ResourcesPath를 변경했다면 자체 접두사를 사용하세요.
두 경우 모두 조용히 실패합니다 — 문제 해결 및 마이그레이션 가이드를 참고하십시오.
옵션 명명 규칙은 실제로 혼합되어 있습니다 — showThumbs, autoLoad, pageZoom는 camelCase이지만 FitType, BasePath, ResPath는 PascalCase입니다. 일관된 규칙이 없으며, 대소문자를 잘못 지정하면 옵션이 조용히 무시되고 위젯은 기본값으로 되돌아갑니다(예외를 발생시키지 않음).
전체 Viewer 패키지 조립
두 레퍼런스 애플리케이션은 다음 구성 요소들을 한 페이지에 함께 설치합니다:
| 패키지 구성 요소 | 요구 사항 | 연결 방식 |
|---|---|---|
Viewer 리소스, 마운트 및 objViewer | 필수 | 핵심 문서 렌더러 |
| Viewer 툴바 | 레퍼런스 구성에서 필수 | 호스트 마크업; 버튼이 동일한 objViewer를 호출 |
| Search 리본 | 옵션, 라이선스된 모듈 | doconutSearchBar(...).attach(objViewer) |
| Annotation 리본 | 옵션, 라이선스된 모듈 | doconutAnnotationBar(...).attach(objViewer) |
주된 Viewer 툴바는 호스트 마크업이지만 Viewer와 함께 설치되며 절대로 독립된 컨트롤로 문서화해서는 안 됩니다. 이렇게 하면 레이아웃, 라벨, 아이콘 및 권한 규칙을 애플리케이션이 제어하면서 모든 버튼이 동일한 Viewer 인스턴스를 구동하도록 할 수 있습니다:
<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>두 레퍼런스 애플리케이션이 사용하는 패키지 초기화 순서를 유지하십시오:
- Viewer와 라이선스 모듈용 CSS를 출력합니다.
- Viewer 툴바, Search/Annotation 마운트, 그리고 Viewer 마운트를 함께 렌더링합니다.
- Viewer와 라이선스 모듈용 스크립트를 출력합니다.
- 호스트 애플리케이션 자체 툴바 헬퍼 스크립트를 로드합니다.
docViewer를 초기화하고 결과objViewer를 보관합니다.- 각 라이선스된 Search 또는 Annotation 리본을 초기화합니다.
- 모든 리본에
attach(objViewer)를 호출합니다. - 문서를 열고 Viewer와 모듈 요청에 사용할 토큰을 유지합니다.
서버‑사이드 가드가 중요합니다: 선택 기능이 사용 불가능할 경우 해당 스크립트가 출력되지 않아 jQuery 플러그인 함수가 존재하지 않게 됩니다.
<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>두 임베디드 컴포넌트는 자체 Ribbon DOM을 생성합니다. Search는 Find, Options, Results 그룹을 포함하고, Annotation은 저작 도구, 스타일 제어, 저장 동작 및 선택적인 내보내기/이미지 동작을 포함합니다. 각 바는 open(), close(), reset(), isOpen()을 제공하므로, 생성 직후 한 번 attach(objViewer)를 호출해야 합니다.
위 예제는 선택적인 호스트 콜백과 Annotation 내보내기/이미지 엔드포인트를 생략하여 시작을 최소화했습니다. 전체 기능별 설정은 검색 와 주석 를, 호스트 소유 Viewer 툴바를 스타일링하거나 교체하려면 맞춤 테마를 참고하십시오.
문서 열기
서버 측은 하나의 엔드포인트이며, 주입된 Viewer 서비스가 문서를 열고 세션 토큰을 반환합니다. 이번 릴리스에서는 열기가 비동기식입니다.
app.MapPost("/api/open", async (Viewer viewer) =>
{
// 토큰은 불투명합니다 — 위젯에 전달하고, 절대 로그에 남기거나 영구 저장하지 마세요.
string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
return Results.Ok(new { token });
});클라이언트는 해당 토큰을 가져와 objViewer.View(token)으로 위젯에 전달합니다:
fetch('/api/open', { method: 'POST' })
.then(resp => resp.json())
.then(data => {
currentToken = data.token;
objViewer.View(currentToken);
});첫 번째 열기 전에 준비할 것이 없습니다. 애플리케이션을 업데이트하고 있다면, InitCache() 호출과 이를 보호하는 플래그를 삭제하세요 — 해당 메서드는 더 이상 존재하지 않습니다.
문서 닫기
사용자가 뷰어를 떠나거나 교체 문서를 열 때 objViewer.Close()를 호출하여 문서를 닫습니다. 서버 기반 워크플로에서는 viewer.CloseDocument(token)이 즉시 캐시된 세션을 제거하고, 렌더링 엔진을 해제하며, 보안 마커를 삭제하고, 토큰을 폐기합니다. 슬라이딩 만료도 결국 동일한 정리를 수행하지만, 대용량 문서의 경우 명시적인 닫기를 권장합니다.
완료된 요청 흐름은 다음과 같습니다:
AddDoconut + middleware
-> render CSS/scripts and mount div
-> initialize docViewer
-> OpenDocumentAsync
-> return opaque token
-> objViewer.View(token)
-> page/search/annotation requests
-> Close / CloseDocument토큰을 베어러 자격 증명처럼 취급하세요: 절대 로그에 남기지 말고, 절대 영구 저장하지 말며, 위젯에만 전달하십시오. 토큰은 서버에서 활성 문서 세션을 식별하며, 해당 세션이 만료되면 작동을 멈춥니다 — 새 토큰을 얻으려면 문서를 다시 열어야 합니다.
실행하기
wwwroot/files/Sample.pdf에 PDF 파일을 배치하고 dotnet run을 실행한 뒤 위젯을 호스팅하는 페이지를 엽니다. 첫 페이지가 뷰어에 렌더링되고 왼쪽에 썸네일 패널이 표시됩니다. 표시되지 않으면 문제 해결 문서를 참고하세요.
라이선스 없이 얻는 것
라이선스가 없어도 오류가 발생하지 않습니다. 뷰어는 정상적으로 렌더링되지만 각 페이지에 평가용 워터마크가 표시됩니다. Doconut이 라이선스를 찾는 방법과 찾은 후 어떤 변화가 있는지 보려면 라이선스 설정 문서를 확인하세요.
이 페이지가 도움이 되었나요?