1. 서론 — DI를 설정했는데 서비스가 null? MAUI에서는 매우 흔한 문제
.NET MAUI는 기본적으로 .NET Generic Host 기반의 Dependency Injection(의존성 주입) 기능을 지원한다.
이에 따라 ViewModel, Repository, API Client, Service 객체 등을 DI 형태로 등록하고, 페이지에서 생성자 주입 또는 ServiceProvider를 통해 가져오는 것이 자연스러운 패턴이다.
그러나 실제 프로젝트에서는 다음과 같은 현상이 자주 발생한다.
- 등록한 서비스가 null로 전달된다
- 생성자 주입이 작동하지 않는다
- Shell 페이지에서는 DI가 적용되지 않는다
- Navigation으로 이동한 페이지에서 서비스가 누락된다
- IHostBuilder 설정은 맞는데 ServiceProvider가 비어 있다
- Scoped 서비스가 동작하지 않거나 매번 새 인스턴스가 만들어진다
이 글에서는 이러한 문제들이 왜 발생하는지 구조적으로 분석하고, 실전 프로젝트에서 DI가 안정적으로 동작하도록 만드는 방법을 제시한다.

2. MAUI DI가 실패하는 핵심 원인 분석
MAUI DI는 ASP.NET Core와 비슷하지만, UI 프레임워크 특성 때문에 삽질 포인트가 몇 가지 있다.
가장 중요한 5가지 원인을 분석한다.
원인 1: DI 컨테이너에 등록했지만 실제 페이지는 생성자 주입을 사용하지 않음
가장 많이 발생하는 실수다.
다음과 같이 서비스를 등록했다고 하자.
그리고 DetailPage에서 다음처럼 사용하려고 한다.
{
private readonly IMyService _service;
public DetailPage(IMyService service)
{
InitializeComponent();
_service = service;
}
}
하지만 Navigation 시점에 Shell은 페이지를 DI로 생성하지 않는다.
즉, 다음 코드:
이렇게 이동하면 생성자에 서비스를 주입하지 않은 새 인스턴스를 만든다.
→ 서비스가 null로 전달된다
→ 생성자 주입 자체가 무효화된다
원인 2: Shell은 페이지를 직접 new DetailPage()로 생성한다
MAUI Shell은 내부적으로 페이지를 생성할 때 DI 컨테이너를 사용하지 않는다.
따라서 다음 구조에서 문제가 발생한다.
이렇게 등록했더라도
이 Navigation 호출에서는 등록된 DetailPage가 아닌, 새 DetailPage() 인스턴스를 만든다.
이는 MAUI 구조상의 제한으로, 많은 개발자가 이 문제를 모르고 헤맨다.
원인 3: ViewModel을 DI로 등록했는데, 페이지 XAML에서 BindingContext를 덮어버리는 경우
예:
<local:DetailViewModel />
</ContentPage.BindingContext>
이 패턴은 DI로 등록한 ViewModel을 완전히 무효화한다.
→ DI를 통해 주입된 ViewModel이 아닌 새 ViewModel()이 만들어짐
→ 내부 서비스도 null
→ 커맨드/바인딩 모두 불일치
XAML BindingContext 설정은 매우 위험하다.
원인 4: Scoped 또는 Transient 등록 시 Navigation과 함께 인스턴스가 여러 개 생성됨
예:
Navigation을 여러 번 하면 DetailViewModel이 매번 새로 생성된다.
특히 API Token이나 상태 보관이 필요한 서비스에서 치명적이다.
원인 5: ServiceProvider를 직접 가져오는 코드를 잘못 사용하는 경우
초보자가 자주 쓰는 코드:
문제는
- App이 생성되기 전에는 Services가 null
- MainPage보다 빨리 접근하면 오류
- Shell이 페이지를 생성할 때 서비스가 초기화되지 않은 상태일 수 있음
정확한 DI 초기화 시점을 알지 못하면 서비스 접근이 실패한다.
3. 실전에서 발생하는 대표 문제 상황 3가지
❌ 문제 상황 1 — ViewModel DI 실패
<local:MainViewModel />
</ContentPage.BindingContext>
ViewModel이 XAML에서 직접 생성되었기 때문에
Services.AddSingleton보다 항상 우선 적용된다.
❌ 문제 상황 2 — Shell 등록 누락
AppShell.xaml:
이 방식은 Shell이 DetailPage를 직접 new DetailPage()로 생성한다.
DI와 완벽히 충돌한다.
❌ 문제 상황 3 — 생성자 주입은 했지만 페이지 생성 방식이 잘못됨
Navigation에서는 생성자 주입이 적용되지 않는다.

4. MAUI DI를 안정적으로 동작시키는 정석 구조
아래는 실무 프로젝트에서 가장 안정적으로 검증된 패턴들이다.
✔ 정석 1: Shell 대신 DI가 생성한 페이지를 Navigation에 사용
App.xaml.cs 또는 DI 확장 메서드에서 등록:
그리고 Navigation 시 다음처럼 사용:
또는:
await Shell.Current.Navigation.PushAsync(page);
이 방식은 100% DI 주입된 페이지를 사용한다.
✔ 정석 2: XAML에서 BindingContext 설정 금지
이 구조는 올바르다:
{
InitializeComponent();
BindingContext = vm;
}
이 구조는 올바르지 않다:
<local:DetailViewModel />
</ContentPage.BindingContext>
✔ 정석 3: ViewModel도 반드시 DI로 등록
✔ 정석 4: Shell Route 등록 시 ContentTemplate 사용 금지
잘못된 예:
올바른 예:
그리고 Navigation 시:
단, 이 경우 생성자 DI는 적용되지 않으므로
DI 페이지를 Navigation에 직접 전달하는 방식을 조합해야 한다.
✔ 정석 5: Singleton과 Transient를 올바르게 선택
- 앱 전역 상태 유지 → Singleton
- 화면 이동 시마다 새로 생성 → Transient
- 로그인 정보 / Token 관리 → Singleton
- UI 상태 관리 → Transient(ViewModel)
5. 마무리 — DI 구조를 이해하면 MAUI 아키텍처가 완성된다
MAUI에서 DI가 실패하는 이유는 단순히 서비스 등록 문제 때문이 아니라
Shell 구조, Navigation 방식, Page 생성 타이밍, BindingContext 설정 방식, ViewModel 라이프사이클 등
여러 시스템이 복잡하게 연결되어 있기 때문이다.
핵심 포인트는 다음이다:
- Shell은 DI 컨테이너를 사용해 페이지를 생성하지 않는다
- 따라서 Navigation에서는 항상 DI로 생성된 페이지를 사용해야 한다
- XAML에서 ViewModel을 생성하면 DI는 절대 동작하지 않는다
- ViewModel과 Page는 DI로만 생성되게 강제해야 한다
- BindingContext는 CodeBehind에서만 설정해야 한다
이 원리를 이해하면
MAUI 아키텍처 전체가 안정되고
예측 가능한 구조를 만들 수 있다.
'MAUI CollectionView 문제 해결 > Korean Version' 카테고리의 다른 글
| MAUI MVVM Command가 두 번 실행되는 문제 해결, 이벤트 버블링부터 GestureRecognizer까지 구조적 분석 (0) | 2025.12.11 |
|---|---|
| MAUI CollectionView에서 체크박스 선택 상태가 반영되지 않을 때 반드시 확인해야 하는 구조적 문제와 해결 전략 (0) | 2025.12.11 |
| MAUI CommandParameter 전달 실패 문제 해결, 구조적 원인 분석과 실전 대응 전략 (0) | 2025.12.10 |
| MAUI CollectionView 이벤트 중복 실행 문제 완전 분석 (0) | 2025.12.10 |
| MAUI BindingContext 꼬임 문제 해결, 데이터 바인딩 실패 원인과 구조적 해결 전략 (0) | 2025.12.10 |