wwwroot
wwwroot는 HandStack 화면과 정적 리소스를 제공하는 모듈입니다. 계약 기반 화면 리소스는 ContractRequestPath 아래에 노출하고, 실제 정적 자산은 WWWRootBasePath에서 서비스하며, 공통 파일 카탈로그에 등록한 호스트 파일도 지정 URL로 제공합니다.
책임 범위
contracts/wwwroot의 애플리케이션별 화면 리소스를 기본/view경로로 노출합니다.modules/wwwroot/wwwroot아래의 실제 정적 파일을 서비스합니다.shared_files.json카탈로그의 요청 경로를 기존 정적 파일 루트 밖의 호스트 파일에 연결합니다.- 정적 파일 요청에 대해 대소문자 무시 탐색과 캐시 정책을 적용합니다.
- Basic token 기반 파일 동기화 API를 제공합니다.
- 계약 파일 업로드 후 대상 모듈의 refresh API를 호출해 런타임 계약 캐시를 갱신합니다.
주요 API
| 메서드 | 경로 | 용도 |
|---|---|---|
POST | /wwwroot/api/sync/upload | 계약 또는 정적 파일을 업로드 동기화합니다. |
GET | /wwwroot/api/sync/refresh | 대상 모듈 refresh API를 호출합니다. |
GET | /wwwroot/api/htmx/* | HTMX 샘플/보조 API입니다. |
GET | /wwwroot/api/index/* | 화면 보조 API와 거래 직접 호출 래퍼입니다. |
GET/POST | /wwwroot/api/dev-account/sign-in | 개발 목적 테스트 계정 로그인입니다. GET은 DevAutoSignIn의 고정 계정을 사용하고, POST는 JSON 본문의 사용자 정보를 사용합니다. AppSettings:IsEnabledDevAutoSignIn이 true이고 RunningEnvironment가 D일 때만 동작하며, 그 외에는 404를 반환합니다. |
GET | /shared-files/manifest | 공통 파일의 공개 요청 경로 목록을 반환합니다. |
핵심 구현
ModuleInitializer.cs: 정적 파일 provider, 계약 리소스 경로, CORS/필터 처리를 구성합니다.Extensions/SharedFileServingMiddleware.cs: 공통 파일 요청 경로를 호스트 파일에 연결합니다.Entity/SharedFileCatalog.cs:items[].requestPath/hostFilePath카탈로그 스키마입니다.wwwroot/js/syn.loader.js: manifest의 공통 CSS/JS와 기타 텍스트 자산을 브라우저 로 딩 과정에 추가합니다.Areas/wwwroot/Controllers/SyncController.cs: 파일 업로드와 refresh 동기화 API입니다.Areas/wwwroot/Controllers/IndexController.cs: 화면 보조 API와transact직접 호출을 제공합니다.Areas/wwwroot/Controllers/DevAccountController.cs:checkup의AccountController.SignIn쿠키 발급 로직을 재사용한 개발용 테스트 계정 자동 로그인 API입니다.Extensions/ModuleApiClient.cs: 화면에서 업무 거래를 직접 호출하는 래퍼입니다.Contracts/wwwroot: 계약 기반 화면/자산 매핑입니다.
주요 설정
| 설정 | 설명 |
|---|---|
ContractRequestPath | 계약 기반 정적 리소스 요청 경로입니다. 기본값은 view입니다. |
ContractBasePath | 계약 기반 화면 리소스 루트입니다. |
WWWRootBasePath | 실제 정적 파일 루트입니다. |
SharedFileConfigPath | 공통 파일 카탈로그 JSON 경로입니다. 빈 값이면 사용하지 않습니다. |
FileSyncTokens | /wwwroot/api/sync/* 호출을 허용할 Basic token 목록입니다. |
BusinessServerUrl | 화면 보조 API가 거래를 실행할 transact URL입니다. |
ModuleLogFilePath | wwwroot 모듈 로그 파일 경로입니다. |
DevAutoSignIn | GET /wwwroot/api/dev-account/sign-in이 발급할 고정 테스트 계정입니다. UserNo/UserID/UserName/Email/Roles와, HandStack.Web.Entity.UserAccount의 선택 프로필 필드를 그대로 따르는 Celluar/PositionName/DepartmentName/CompanyName/BirthDate/Gender/Address/ExtendOption을 설정합니다(빈 문자열은 미설정으로 취급). POST 요청은 이 설정 대신 같은 형식의 JSON 본문을 사용합니다. 호스트의 AppSettings:IsEnabledDevAutoSignIn이 true이고 RunningEnvironment가 D일 때만 사용됩니다. |
POST 요청은 다음처럼 Content-Type: application/json과 사용자 정보를 보냅니다. UserID는 필수이며, 기존 로그인 쿠키가 있으면 요청 정보와 관계없이 기존 동작처럼 returnUrl로 이동합니다.
{
"UserNo": "USERNO001",
"UserID": "user01@handstack.kr",
"UserName": "사용자",
"Email": "user01@handstack.kr",
"Roles": [ "Administrator" ],
"Celluar": "010-0000-0000",
"PositionName": "담당자",
"DepartmentName": "업무팀",
"CompanyName": "HandStack",
"BirthDate": "2026-01-01",
"Gender": "M",
"Address": "서울특별시 중구 세종대로 110",
"ExtendOption": "..."
}
공통 파일 카탈로그
module.json에서 카탈로그 경로를 지정합니다.
"SharedFileConfigPath": "../config/shared_files.json"
shared_files.json은 다음 형식입니다.
{
"items": [
{
"requestPath": "/assets/common.css",
"hostFilePath": "../shared-files/common.css"
},
{
"requestPath": "/assets/common.js",
"hostFilePath": "../shared-files/common.js"
},
{
"requestPath": "/assets/settings.json",
"hostFilePath": "../shared-files/settings.json"
},
{
"requestPath": "/assets/fragment.html",
"hostFilePath": "../shared-files/fragment.html"
}
]
}
예제 파일은 shared_files.json, common.css, common.js, settings.json과 /sample/wwwroot/shared-files/fragment.html에서 확인할 수 있습니다. 다음처럼 배치하면 위 상대경로가 일치합니다.
HANDSTACK_HOME/
├─ app/
│ └─ ack
├─ config/
│ └─ shared_files.json
└─ shared-files/
├─ common.css
├─ common.js
├─ settings.json
└─ fragment.html
SharedFileConfigPath와hostFilePath의 상대경로는 모두 카탈로그 디렉터리가 아니라 ack 실행 기준 경로인GlobalConfiguration.EntryBasePath에서 해석됩니다. 환경 변수와 절대경로도 사용할 수 있습니다.requestPath는HttpContext.Request.Path와 대소문자를 무시하고 정확히 비교하므로/로 시작해야 합니다. 쿼리 문자열은 비교 대상이 아닙니다.requestPath또는hostFilePath가 비어 있는 항목은 적재하지 않습니다.- 중복 요청 경로는 첫 번째 항목이 우선하며, 대상 파일이 없으면 다음 미들웨어로 넘어갑니다.
- 파일 확장자로 MIME 형식을 판단하고 알 수 없는 형식은
application/octet-stream으로 응답합니다. - 카탈로그는 모듈 초기화 때 한 번 적재되므로 파일을 수정한 뒤 ack를 다시 시작해야 합니다.
GET /shared-files/manifest는 hostFilePath를 제외한 다음 형식의 요청 경로 목록을 반환합니다.
[
{ "requestPath": "/assets/common.css" },
{ "requestPath": "/assets/common.js" },
{ "requestPath": "/assets/settings.json" },
{ "requestPath": "/assets/fragment.html" }
]
syn.loader.js는 화면 시작 시 manifest를 no-cache로 조회합니다.
| 확장자 | 브라우저 처리 |
|---|---|
.css | 기존 스타일 로드 목록에 추가합니다. |
.js | 기존 스크립트 로드 목록에 추가합니다. |
.html, .htm | type="text/html" 텍스트 자산으로 <head>에 추가합니다. |
.json | type="application/json" 텍스트 자산으로 <head>에 추가합니다. |
.xml | type="application/xml" 텍스트 자산으로 <head>에 추가합니다. |
.md | type="text/markdown" 텍스트 자산으로 <head>에 추가합니다. |
.txt | type="text/plain" 텍스트 자산으로 <head>에 추가합니다. |
.csv | type="text/csv" 텍스트 자산으로 <head>에 추가합니다. |
| 그 외 | type="text/plain" 텍스트 자산으로 <head>에 추가합니다. |
텍스트 자산 ID는 마지막 확장자를 제거한 파일명에서 만들고 영문·숫자·밑줄 이외의 문자는 _로 바꿉니다. 다음과 같이 JSON이나 HTML 내용을 읽을 수 있습니다.
const settings = JSON.parse(document.getElementById('shared_settings').textContent);
const fragment = document.getElementById('shared_fragment').innerHTML;
CSS/JS 분기 비교는 대소문자를 구분하므로 파일 확장자는 소문자로 작성합니다. 서로 다른 경로라도 확장자를 제외한 파일명이 같으면 DOM ID가 중복될 수 있으므로 고유한 파일명을 사용합니다. manifest 또는 텍스트 파일 로딩 예외는 로더 로그에 남고 나머지 화면 초기화는 계속됩니다.
공통 파일 미들웨어와 manifest는 세션·인증 미들웨어보다 먼저 실행하고 자체 인증·권한 검사를 하지 않습니다. manifest가 요청 경로 목록도 공개하므로 카탈로그와 대상 파일은 공개 가능한 자산만 등록하고 카탈로그 쓰기 권한을 제한해야 합니다.