Files
Unimarc/unimarc/CLAUDE.md

12 KiB

⚠️ 중요: 대화 시작시 이 파일을 반드시 읽으세요!

답변은 가급적이면 한글로!

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. 요소 대기 및 존재 확인

// DOM 요소가 준비될 때까지 대기 (Selenium의 WebDriverWait와 유사)
await WaitForElementReady("#clickAll", 10000);  // 10초 타임아웃
await WaitForElementReady("input[name='query']", 5000);  // 5초 타임아웃

2. 요소 상태 확인

// 체크박스가 체크되어 있는지 확인 (Selenium의 element.IsSelected와 유사)
bool isChecked = await IsElementChecked("#clickAll");
bool isLibMAChecked = await IsElementChecked("#libMA");

3. 요소 클릭

// 요소 클릭 (Selenium의 element.Click()와 유사)
await ClickElement("#clickAll");        // 전체 선택 클릭
await ClickElement("#libMA");           // 문화정보도서관 클릭
await ClickElement("button[type='submit']");  // 검색 버튼 클릭

4. 값 입력 및 설정

// 입력창에 값 설정 (Selenium의 element.SendKeys()와 유사)
await SetElementValue("input[name='query']", "검색어");
await SetElementValue("#search_txt", "도서명");
// 이벤트도 자동으로 발생시킴 (input, change 이벤트)

5. 텍스트 가져오기

// 요소의 텍스트 내용 가져오기 (Selenium의 element.Text와 유사)
string resultText = await GetElementText(".search-result span");
string bookCount = await GetElementText("span:contains('전체')");

🚀 실제 사용 예제

도서관 선택 (남구통합도서관 예제)

protected override async Task<bool> 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;
    }
}

검색 실행 (단계별)

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 선택자 참고

남구통합도서관 주요 선택자들

/* 도서관 선택 체크박스 */
#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 반환

피해야 할 패턴:

// 복잡한 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 참조 오류 발생 가능

권장하는 안전한 패턴:

// 단순한 문자열 반환 - 안정적임
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. 단계별 진단: 복잡한 로직은 여러 개의 간단한 스크립트로 분할하여 실행

권장 디버깅 패턴:

// 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는 메모리 누수로 인해 프로그램이 갑자기 종료될 수 있습니다.

해결책: 검색 전후로 메모리 정리를 수행하세요.

// 검색 시작 전
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보다 메모리 관리가 까다로움