Viewer는 무상태 — .NET 8 최소 오픈 엔드포인트 구축 방법
← Back to Blog••3 min read

Viewer는 무상태 — .NET 8 최소 오픈 엔드포인트 구축 방법

Viewer는 무상태 — 설계상

Viewer은 보관하지 요청당 문서 데이터를 유지합니다. 이는 sealed 클래스이며, IDisposable를 구현하지 않으며, 열어보는 각 문서는 프레임워크 캐시가 관리하는 별도 세션에 존재합니다. 따라서 ASP.NET Core 앱 어디에서든 단일 Viewer 인스턴스를 주입하고 다수의 동시 요청에서 안전하게 호출할 수 있습니다.


무상태 서비스의 결과

  • 숨겨진 리소스 없음 – 서비스 자체가 명시적인 정리가 필요한 큰 버퍼를 할당하지 않습니다.
  • 기본적으로 스레드 안전 – 요청별 상태가 저장되지 않기 때문에 동일한 Viewer 인스턴스를 여러 스레드가 잠금 없이 사용할 수 있습니다.
  • 확장 가능한 배포 – 수평 확장이 기본 제공되며, 스티키 세션이나 서버 친화성이 필요 없고, 클라이언트가 보유한 세션 토큰만 있으면 됩니다.

문서별 정보(페이지, 주석, 검색 인덱스)는 OpenDocumentAsync가 만든 세션에 보관됩니다. 세션은 IMemoryCache(또는 구성한 任意의 IDistributedCache)에 저장되며 비활동 기간이 지나면 자동으로 만료됩니다(기본 60분). 작업이 끝났을 때는 CloseDocument를 호출하여 캐시 항목을 조기에 해제할 수 있습니다.

무상태 Viewer 등록

Viewer는 가변 상태가 없으므로, 시작 시 한 번만 등록하면 됩니다:

builder.Services.AddDoconut();               // Viewer와 필요한 미들웨어를 등록합니다
builder.Services.AddSession();               // 토큰 보안을 위해 필요합니다
builder.Services.AddDistributedMemoryCache(); // 데모용 간단한 캐시

AddDoconut 호출은 Viewer의 정적 리소스를 제공하는 미들웨어도 연결합니다. 서비스가 설정된 후에는 어떤 엔드포인트 시그니처에서도 Viewer를 요청할 수 있습니다.

최소 오픈‑문서 엔드포인트 (net‑8 예시)

다음 코드는 공식 문서에서 발췌한 정확한 스니펫이며 .NET 8 최소 API 프로젝트에서 바로 동작합니다:

app.MapPost("/api/open", async (string fileName, Viewer viewer) =>
{
    var token = await viewer.OpenDocumentAsync($"files/{fileName}");
    return Results.Content(token, "text/plain");
});

코드가 수행하는 작업

  1. 파일 이름 수락 – 클라이언트가 wwwroot/files 아래에서 열 파일을 서버에 알려줍니다.
  2. OpenDocumentAsync 호출 – 무상태 Viewer가 새로운 세션을 생성하고 캐시에 저장한 뒤 불투명 토큰을 반환합니다.
  3. 토큰을 평문 텍스트로 반환 – 클라이언트는 토큰만 받으며, 서버는 이 응답에서 문서 바이트를 전송하지 않습니다.

Viewer는 문서 데이터를 보유하지 않으므로, 엔드포인트는 using 블록, Dispose 호출, 혹은 요청당 팩토리가 필요하지 않습니다. 토큰은 클라이언트가 Viewer 위젯에 다시 보낼 수 있는 유일한 인증 정보이며(objViewer.View(token)가 프론트엔드에 존재),

클라이언트 측에서 토큰 사용

토큰이 반환된 후, 프론트엔드 위젯은 objViewer.View(token)를 호출합니다. 위젯은 이후 페이지 이미지, 검색, 주석 작업 등에 대한 모든 요청에 동일한 토큰을 포함합니다. 서버는 토큰을 원래 문서를 연 세션과 비교해 검증합니다. 토큰이 다른 브라우저 세션에서 제공되면, Viewer는 권한 오류를 반환하며, 이는 의도된 보안 동작입니다.

세션을 조기에 종료 (옵션)

워크플로우에서 문서가 다시 접근되지 않을 것을 알면, Viewer에서 CloseDocument를 호출해 캐시 항목을 해제할 수 있습니다. 이렇게 하면 세션이 캐시에서 제거되고 슬라이딩 만료 타이머가 세션을 유지하지 않게 됩니다.

무상태 흐름 요약

단계무슨 일 발생상태 저장 위치
Viewer 주입시작 시 단일 Viewer 인스턴스가 생성됩니다.DI 컨테이너에 (싱글톤).
OpenDocumentAsync라이선스 확인 → 세션 생성 → 토큰 반환.세션이 IMemoryCache에 저장됩니다(또는 분산 캐시).
클라이언트 렌더링위젯이 모든 요청에 토큰을 포함하여 전송합니다.토큰은 클라이언트가 보유한 유일한 문서별 데이터입니다.
세션 만료 / CloseDocument시간 초과 또는 명시적 종료 후 캐시 항목이 제거됩니다.Viewer 서비스에 남아있는 리소스가 없습니다.

서비스 자체가 문서 데이터를 저장하지 않으므로, 웹 팜을 자유롭게 확장하거나 노드를 교체하거나 애플리케이션을 재시작해도 사용자 가시 상태가 손실되지 않습니다—토큰만 무효화되어 클라이언트가 문서를 다시 열도록 유도됩니다.

앱에 무상태 Viewer 배포

  1. AddDoconut, AddSession 및 캐시 제공자를 Program.cs에 추가합니다.
  2. 오픈‑엔드포인트 스니펫을 최소 API 구성에 붙여넣습니다.
  3. 반환된 토큰을 프론트엔드의 objViewer.View(token)에 전달합니다.

이제 Doconut의 Viewer 설계를 활용한 완전 무상태, 프로덕션 준비된 문서 오픈 API를 갖게 되었습니다.

#dotnet8#viewer#stateless#api#doconut#뷰어#무상태