빠른 시작
몇 분 안에 첫 문서를 렌더링하세요
이 안내서는 빈 Program.cs 파일을 가진 ASP.NET Core 앱을 브라우저에서 문서가 렌더링되는 상태까지 진행합니다: 서버 등록, 전체 Viewer 패키지(Viewer 툴바, Viewer 마운트 및 선택적 Search/Annotation 리본), 자산 참조, 클라이언트 초기화, 문서 열기 및 실행.
서버 설정
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();프로덕션 스타일 경로 레이아웃을 위해, 문서 미들웨어를 명시적인 브랜치에 매핑하고 네 개의 경로 설정을 일치시킵니다:
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를 사용합니다:
<div id="divDocViewer">
<div id="div_ctlDoc"></div>
</div>툴바, 모듈 마운트 및 Viewer 표면을 하나의 페이지 구성으로 취급하세요. Search와 Annotation은 선택적 마운트에 임베디드 리본을 삽입하지만, 해당 모듈은 절대 독립적으로 존재하지 않으며 항상 같은 페이지의 Viewer에 연결됩니다. Doconut.TestApp 및 Doconut.TestApp.Distributed와 동일한 순서를 사용하세요:
<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는 여전히 시작됩니다.
뷰어 초기화
클라이언트 측 위젯은 jQuery 플러그인입니다. 다음은 실제 초기화 옵션의 최소 집합입니다(의사코드가 아님):
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입니다. 일관된 규칙이 없으며, 대소문자를 잘못 지정하면 옵션이 조용히 무시됩니다(위젯이 오류를 발생시키는 대신 기본값으로 되돌아갑니다).
전체 뷰어 패키지 조립
두 .NET 6 레퍼런스 애플리케이션은 다음 구성 요소들을 한 페이지에 함께 설치합니다:
| 패키지 구성 요소 | 요구 사항 | 연결 방식 |
|---|---|---|
뷰어 리소스, 마운트 및 objViewer | 필수 | 핵심 문서 렌더러 |
| 뷰어 툴바 | 레퍼런스 구성에 필수 | 호스트 마크업; 버튼이 동일한 objViewer를 호출 |
| 검색 리본 | 선택 사항, 라이선스 모듈 | doconutSearchBar(...).attach(objViewer) |
| 주석 리본 | 선택 사항, 라이선스 모듈 | doconutAnnotationBar(...).attach(objViewer) |
주요 뷰어 툴바는 호스트 마크업이지만 뷰어와 함께 설치되며 절대로 독립된 컨트롤로 문서화해서는 안 됩니다. 이렇게 하면 레이아웃, 레이블, 아이콘 및 권한 규칙이 애플리케이션의 제어 하에 유지되면서 모든 버튼이 동일한 뷰어 인스턴스를 제어합니다:
<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"> 마크업을 함께 유지합니다.
두 레퍼런스 애플리케이션에서 사용되는 패키지 초기화 순서를 유지하십시오:
- 뷰어와 라이선스 모듈용 CSS를 출력합니다.
- 뷰어 툴바, 검색/주석 마운트 및 뷰어 마운트를 함께 렌더링합니다.
- 뷰어와 라이선스 모듈용 스크립트를 출력합니다.
- 호스트 애플리케이션의
viewerToolbar.js를 로드합니다. docViewer를 초기화하고 결과objViewer를 유지합니다.- 각 라이선스 검색 또는 주석 리본을 초기화합니다.
- 모든 리본에
attach(objViewer)를 호출합니다. - 문서를 열고 뷰어와 모듈 요청에 사용할 토큰을 보관합니다.
Doconut.TestApp.Distributed는 이 정확한 UI 구성을 유지하고 동일한 뷰어-툴바 도우미를 사용합니다. 추가적인 access 요청 값과 비동기 렌더 재시도 설정은 분산 전송에 해당하며, 뷰어, 툴바 또는 리본이 조립되는 방식에는 영향을 주지 않습니다.
서버 측 보호 장치는 중요합니다: 선택적 기능을 사용할 수 없을 경우 해당 스크립트가 출력되지 않아 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>두 임베드된 컴포넌트는 각각 자체 리본 DOM을 생성합니다. 검색 리본은 찾기, 옵션 및 결과 그룹을 포함하고, 주석 리본은 저작 도구, 스타일 제어, 저장 동작 및 선택적 내보내기/이미지 동작을 포함합니다. 각 바는 open(), close(), reset(), isOpen()을 제공하며, 생성 후 한 번만 attach(objViewer)를 호출해야 합니다.
위 예제는 시작을 최소화하기 위해 선택적 호스트 콜백 및 주석 내보내기/이미지 엔드포인트를 생략했습니다. 전체 기능별 설정은 검색 및 주석을 참고하고, 호스트 소유 뷰어 툴바를 스타일링하거나 교체하려면 맞춤 테마를 확인하십시오.
문서 열기
서버 측은 하나의 엔드포인트이며, 주입된 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);
});문서 닫기
사용자가 뷰어를 떠나거나 다른 문서를 열 때 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이 라이선스를 찾는 방법과 라이선스를 찾은 후 어떤 변화가 있는지 보려면 라이선스 설정을 확인하세요.
이 페이지가 도움이 되었나요?