이런 분이 읽으면 좋습니다
요약: WPF는 2006년에 출시됐지만 2026년에도 엔터프라이즈 데스크톱 앱의 핵심이다. 문제는 “WPF 자체”가 아니라 “2010년대 스타일로 작성된 WPF 코드”다. .NET 9 + Generic Host + CommunityToolkit.Mvvm 조합으로 WPF 앱을 현대적으로 구성하면, ASP.NET Core 수준의 DI·설정·로깅을 데스크톱에서 쓸 수 있고, MVVM 보일러플레이트를 90% 줄일 수 있다. 이 글은 새 WPF 프로젝트의 아키텍처 구성과 기존 프로젝트의 점진적 현대화 전략을 다룬다.
이 글은 WPF 앱을 유지보수하거나 신규 개발하는 C# 개발자를 위해 썼다. .NET Framework 4.x에서 .NET 9로의 마이그레이션을 고려하는 팀에도 참고가 된다.
2026년 WPF의 위치: 죽지 않았고, 대체되지도 않았다
“WPF는 레거시 아닌가요?” — 아니다. Microsoft는 .NET 9에서도 WPF를 적극 지원하고 있고, Windows 데스크톱 앱 시장에서 WPF의 점유율은 여전히 압도적이다. 금융 트레이딩 터미널, 의료 영상 뷰어, 산업 제어 시스템, 엔터프라이즈 관리 도구 등에서 WPF를 대체할 현실적 대안이 없는 경우가 많다.
| WPF (.NET 9) | WinUI 3 | MAUI | Avalonia | |
|---|---|---|---|---|
| 성숙도 | 20년, 매우 안정 | 성장 중, 일부 미완성 | 성장 중, 모바일 중심 | 성숙, 크로스플랫폼 |
| Windows 지원 | Win7~11 모두 | Win10 1809+만 | Win10+ | Win/Mac/Linux |
| 생태계 | 최대 (Telerik, DevExpress 등) | 성장 중 | 제한적 | 성장 중 |
| 기존 자산 호환 | 기존 XAML 100% 호환 | XAML 유사하나 비호환 | XAML 유사하나 비호환 | 유사 XAML, 비호환 |
| DirectX 통합 | DirectX 9/11 | DirectX 12 (WinAppSDK) | 플랫폼 의존 | Skia 기반 |
| 적합 시나리오 | 엔터프라이즈 LOB, 레거시 현대화 | Windows 전용 신규 앱 | 크로스플랫폼 모바일+데스크톱 | 크로스플랫폼 데스크톱 |
모던 WPF 아키텍처 구성 — 핵심 4계층
전체 구조
MyApp.sln
├── MyApp/ # WPF 프로젝트 (진입점)
│ ├── App.xaml / App.xaml.cs # Generic Host 통합
│ ├── Views/ # XAML View 파일
│ ├── appsettings.json # 설정 파일
│ └── Hosting/ # Host 연결 코드
├── MyApp.Core/ # 순수 C# 라이브러리 (UI 무관)
│ ├── ViewModels/ # CommunityToolkit.Mvvm ViewModel
│ ├── Models/ # 도메인 모델
│ ├── Services/ # 비즈니스 로직
│ └── Interfaces/ # 서비스 인터페이스
└── MyApp.Tests/ # 단위 테스트
└── ViewModelTests/
핵심 원칙: ViewModel과 비즈니스 로직은 WPF 참조 없는 순수 .NET 프로젝트에 분리한다. 이렇게 하면 ViewModel을 콘솔 앱이나 테스트 프로젝트에서 WPF 없이 실행할 수 있다.
1계층: Generic Host 통합 — App.xaml.cs
WPF의 Application 라이프사이클과 .NET Generic Host의 라이프사이클을 연결하는 것이 첫 번째 단계다.
이 패턴의 이점:
- DI 컨테이너 — 생성자 주입으로 의존성 관리,
new키워드로 직접 생성하지 않음 - 설정 관리 —
appsettings.json+ 환경별 오버라이드 (appsettings.Development.json) - 로깅 —
ILogger<T>표준 인터페이스, Serilog/NLog 플러그인 - HttpClient —
IHttpClientFactory패턴으로 소켓 고갈 방지
2계층: CommunityToolkit.Mvvm — 보일러플레이트 제거
CommunityToolkit.Mvvm(이전 Microsoft.Toolkit.Mvvm)은 소스 생성기 기반의 MVVM 라이브러리다. 어트리뷰트만 붙이면 컴파일 타임에 코드가 자동 생성된다.
기존 방식 (수동 INotifyPropertyChanged):
// 30줄 — 필드 1개, 커맨드 1개에 이 양의 보일러플레이트
public class MainViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
private ICommand _saveCommand;
public ICommand SaveCommand => _saveCommand ??=
new RelayCommand(Save, () => !string.IsNullOrEmpty(Name));
private void Save() { /* ... */ }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
CommunityToolkit.Mvvm 방식:
소스 생성기가 자동으로 만드는 것:
Name프로퍼티 (get/set +PropertyChanged이벤트 발화)SaveCommand(IRelayCommand구현)CanExecute연결 (NotifyCanExecuteChangedFor)
3계층: 서비스 계층 — 인터페이스 기반 설계
핵심 패턴:
- 인터페이스 분리 — ViewModel은
IDataService에만 의존, 구현은 DI가 주입 - 비동기 우선 — 모든 I/O 작업은
async/await, UI 스레드 블로킹 방지 - CancellationToken — 사용자가 화면을 전환하면 진행 중인 쿼리를 취소 가능
- DbContextFactory —
DbContext를 매번 새로 생성하여 동시성 문제 방지
4계층: View — XAML은 얇게
View 원칙: 코드비하인드에 비즈니스 로직을 넣지 않는다. DataContext 설정과 순수 UI 조작(드래그&드롭, 포커스 이동 등 XAML로 할 수 없는 것)만 허용.
네비게이션: 화면 전환 패턴
WPF에서 화면 전환은 크게 두 가지 패턴이 있다.
| ContentControl 교체 | Window 기반 네비게이션 | |
|---|---|---|
| 구조 | 단일 Window + ContentControl.Content 교체 | 화면별 별도 Window |
| 적합 시나리오 | 탭/사이드바 기반 SPA 스타일 | 다이얼로그, 독립 창 |
| DI 연동 | INavigationService가 ViewModel 교체 | Window를 DI로 생성 |
| 상태 관리 | ViewModel 생존 주기 제어 용이 | Window 닫히면 상태 소멸 |
| 추천 | 대부분의 LOB 앱에 적합 | 설정 창, 모달 다이얼로그에 적합 |
테스트: ViewModel은 WPF 없이 테스트
CommunityToolkit.Mvvm + 인터페이스 기반 설계의 가장 큰 이점은 ViewModel 단위 테스트가 WPF 참조 없이 가능하다는 것이다.
기존 WPF 프로젝트 현대화 전략
.NET Framework → .NET 9 마이그레이션 순서
Phase 1: 빌드 시스템 전환 (위험 낮음)
.csproj를 SDK 스타일로 변환 (dotnet try-convert도구)- NuGet
packages.config→PackageReference - 이 시점에서는 여전히 .NET Framework 타겟
Phase 2: .NET 9 타겟 전환 (위험 중간)
<TargetFramework>변경:net48→net9.0-windows- 호환되지 않는 NuGet 패키지 대체
- WCF → gRPC/REST, Remoting → 직접 통신으로 전환
Microsoft.Windows.Compatibility패키지로 Win32 API 호환성 확보
Phase 3: 아키텍처 현대화 (위험 낮음, 점진적)
- Generic Host + DI 도입 (App.xaml.cs 수정)
- 한 ViewModel씩 CommunityToolkit.Mvvm으로 전환
- 서비스 계층 인터페이스 추출 + DI 등록
- 기존 싱글턴/정적 클래스 → DI 관리 서비스로 이관
Phase 4: 테스트 추가 (점진적)
- 새로 전환한 ViewModel부터 단위 테스트 추가
- 레거시 코드는 변경할 때만 테스트 추가 (보이스카우트 규칙)
WPF vs 전면 교체: 판단 기준
- Win10 이상만 지원해도 되고, WinUI 3의 최신 컨트롤이 필요한 경우
- 크로스플랫폼이 필수인 경우 (Mac/Linux → Avalonia)
- 모바일까지 확장해야 하는 경우 (→ MAUI)
- 기존 WPF 코드가 거의 없고 신규 개발에 가까운 경우
그 외에는 기존 WPF + .NET 9 + Generic Host + CommunityToolkit.Mvvm이 2026년 가장 현실적인 선택이다.
피해야 할 안티패턴
결론: WPF는 레거시가 아니라 “레거시 패턴으로 작성된 WPF”가 레거시다
2026년 WPF 모던 아키텍처의 핵심 스택:
- .NET 9 — 최신 런타임, 성능 향상, C# 13 문법
- Generic Host — DI + Configuration + Logging 통합
- CommunityToolkit.Mvvm — 소스 생성기로 MVVM 보일러플레이트 제거
- EF Core + DbContextFactory — 비동기 데이터 액세스
- xUnit + NSubstitute — ViewModel 단위 테스트
이 구성이면 ASP.NET Core 프로젝트와 동일한 수준의 구조, 테스트 가능성, 유지보수성을 데스크톱 앱에서 달성할 수 있다. WPF 자체를 바꿀 필요가 아니라, WPF 위에서 작성하는 코드의 구조를 바꾸면 된다.
다음에 읽을 글
- 프로그래밍 언어 비교 2026: 특징·성능·활용 분야별 선택 가이드 — C#의 포지션과 다른 언어 비교
- 소프트웨어 아키텍처 진화사: 메인프레임에서 AI 에이전트까지 — 데스크톱 → 웹 → 클라우드 전환의 큰 그림
- 모듈러 모놀리스 vs 마이크로서비스 — 데스크톱 앱 백엔드의 아키텍처 선택