플랫폼별 통합

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)를 권장합니다.