# ExperionCrawler 코딩 컨벤션 ## 1. ASP.NET Core 컨트롤러 JSON 직렬화 ### 핵심 설정 `src/Web/Program.cs`: ```csharp builder.Services.AddControllers().AddJsonOptions(opt => { opt.JsonSerializerOptions.PropertyNamingPolicy = null; // PascalCase 직렬화 }); ``` `PropertyNamingPolicy = null`이므로 C# 속성명이 **그대로** JSON 키가 된다. 프론트엔드(`wwwroot/js/app.js`)는 모든 JSON 필드를 **camelCase**로 접근한다. ### 규칙: 컨트롤러 응답은 반드시 명시적 camelCase 익명 객체 사용 #### 금지 패턴 ```csharp // ❌ shorthand — "Id", "TagName"(PascalCase)이 JSON 키가 됨 → JS에서 undefined return Ok(new { x.Id, x.TagName, x.NodeId }); // ❌ typed 객체 직접 반환 — PascalCase 키 → JS에서 undefined return Ok(myDto); return Ok(new MyRecord { Id = 1, TagName = "abc" }); ``` #### 올바른 패턴 ```csharp // ✅ 명시적 소문자 키 — JS에서 r.id, r.tagName으로 정상 접근 return Ok(new { id = x.Id, tagName = x.TagName, nodeId = x.NodeId, liveValue = x.LiveValue, timestamp = x.Timestamp }); // ✅ 컬렉션 return Ok(new { total = r.Total, items = r.Items.Select(x => new { id = x.Id, tagName = x.TagName, nodeId = x.NodeId, dataType = x.DataType }) }); // ✅ C# 예약어 처리: @class → JSON "class" return Ok(new { id = x.Id, @class = x.Class, // JSON key: "class" name = x.Name }); ``` ### 프론트엔드 접근 방식 (참고) ```javascript // app.js에서 모든 응답 필드를 camelCase로 접근 items.forEach(x => { row.cells[0].textContent = x.id; // ← "id" (소문자) row.cells[1].textContent = x.tagName; // ← "tagName" (camelCase) row.cells[2].textContent = x.nodeId; // ← "nodeId" }); ``` ### 발생 이력 이 규칙을 어기면 다음 증상이 나타난다: - 테이블에 모든 셀이 빈칸 또는 `[undefined]` - 브라우저 콘솔에 오류 없음 (값이 `undefined`이므로 조용히 실패) - 서버 응답 자체는 정상 (Network 탭에서 데이터 확인 가능) **실제 발생 사례** (2026-04-26): - Browse 노드 목록: `n.NodeId`, `n.DisplayName` → `undefined` / "이름 없음" - NodeMap 대시보드: `x.Id`, `x.Level`, `x.Class` → `undefined` - PointBuilder 포인트 목록: `p.Id`, `p.TagName`, `p.LiveValue` → `undefined` --- ## 2. OPC UA SDK 버전 호환성 (v1.5.378.134) ### Session 생성 ```csharp // ❌ 구버전 — Session.Create()가 Task을 반환하므로 cast 실패 var session = (ISession)Session.Create(config, endpoint, false, name, 60000, identity, null); // ✅ 신버전 var session = await new DefaultSessionFactory(null).CreateAsync( config, endpoint, false, name, 60_000, identity, null, CancellationToken.None); ``` ### 인증서 검증 이벤트 `OpcUaConfigProvider.GetConfigAsync`에서 config를 빌드한 후 이벤트 핸들러를 등록해야 한다. `ExperionOpcClient.BuildConfigAsync`는 실제로 호출되지 않는 dead code이므로 거기에 등록해도 무효. ```csharp await config.ValidateAsync(ApplicationType.Client); config.CertificateValidator.CertificateValidation += (_, e) => { e.Accept = true; }; ``` --- ## 3. 컨트롤러 응답 구조 체크리스트 컨트롤러에서 `Ok(...)` 사용 시 반드시 확인: - [ ] 익명 객체의 **모든 키**가 camelCase인가? (`id`, `tagName`, `nodeId` ...) - [ ] `new { x.SomeProp }` shorthand가 **전혀** 없는가? - [ ] typed record/class를 `Ok()`에 **직접 전달**하지 않는가? - [ ] C# 예약어(`class`)에 `@` 접두사를 붙였는가?