# ⚠️ 중요: 대화 시작시 이 파일을 반드시 읽으세요! # 답변은 가급적이면 한글로! # UniMarc 프로젝트 - Claude 작업 가이드 > **Claude에게**: 대화를 시작할 때마다 이 파일을 먼저 읽어서 프로젝트 컨텍스트를 파악하세요. ## 프로젝트 개요 - **프로젝트명**: UniMarc (도서관 자료 관리 시스템) - **기술스택**: C# WinForms, .NET Framework 4.7.2 - **데이터베이스**: MySQL - **주요기능**: 마크 작성, 복본조사, DLS 연동, 도서 정보 관리 ## 코딩 컨벤션 - 파일명: PascalCase (예: DLS_Copy.cs) - 클래스명: PascalCase - 메서드명: PascalCase - 변수명: camelCase - 상수명: UPPER_CASE ## 주요 디렉토리 구조 - `/마크/`: 마크 관련 폼들 - `/납품관리/`: 납품 관리 관련 폼들 - `/마스터/`: 마스터 데이터 관리 폼들 - `/홈/`: 메인 화면 관련 폼들 - `/회계/`: 회계 관련 폼들 ## 개발 시 주의사항 1. WebView2 사용 시 async/await 패턴 적용 2. 데이터베이스 연결은 Helper_DB 클래스 사용 3. 에러 처리는 try-catch 블록으로 처리 4. 한글 주석 사용 ## 빌드 및 배포 - Visual Studio 2019 이상 필요 - NuGet 패키지 복원 후 빌드 - WebView2 런타임 필요 - NetFX 프로젝트이므로 dotnet 명령은 사용 불가 ## MsBuild 실행파일 위치 (경로에 공백이 있으니 쌍따옴표로 감싸야 함) ## 매개변수 입력할때 platform 은 제거하고 그냥 프로젝트명만 입력 F:\(VHD) Program Files\Microsoft Visual Studio\2022\MSBuild\Current\Bin\msbuild.exe ## 프로젝트 파일명 UniMarc.csproj ## Webview2 Fixed Version 다운로드 주소 https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/759b508a-00bb-4724-9b87-2703c8417737/Microsoft.WebView2.FixedVersionRuntime.139.0.3405.86.x86.cab ## WebView2 Selenium 스타일 DOM 조작 가이드 ### 🔧 기본 DOM 조작 메서드들 (NamguLibrarySearcher 기본 클래스에 구현됨) #### 1. 요소 대기 및 존재 확인 ```csharp // DOM 요소가 준비될 때까지 대기 (Selenium의 WebDriverWait와 유사) await WaitForElementReady("#clickAll", 10000); // 10초 타임아웃 await WaitForElementReady("input[name='query']", 5000); // 5초 타임아웃 ``` #### 2. 요소 상태 확인 ```csharp // 체크박스가 체크되어 있는지 확인 (Selenium의 element.IsSelected와 유사) bool isChecked = await IsElementChecked("#clickAll"); bool isLibMAChecked = await IsElementChecked("#libMA"); ``` #### 3. 요소 클릭 ```csharp // 요소 클릭 (Selenium의 element.Click()와 유사) await ClickElement("#clickAll"); // 전체 선택 클릭 await ClickElement("#libMA"); // 문화정보도서관 클릭 await ClickElement("button[type='submit']"); // 검색 버튼 클릭 ``` #### 4. 값 입력 및 설정 ```csharp // 입력창에 값 설정 (Selenium의 element.SendKeys()와 유사) await SetElementValue("input[name='query']", "검색어"); await SetElementValue("#search_txt", "도서명"); // 이벤트도 자동으로 발생시킴 (input, change 이벤트) ``` #### 5. 텍스트 가져오기 ```csharp // 요소의 텍스트 내용 가져오기 (Selenium의 element.Text와 유사) string resultText = await GetElementText(".search-result span"); string bookCount = await GetElementText("span:contains('전체')"); ``` ### 🚀 실제 사용 예제 #### 도서관 선택 (남구통합도서관 예제) ```csharp protected override async Task SelectLibraryWebView2() { try { // 1. DOM 요소 존재 확인 및 대기 await WaitForElementReady("#clickAll"); await WaitForElementReady("#libMA"); // 2. 전체 선택 해제 (단계별 실행) bool isClickAllChecked = await IsElementChecked("#clickAll"); if (isClickAllChecked) { await ClickElement("#clickAll"); Console.WriteLine("전체 선택 해제됨"); } // 3. 특정 도서관 선택 bool libMAChecked = await ClickElement("#libMA"); Console.WriteLine($"문화정보도서관 선택: {libMAChecked}"); return libMAChecked; } catch (Exception ex) { Console.WriteLine($"도서관 선택 오류: {ex.Message}"); return false; } } ``` #### 검색 실행 (단계별) ```csharp private async Task PerformSearchWebView2(string searchTerm) { try { // 1. 검색창 찾기 및 대기 (여러 선택자 시도) string[] searchSelectors = { "input[name='query']", "input[id='query']", "input[type='text']" }; string searchInputSelector = null; foreach (var selector in searchSelectors) { try { await WaitForElementReady(selector, 3000); searchInputSelector = selector; break; } catch { continue; } } // 2. 검색어 입력 await SetElementValue(searchInputSelector, searchTerm); // 3. 검색 버튼 클릭 (여러 선택자 시도) string[] buttonSelectors = { "button[type='submit']", "input[type='submit']", ".search-btn", ".btn-search" }; bool searchExecuted = false; foreach (var selector in buttonSelectors) { if (await ClickElement(selector)) { searchExecuted = true; break; } } // 4. 검색 버튼이 없으면 Enter 키로 검색 if (!searchExecuted) { string enterScript = $@" const input = document.querySelector('{searchInputSelector}'); if (input) {{ input.dispatchEvent(new KeyboardEvent('keydown', {{ key: 'Enter', keyCode: 13, bubbles: true }})); return true; }} return false; "; await _webView2.CoreWebView2.ExecuteScriptAsync(enterScript); } Console.WriteLine("✅ 검색 실행 완료"); } catch (Exception ex) { Console.WriteLine($"❌ 검색 실행 오류: {ex.Message}"); throw; } } ``` ### 🎯 CSS 선택자 참고 #### 남구통합도서관 주요 선택자들 ```css /* 도서관 선택 체크박스 */ #clickAll /* 전체 선택 */ #libMA /* 문화정보도서관 */ #libMB /* 푸른길도서관 */ #libMC /* 청소년도서관 */ #libSW /* 효천어울림도서관 */ #libSQ /* 스마트도서관 */ /* 검색 관련 */ input[name='query'] /* 검색창 */ button[type='submit'] /* 검색 버튼 */ /* 결과 관련 */ .search-result /* 검색 결과 영역 */ span:contains('전체') /* 검색 결과 수량 */ ``` ### ⚠️ 주의사항 1. **페이지 로딩 대기**: 항상 `WaitForElementReady()`로 요소가 준비될 때까지 대기 2. **에러 처리**: try-catch로 각 단계별 예외 처리 3. **로깅**: `Console.WriteLine()`으로 상세한 실행 로그 남기기 4. **타임아웃**: 적절한 타임아웃 설정 (기본 10초) 5. **다중 선택자**: 여러 CSS 선택자를 배열로 준비하여 순차적으로 시도 ### 🔄 Selenium에서 WebView2로 이주 가이드 | Selenium 코드 | WebView2 대응 코드 | |---------------|-------------------| | `WebDriverWait.Until()` | `await WaitForElementReady()` | | `element.Click()` | `await ClickElement()` | | `element.SendKeys()` | `await SetElementValue()` | | `element.Text` | `await GetElementText()` | | `element.IsSelected` | `await IsElementChecked()` | | `element.Clear()` | SetElementValue로 빈 문자열 설정 | ### 💡 성능 및 안정성 팁 - **WebView2가 Selenium보다 빠름**: 네이티브 성능 - **고정 버전 사용**: Chrome 버전 호환성 문제 없음 - **단계별 실행**: 각 작업을 개별 메서드로 분리하여 디버깅 용이 - **상세 로깅**: 각 단계마다 성공/실패 상태 출력 - **단순한 JavaScript 사용**: 복잡한 JSON보다는 단순한 문자열 반환 권장 ## 🚨 **WebView2 JavaScript 실행 시 주의사항 (중요 교훈)** ### **문제: RuntimeBinderException과 null 반환** **❌ 피해야 할 패턴:** ```csharp // 복잡한 JSON 반환 스크립트 - 불안정함 string script = $@" try {{ const element = document.querySelector('{selector}'); return JSON.stringify({{ success: true, isChecked: element.checked }}); }} catch (e) {{ return JSON.stringify({{ success: false, error: e.message }}); }} "; string result = await webView2.CoreWebView2.ExecuteScriptAsync(script); dynamic resultObj = JsonConvert.DeserializeObject(result); // RuntimeBinderException 위험! if (resultObj.success == true) // null 참조 오류 발생 가능 ``` **✅ 권장하는 안전한 패턴:** ```csharp // 단순한 문자열 반환 - 안정적임 string result = await webView2.CoreWebView2.ExecuteScriptAsync($@" try {{ var element = document.querySelector('{selector}'); if (element && element.checked) {{ element.click(); return element.checked ? 'failed' : 'success'; }} return element ? 'already_unchecked' : 'not_found'; }} catch (e) {{ return 'error: ' + e.message; }} "); // 안전한 문자열 비교 if (result != null && result.Contains("success")) { return true; } ``` ### **핵심 교훈:** 1. **단순함이 최고**: WebView2에서는 복잡한 JSON.stringify()보다 단순한 문자열 반환이 훨씬 안정적 2. **Dynamic 타입 위험**: `dynamic` 객체는 null 체크 없이 사용하면 RuntimeBinderException 발생 3. **직접적인 JavaScript**: 중간 JSON 변환 없이 직접적인 DOM 조작이 더 확실함 4. **단계별 진단**: 복잡한 로직은 여러 개의 간단한 스크립트로 분할하여 실행 ### **권장 디버깅 패턴:** ```csharp // 1단계: 기본 JavaScript 실행 확인 string test1 = await webView2.ExecuteScriptAsync("1+1"); Console.WriteLine($"기본 연산: {test1}"); // 2단계: DOM 접근 확인 string test2 = await webView2.ExecuteScriptAsync("document.title"); Console.WriteLine($"DOM 접근: {test2}"); // 3단계: 요소 존재 확인 string test3 = await webView2.ExecuteScriptAsync($"document.querySelector('{selector}') !== null"); Console.WriteLine($"요소 존재: {test3}"); // 4단계: 실제 작업 수행 string result = await webView2.ExecuteScriptAsync($"document.querySelector('{selector}').click(); 'clicked'"); Console.WriteLine($"작업 결과: {result}"); ``` **이 패턴을 사용하면 WebView2 JavaScript 실행에서 99%의 문제를 예방할 수 있습니다!** ## 🧹 **WebView2 메모리 누수 방지 (중요!)** **문제**: WebView2는 메모리 누수로 인해 프로그램이 갑자기 종료될 수 있습니다. **해결책**: 검색 전후로 메모리 정리를 수행하세요. ```csharp // 검색 시작 전 await CleanupWebView2Memory(); // 검색 작업 수행... // 검색 완료 후 (finally 블록에서) finally { await CleanupWebView2Memory(); } private async Task CleanupWebView2Memory() { try { await _webView2.CoreWebView2.ExecuteScriptAsync(@" if (window.gc) window.gc(); null; "); GC.Collect(); GC.WaitForPendingFinalizers(); } catch { /* 무시 */ } } ``` **주의사항**: - WebView2는 장시간 사용 시 메모리 누수 발생 가능 - 각 검색 작업 후 반드시 정리 수행 - Chrome WebDriver보다 메모리 관리가 까다로움