플랫폼별 통합
ASP.NET · Classic ASP
ASP.NET Core(권장)와 Classic ASP 환경의 통합 예제입니다. 구조는 동일합니다 — 뷰에서 마운트하고, 저장은 JSON 엔드포인트로 받습니다.
공통 전제
뷰어 산출물 /pdfv/를 정적 파일로 서빙해야 합니다 — ASP.NET Core는 wwwroot/pdfv/, IIS는 사이트 루트 하위 pdfv 가상 디렉터리. 시작하기 참고.
ASP.NET Core (Razor)
Show.cshtml cshtml
@* Views/Documents/Show.cshtml — ASP.NET Core MVC *@
@model DocumentViewModel
<div id="pdf-container" style="width:100%; height:80vh"></div>
<script src="/pdfv/sdk/pdfv-sdk.js"></script>
<script>
// Json.Serialize가 값을 안전한 JS 리터럴로 출력합니다
var initialCanvasData = @Json.Serialize(Model.LatestCanvasData);
var viewer = Inko.mount('#pdf-container', {
src: '/pdfv/index.html',
pdfUrl: @Json.Serialize(Model.FileUrl),
fileName: @Json.Serialize(Model.FileName),
initialCanvasData: initialCanvasData || undefined,
onSave: function (canvasData, ok) {
if (!ok) return;
fetch('/api/annotations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'RequestVerificationToken':
document.querySelector('input[name="__RequestVerificationToken"]')?.value || ''
},
body: JSON.stringify({ docId: @Model.Id, canvasData: canvasData })
});
}
});
</script>저장 엔드포인트 (Web API)
AnnotationsController.cs csharp
// Controllers/AnnotationsController.cs — ASP.NET Core
[ApiController]
[Route("api/annotations")]
public class AnnotationsController : ControllerBase
{
private readonly string _connString;
public AnnotationsController(IConfiguration config)
=> _connString = config.GetConnectionString("Default");
public record SaveRequest(long DocId, string CanvasData);
[HttpPost]
public async Task<IActionResult> Save([FromBody] SaveRequest req)
{
if (req.DocId <= 0 || string.IsNullOrEmpty(req.CanvasData))
return BadRequest();
await using var conn = new SqlConnection(_connString);
await conn.ExecuteAsync(@"
INSERT INTO doc_annotations (doc_id, canvas_data, version)
VALUES (@DocId, @CanvasData,
ISNULL((SELECT MAX(version) FROM doc_annotations
WHERE doc_id = @DocId), 0) + 1)",
new { req.DocId, req.CanvasData }); // Dapper 기준
return Ok(new { ok = true });
}
}Antiforgery(CSRF — 사이트 간 요청 위조 방지)를 전역 적용 중이라면 예제처럼 RequestVerificationToken 헤더를 전달하고
액션에 [ValidateAntiForgeryToken]을 두거나, API 전용 경로는 토큰 검증을 제외하세요.
Classic ASP
레거시 Classic ASP에서도 클라이언트 측 통합은 동일하게 동작합니다.
Classic ASP에는 표준 JSON 직렬화가 없으므로, 긴 canvasData는 인라인 주입 대신 API로 받아 주입하는 패턴을 권장합니다.
document-view.asp asp
<%' document-view.asp — Classic ASP %>
<%
Dim docId, fileUrl, latestCanvas
docId = CLng(Request.QueryString("id"))
' DB에서 fileUrl·latestCanvas 조회 (고객사 로직)
%>
<div id="pdf-container" style="width:100%; height:80vh"></div>
<script src="/pdfv/sdk/pdfv-sdk.js"></script>
<script>
var viewer = Inko.mount('#pdf-container', {
src: '/pdfv/index.html',
pdfUrl: '<%= fileUrl %>',
onSave: function (canvasData, ok) {
if (!ok) return;
fetch('/api/save-annotation.asp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ docId: <%= docId %>, canvasData: canvasData })
});
}
});
// canvasData가 큰 경우 인라인 주입 대신 API로 받아 주입 (권장)
fetch('/api/latest-annotation.asp?docId=<%= docId %>')
.then(function (r) { return r.json(); })
.then(function (d) {
if (d.canvasData) viewer.loadPdfUrl('<%= fileUrl %>', undefined, d.canvasData);
});
</script>주의사항
- IIS 정적 파일 — 뷰어 배포 후
/pdfv/index.html이 브라우저에서 직접 열리는지 먼저 확인하세요. MIME 누락 시 IIS에.mjs·.wasm등 확장자 매핑을 추가해야 할 수 있습니다. - 요청 크기 제한 —
maxAllowedContentLength(IIS)·[RequestSizeLimit](Core)를 canvasData 크기에 맞게 조정하세요. - DB 컬럼 — SQL Server는
NVARCHAR(MAX)를 권장합니다.