2026년 .NET MAUI 완벽 가이드: 크로스 플랫폼 개발과 면접 질문 정리

.NET MAUI 10으로 크로스 플랫폼 앱을 구축하는 방법을 핸들러 아키텍처, MVVM, HybridWebView, SafeAreaEdges와 함께 다루고, 2026년 기술 면접 핵심 질문을 정리합니다.

.NET MAUI 크로스 플랫폼 개발: C#으로 Android, iOS, 데스크톱을 단일 코드베이스에서 구현

.NET MAUI(Multi-platform App UI)는 .NET 10과 함께 프로덕션 수준의 크로스 플랫폼 프레임워크로 완전히 성숙한 단계에 접어들었습니다. 2028년 11월까지 지원되는 LTS(Long-Term Support) 릴리스로 출시된 .NET MAUI 10은 워크로드 및 NuGet 패키지 형태로 배포되며, 품질 개선, 성능 향상, HybridWebView 강화 및 SafeAreaEdges 같은 신규 API를 제공합니다. 이 글에서는 크로스 플랫폼 앱 구축 과정을 단계별로 살펴보고, MAUI의 핵심 아키텍처를 분석하며, 2026년 채용 현장에서 실제로 출제되는 면접 질문을 다룹니다.

.NET MAUI 10 LTS

.NET 10은 2028년 11월까지 지원되는 장기 지원(LTS) 릴리스입니다. MAUI 10은 새로운 UI 컨트롤 추가보다 품질과 성능 개선에 집중하여, 역대 가장 안정적인 MAUI 릴리스로 평가받고 있습니다. .NET 워크로드와 NuGet 패키지 방식으로 배포되어 프로젝트별 버전 고정(version pinning)이 가능합니다.

.NET MAUI 10 프로젝트 초기 설정

MAUI 앱을 가장 빠르게 실행하는 방법은 .NET CLI를 사용하는 것입니다. .NET 10에서는 .NET Aspire 서비스 기본값이 포함된 업데이트된 프로젝트 템플릿이 도입되어, 텔레메트리와 서비스 디스커버리가 기본적으로 연결됩니다.

bash
# Install the MAUI workload (if not already present)
dotnet workload install maui

# Create a new MAUI app
dotnet new maui -n CrossPlatformDemo
cd CrossPlatformDemo

# Run on Android emulator
dotnet build -t:Run -f net10.0-android

단일 프로젝트 구조는 플랫폼별 코드를 Platforms/ 폴더 아래에 통합하고 나머지 코드를 공유하는 방식입니다. MauiProgram.cs 파일이 컴포지션 루트(composition root) 역할을 하며, 서비스, 폰트, 핸들러 등록이 이곳에서 이루어집니다.

MauiProgram.cscsharp
using Microsoft.Extensions.Logging;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        // Register services for dependency injection
        builder.Services.AddSingleton<IApiService, ApiService>();
        builder.Services.AddTransient<MainViewModel>();

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

MAUI의 의존성 주입은 ASP.NET Core와 동일한 패턴을 따릅니다. Singleton 서비스는 앱 전체 수명 동안 유지되고, Transient 서비스는 요청마다 새로 생성됩니다. Scoped 서비스도 사용 가능하지만, MAUI에는 HTTP 요청처럼 내장된 스코프 개념이 없으므로 주의가 필요합니다.

핸들러 아키텍처: 크로스 플랫폼 렌더링의 핵심 구조

MAUI는 Xamarin.Forms의 렌더러(Renderer)를 핸들러(Handler) 아키텍처로 대체했습니다. 핸들러는 각 크로스 플랫폼 컨트롤을 얇은 추상화 계층을 통해 네이티브 대응 컨트롤에 매핑합니다. 핵심적인 차이점은 핸들러가 상태를 갖지 않으며(stateless) 가상 뷰(virtual view)와 분리되어 있다는 점으로, 이로 인해 커스터마이징이 더 빠르고 용이합니다.

CustomEntryHandler.cs — Customizing the Entry control on Androidcsharp
using Microsoft.Maui.Handlers;

public class CustomEntryHandler : EntryHandler
{
    protected override void ConnectHandler(MauiAppCompatEditText platformView)
    {
        base.ConnectHandler(platformView);
        // Remove the default underline on Android
        platformView.SetBackgroundColor(Android.Graphics.Color.Transparent);
    }
}

// Register in MauiProgram.cs
builder.ConfigureMauiHandlers(handlers =>
{
    handlers.AddHandler<Entry, CustomEntryHandler>();
});

.NET 10에서는 Android의 Entry 및 Editor 컨트롤이 AppCompatEditText에서 MauiAppCompatEditText로 전환되어, SelectionChanged 이벤트에 대한 네이티브 지원이 추가되었습니다. .NET 9에서 도입된 개선된 CollectionView와 CarouselView 핸들러는 이제 iOS 및 Mac Catalyst에서 기본값으로 적용되어, 오랫동안 지적되던 안정성 문제가 해결되었습니다.

CommunityToolkit.Mvvm을 활용한 MVVM 패턴: 보일러플레이트 제거

CommunityToolkit.Mvvm 소스 제너레이터를 사용하면 MVVM 패턴 구현에 필요한 반복 코드를 약 80% 수준으로 줄일 수 있습니다. 수동으로 INotifyPropertyChanged를 구현하거나 커맨드 래퍼를 작성할 필요 없이, 어트리뷰트만으로 코드가 자동 생성됩니다.

MainViewModel.cscsharp
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

public partial class MainViewModel : ObservableObject
{
    private readonly IApiService _apiService;

    public MainViewModel(IApiService apiService)
    {
        _apiService = apiService;
    }

    // Source generator creates the 'Title' property with change notification
    [ObservableProperty]
    private string _title = string.Empty;

    // Source generator creates the 'IsLoading' property
    [ObservableProperty]
    private bool _isLoading;

    // Source generator creates an async ICommand
    [RelayCommand]
    private async Task LoadDataAsync()
    {
        IsLoading = true;
        try
        {
            Title = await _apiService.FetchTitleAsync();
        }
        finally
        {
            IsLoading = false;
        }
    }
}

XAML에서는 생성된 프로퍼티와 커맨드에 직접 바인딩합니다.

xml
<!-- MainPage.xaml -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:CrossPlatformDemo.ViewModels"
             x:DataType="vm:MainViewModel">
    <VerticalStackLayout Padding="20" Spacing="16">
        <Label Text="{Binding Title}"
               FontSize="24"
               HorizontalOptions="Center" />
        <Button Text="Load Data"
                Command="{Binding LoadDataCommand}"
                IsEnabled="{Binding IsLoading, Converter={StaticResource InverseBoolConverter}}" />
        <ActivityIndicator IsRunning="{Binding IsLoading}"
                           IsVisible="{Binding IsLoading}" />
    </VerticalStackLayout>
</ContentPage>

x:DataType 어트리뷰트는 컴파일된 바인딩(compiled binding)을 활성화합니다. 리플렉션 기반 바인딩보다 빠르며, 바인딩 경로에 오류가 있을 경우 컴파일 시점에 오류가 발생하여 런타임 문제를 사전에 방지할 수 있습니다.

.NET 면접 준비가 되셨나요?

인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.

.NET 10의 HybridWebView: 네이티브와 웹의 연결

HybridWebView는 MAUI 앱 내부에 웹 콘텐츠를 삽입하면서 C#과 JavaScript 간 양방향 통신을 유지하는 기능을 제공합니다. .NET 10에서는 세 가지 기능이 추가되었습니다. Fire-and-forget 방식의 JavaScript 호출, 플랫폼별 설정을 위한 초기화 이벤트, 그리고 웹 요청 인터셉션입니다.

MainPage.xaml.cs — HybridWebView interactioncsharp
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        // Initialization event for platform-specific tweaks
        hybridWebView.WebViewInitialized += (sender, args) =>
        {
            // Access the native platform view after initialization
            System.Diagnostics.Debug.WriteLine("WebView ready");
        };
    }

    // Call JavaScript from C#
    private async void OnCallJsClicked(object sender, EventArgs e)
    {
        var result = await hybridWebView.InvokeJavaScriptAsync<string>(
            "getFormData",  // JS function name
            HybridSampleContext.Default.String  // JSON serialization context
        );
        await DisplayAlert("Result", result, "OK");
    }

    // Fire-and-forget: no return type needed (.NET 10)
    private async void OnResetClicked(object sender, EventArgs e)
    {
        await hybridWebView.InvokeJavaScriptAsync("resetForm");
    }
}

JavaScript 측에서는 C#의 호출을 수신하고, 반대로 C# 측으로 메시지를 전송할 수 있습니다.

wwwroot/index.html (HybridWebView content)javascript
function getFormData() {
    return JSON.stringify({
        name: document.getElementById('name').value,
        email: document.getElementById('email').value
    });
}

function resetForm() {
    document.getElementById('name').value = '';
    document.getElementById('email').value = '';
}

InvokeJavaScriptAsync 실행 중 JavaScript에서 발생한 예외는 이제 .NET 측으로 자동 전달되어 예외로 처리됩니다. 이전 버전에서 문제가 되던 사일런트 실패(silent failure)가 완전히 해소되었습니다.

SafeAreaEdges: 모든 디바이스에서 정밀한 레이아웃 구현

.NET MAUI 10은 Layout, ContentView, ContentPage, Border, ScrollView에 SafeAreaEdges 속성을 도입했습니다. None, SoftInput, Container, Default, All 값을 제공하는 새로운 열거형(enum)은 기존 iOS 전용이던 Page.UseSafeArea를 크로스 플랫폼 방식으로 대체합니다.

xml
<!-- Granular safe area control per section -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             SafeAreaEdges="All">
    <Grid RowDefinitions="Auto,*,Auto">
        <!-- Header respects all safe areas -->
        <Border Grid.Row="0" SafeAreaEdges="Container">
            <Label Text="Header" />
        </Border>

        <!-- Content scrolls under safe areas -->
        <ScrollView Grid.Row="1" SafeAreaEdges="SoftInput">
            <VerticalStackLayout Padding="16">
                <Entry Placeholder="Type here..." />
            </VerticalStackLayout>
        </ScrollView>

        <!-- Footer avoids home indicator -->
        <Border Grid.Row="2" SafeAreaEdges="Container">
            <Label Text="Footer" />
        </Border>
    </Grid>
</ContentPage>

SoftInput은 소프트 키보드가 나타날 때 콘텐츠를 자동으로 조정합니다. Container는 노치, 센서 하우징, 홈 인디케이터 영역을 피합니다. 컨트롤별로 이 값들을 조합할 수 있어, 헤더는 상태 바 아래에 위치시키면서 콘텐츠 영역은 안전하게 유지하는 세밀한 레이아웃 제어가 가능합니다.

Xamarin.Forms에서 .NET MAUI로의 마이그레이션

Xamarin.Forms는 2024년 5월에 공식 지원이 종료되었습니다. MAUI로의 마이그레이션은 단순한 네임스페이스 변경을 넘어 구조적인 수정이 필요합니다. 실제 프로젝트 전환 경험을 바탕으로 정리한 마이그레이션 체크리스트는 다음과 같습니다.

Xamarin 지원 종료

Xamarin.Forms는 2024년 5월부터 지원이 중단되었습니다. Xamarin 기반으로 운영 중인 애플리케이션은 보안 및 호환성 위험을 안고 있습니다. .NET MAUI 10(LTS, 2028년 11월까지 지원)이 공식 마이그레이션 대상입니다.

  1. 프로젝트 구조 -- 플랫폼별 개별 프로젝트를 MAUI 단일 프로젝트 모델로 전환합니다. 공유 코드는 루트로 이동하고, 플랫폼 코드는 Platforms/ 하위에 배치합니다.
  2. 네임스페이스 -- Xamarin.FormsMicrosoft.Maui.Controls로, Xamarin.EssentialsMicrosoft.Maui.Essentials(MAUI에 내장)로 교체합니다.
  3. 렌더러에서 핸들러로 -- 커스텀 렌더러는 핸들러로 재작성해야 합니다. 핸들러 API가 더 단순하지만 매핑 로직은 상이합니다.
  4. 시작 코드 -- App.xaml.cs 기반 초기화를 빌더 패턴을 사용하는 MauiProgram.cs로 전환합니다.
  5. NuGet 패키지 -- Xamarin 시대의 패키지 중 상당수에 MAUI 대응 패키지가 존재합니다. 업그레이드 전 호환성을 반드시 확인해야 합니다.
  6. 의존성 주입 -- MAUI는 Microsoft.Extensions.DependencyInjection을 네이티브로 사용합니다. 서드파티 DI 컨테이너나 DependencyService 호출은 모두 교체가 필요합니다.

.NET Upgrade Assistant가 1-2번 단계를 부분적으로 자동화해 주지만, 핸들러 전환(3번)과 비즈니스 로직 조정은 수작업이 불가피합니다.

2026년 .NET MAUI 핵심 면접 질문

다음은 .NET 10 생태계를 기준으로 2026년 채용 면접에서 실제로 출제되는 질문입니다.

MAUI 핸들러 아키텍처는 Xamarin.Forms 렌더러와 어떻게 다릅니까?

Xamarin.Forms의 렌더러는 크로스 플랫폼 컨트롤과 네이티브 뷰 양쪽에 강하게 결합된 양방향 의존성을 가지고 있었습니다. 반면 MAUI의 핸들러는 상태를 갖지 않는(stateless) 매퍼입니다. 프로퍼티 변경 알림을 수신하고 매퍼 딕셔너리를 통해 네이티브 뷰에 적용하는 방식입니다. 이러한 분리로 인해 핸들러는 테스트, 확장, 재사용이 용이합니다. PropertyMapperCommandMapper 딕셔너리가 기존의 OnElementPropertyChanged 오버라이드 패턴을 대체하며, switch 문 내부에 묻혀 있던 커스터마이징 로직이 명시적인 형태로 전환되었습니다.

MAUI에서 DI 수명 주기와 관련된 함정은 무엇입니까?

MAUI는 Singleton, Transient, Scoped 수명 주기를 모두 지원하지만, Scoped는 ASP.NET Core와 다르게 동작합니다. HTTP 요청 같은 자연스러운 스코프 경계가 존재하지 않기 때문입니다. MAUI에서 등록된 Scoped 서비스는 커스텀 스코프를 수동으로 생성하지 않는 한 Singleton처럼 동작합니다. 흔히 발생하는 실수로는, 페이지별 상태를 보유하는 ViewModel을 Singleton으로 등록하여 네비게이션 간 데이터가 잔존하는 경우, 또는 데이터베이스 연결을 Transient로 등록하여 커넥션 풀이 고갈되는 경우가 있습니다. 일반적인 원칙으로, ViewModel은 Transient, 서비스는 Singleton으로 등록하고, Scoped는 스코프 수명 주기를 명시적으로 관리하지 않는 한 사용하지 않는 것이 권장됩니다.

면접 팁

DI 관련 질문에 답변할 때는 MAUI의 수명 주기가 ASP.NET Core의 요청 기반 스코프 모델과 어떻게 다른지를 설명하는 것이 중요합니다. 면접관은 장시간 실행되는 모바일 앱에서 발생하는 메모리 누수와 상태 불일치 문제에 대한 인식 수준을 주로 평가합니다.

컴파일된 바인딩(Compiled Binding)과 리플렉션 기반 바인딩의 차이점은 무엇입니까?

리플렉션 기반 바인딩은 런타임에 System.Reflection을 사용하여 프로퍼티 경로를 해석하므로, 속도가 느리고 오타로 인한 오류가 런타임에서만 발견됩니다. x:DataType를 사용하여 활성화하는 컴파일된 바인딩은 바인딩 경로를 컴파일 시점에 해석합니다. 컴파일러가 리플렉션을 건너뛰는 직접 프로퍼티 접근 코드를 생성하여, 시작 시간 단축, 메모리 할당 감소, 빌드 시점 바인딩 오류 감지가 가능해집니다. .NET 10에서는 새로운 XAML 소스 제너레이터가 XAML을 런타임 파싱 대신 빌드 시점에 컴파일하여 이 최적화를 더욱 강화했습니다.

MAUI 앱과 ASP.NET Core 백엔드 간 코드 공유 전략은 무엇입니까?

권장되는 접근 방식은 DTO, 유효성 검증 로직, 비즈니스 규칙을 포함하는 공유 클래스 라이브러리를 사용하는 것입니다. MAUI 앱과 ASP.NET Core 백엔드가 모두 이 라이브러리를 참조합니다. .NET 10은 MAUI용 .NET Aspire 통합을 통해 이 패턴을 더욱 강화하며, 모바일과 백엔드 프로젝트 간 서비스 디스커버리와 텔레메트리를 제공합니다. System.Text.Json 소스 제너레이터를 활용한 공유 계약(contract)으로 직렬화 일관성을 보장할 수 있습니다. 핵심 제약 사항은 공유 라이브러리가 플랫폼별 TFM이 아닌 net10.0을 타겟으로 해야 이식성이 유지된다는 점입니다.

HybridWebView와 BlazorWebView는 어떤 차이가 있습니까?

BlazorWebView는 MAUI 앱 내부에서 완전한 Blazor 애플리케이션을 호스팅합니다. Razor 컴포넌트가 내장 WebView에 렌더링되지만, .NET 런타임은 WebAssembly가 아닌 네이티브로 실행됩니다. HybridWebView는 이보다 가벼운 구성으로, 정적 HTML/CSS/JS 콘텐츠를 로드하고 Blazor 프레임워크 오버헤드 없이 C#-JavaScript 간 인터롭을 제공합니다. 선택 기준은 사용 사례에 따라 달라집니다. BlazorWebView는 기존 Blazor 컴포넌트를 보유한 팀이 코드 재사용을 원할 때 적합합니다. HybridWebView는 대시보드, 지도, 에디터 같은 기존 웹 콘텐츠를 전체 프레임워크 없이 네이티브에 통합해야 하는 시나리오에 적합합니다.

SharpSkill .NET 면접 연습에서 이러한 개념을 인터랙티브 연습 문제로 심화 학습할 수 있습니다.

연습을 시작하세요!

면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

결론

  • .NET MAUI 10은 LTS 릴리스(2028년 11월까지 지원)로, 기능 추가보다 안정성과 성능에 집중하여 프로덕션 크로스 플랫폼 앱에 신뢰할 수 있는 선택지입니다
  • 핸들러 아키텍처는 Xamarin 렌더러를 상태를 갖지 않는 매퍼로 대체하여, PropertyMapperCommandMapper를 통한 테스트 용이성과 커스터마이징 편의성을 제공합니다
  • CommunityToolkit.Mvvm 소스 제너레이터는 MVVM 보일러플레이트 코드를 대폭 제거합니다. [ObservableProperty][RelayCommand] 어트리뷰트가 수동 INotifyPropertyChanged 및 커맨드 구현을 대체합니다
  • .NET 10의 HybridWebView는 fire-and-forget JavaScript 호출, 초기화 이벤트, 요청 인터셉션을 추가하여 네이티브-웹 통합을 강화했습니다
  • SafeAreaEdges는 디바이스 노치, 키보드, 시스템 바에 대한 세밀한 크로스 플랫폼 제어를 제공하며, 기존 iOS 전용이던 UseSafeArea를 대체합니다
  • Xamarin.Forms 마이그레이션에는 핸들러 재작성과 시작 코드 리팩토링이 필요하며, .NET Upgrade Assistant가 네임스페이스 변경은 처리하지만 커스텀 렌더러에 대한 수작업은 불가피합니다
  • 면접 준비 시 핸들러 대 렌더러 아키텍처, 장시간 실행 앱에서의 DI 수명 주기 함정, 컴파일된 바인딩, HybridWebView와 BlazorWebView의 트레이드오프에 집중하는 것이 효과적입니다

연습을 시작하세요!

면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

태그

#.net maui
#cross-platform
#tutorial
#interview
#xamarin migration
#.net 10
#mobile development

공유

관련 기사