종속성 주입

.NET 다중 플랫폼 앱 UI(.NET MAUI)는 종속성 주입을 사용하기 위한 기본 제공 지원을 제공합니다. 종속성 주입은 IoC(Inversion of Control) 패턴의 특수 버전으로, 반전되는 문제는 필요한 종속성을 가져오는 프로세스입니다. 종속성 주입을 사용하면 다른 클래스가 런타임에 개체에 종속성을 주입합니다.

일반적으로 클래스 생성자는 개체를 인스턴스화할 때 호출되고 개체에 필요한 모든 값은 생성자에 인수로 전달됩니다. 이것은 생성자 주입이라고 하는 종속성 주입의 예입니다. 개체에 필요한 종속성이 생성자에 주입됩니다.

참고 항목

속성 setter 삽입 및 메서드 호출 주입과 같은 다른 유형의 종속성 주입도 있지만 덜 일반적으로 사용됩니다.

종속성을 인터페이스 형식으로 지정하면 종속성 주입을 통해 이러한 형식에 종속된 코드에서 구체적인 형식을 분리할 수 있습니다. 일반적으로 인터페이스와 추상 형식 간의 등록 및 매핑 목록을 포함하는 컨테이너와 이러한 형식을 구현하거나 확장하는 구체적인 형식을 사용합니다.

종속성 주입 컨테이너

클래스가 필요한 개체를 직접 인스턴스화하지 않는 경우 다른 클래스가 이 책임을 져야 합니다. 생성자 인수가 필요한 뷰 모델 클래스를 보여 주는 다음 예제를 살펴보겠습니다.

public class MainPageViewModel
{
    readonly ILoggingService _loggingService;
    readonly ISettingsService _settingsService;

    public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
    {
        _loggingService = loggingService;
        _settingsService = settingsService;
    }
}

이 예제 MainPageViewModel 에서 생성자에는 다른 클래스에서 삽입한 인수로 두 개의 인터페이스 개체 인스턴스가 필요합니다. MainPageViewModel 클래스의 유일한 종속성은 인터페이스 형식에 있습니다. 따라서 MainPageViewModel 클래스에는 인터페이스 개체 인스턴스화를 담당하는 클래스에 대한 지식이 없습니다.

마찬가지로 생성자 인수가 필요한 페이지 클래스를 보여 주는 다음 예제를 살펴보겠습니다.

public MainPage(MainPageViewModel viewModel)
{
    InitializeComponent();

    BindingContext = viewModel;
}

이 예제 MainPage 에서 생성자는 다른 클래스에 의해 삽입되는 인수로 구체적인 형식이 필요합니다. 클래스의 MainPage 유일한 종속성은 형식에 있습니다 MainPageViewModel . 따라서 클래스에는 MainPage 구체적인 형식을 인스턴스화하는 클래스에 대한 지식이 없습니다.

두 경우 모두 종속성을 인스턴스화하고 종속 클래스에 삽입하는 클래스를 종속성 주입 컨테이너라고 합니다.

종속성 주입 컨테이너는 클래스 인스턴스를 인스턴스화하고 컨테이너의 구성에 따라 수명을 관리하는 기능을 제공하여 개체 간의 결합을 줄입니다. 개체를 만드는 동안 컨테이너는 개체에 필요한 모든 종속성을 삽입합니다. 해당 종속성을 만들지 않은 경우 컨테이너는 먼저 해당 종속성을 만들고 확인합니다.

종속성 주입 컨테이너를 사용하는 경우 다음과 같은 몇 가지 이점이 있습니다.

  • 컨테이너를 사용하면 클래스가 종속성을 찾고 수명을 관리할 필요가 없습니다.
  • 컨테이너를 사용하면 클래스에 영향을 주지 않고 구현된 종속성을 매핑할 수 있습니다.
  • 컨테이너는 종속성을 모의로 만들 수 있도록 하여 테스트를 용이하게 합니다.
  • 컨테이너는 새 클래스를 앱에 쉽게 추가할 수 있도록 하여 유지 관리 가능성을 높입니다.

MVVM(Model-View-ViewModel) 패턴을 사용하는 .NET MAUI 앱의 컨텍스트에서 종속성 주입 컨테이너는 일반적으로 뷰 등록 및 확인, 뷰 모델 등록 및 확인, 서비스 등록 및 뷰 모델에 삽입하는 데 사용됩니다. MVVM 패턴 에 대한 자세한 내용은 MVVM(Model-View-ViewModel)을 참조하세요.

.NET에 사용할 수 있는 많은 종속성 주입 컨테이너가 있습니다. .NET MAUI는 앱에서 뷰, 뷰 모델 및 서비스 클래스의 인스턴스화를 관리하는 데 사용할 Microsoft.Extensions.DependencyInjection 수 있도록 기본적으로 지원됩니다. Microsoft.Extensions.DependencyInjection에서는 느슨하게 결합된 앱을 쉽게 빌드할 수 있으며, 형식 매핑 및 개체 인스턴스를 등록하고, 개체를 확인하고, 개체 수명을 관리하고, 종속 개체를 확인하는 개체의 생성자에 주입하는 메서드를 포함하여 종속성 주입 컨테이너에서 일반적으로 발견되는 모든 기능을 제공합니다. Microsoft.Extensions.DependencyInjection에 대한 자세한 내용은 .NET에서 종속성 주입을 참조하세요.

런타임 시 컨테이너는 요청된 개체에 대해 인스턴스화하기 위해 요청되는 종속성의 구현을 알고 있어야 합니다. 위의 예제에서는 개체를 ILoggingService 인스턴스화하기 전에 인터페이스와 ISettingsService 인터페이스를 MainPageViewModel 확인해야 합니다. 여기에는 컨테이너가 다음 작업을 수행하는 경우가 포함됩니다.

  • 인터페이스를 구현하는 개체를 인스턴스화하는 방법을 결정합니다. 이것을 등록이라고 합니다. 자세한 내용은 등록을 참조 하세요.
  • 필요한 인터페이스 및 MainPageViewModel 개체를 구현하는 개체를 인스턴스화합니다. 이것을 ‘확인’이라고 합니다. 자세한 내용은 해결을 참조 하세요.

결국 앱은 개체 사용을 MainPageViewModel 완료하고 가비지 수집에 사용할 수 있게 됩니다. 이 시점에서 가비지 수집기는 다른 클래스가 동일한 인스턴스를 공유하지 않는 경우 단기 인터페이스 구현을 삭제해야 합니다.

등록

종속성을 개체에 삽입하려면 먼저 종속성에 대한 형식을 컨테이너에 등록해야 합니다. 형식을 등록하려면 일반적으로 컨테이너에 구체적인 형식을 전달하거나 인터페이스를 구현하는 인터페이스와 구체적인 형식을 전달해야 합니다.

컨테이너에 형식 및 개체를 등록하는 두 가지 기본 방법이 있습니다.

  • 컨테이너에 형식 또는 매핑을 등록합니다. 이것을 임시 등록이라고 합니다. 필요한 경우 컨테이너는 지정된 형식의 인스턴스를 빌드합니다.
  • 컨테이너의 기존 개체를 싱글톤으로 등록합니다. 필요한 경우 컨테이너는 기존 개체에 대한 참조를 반환합니다.

주의

종속성 주입 컨테이너가 .NET MAUI 앱에 항상 적합한 것은 아닙니다. 종속성 주입은 더 작은 앱에 적절하거나 유용하지 않을 수 있는 추가 복잡성 및 요구 사항을 도입합니다. 클래스에 종속성이 없거나 다른 형식에 대한 종속성이 아닌 경우 컨테이너에 배치하는 것이 적합하지 않을 수 있습니다. 또한 클래스에 형식에 필수적인 단일 종속성 집합이 있고 변경되지 않는 경우 컨테이너에 배치하는 것은 의미가 없을 수 있습니다.

종속성 주입이 필요한 형식의 등록은 앱의 단일 메서드에서 수행해야 합니다. 이 메서드는 해당 클래스 간의 종속성을 인식할 수 있도록 앱의 수명 주기 초기에 호출되어야 합니다. 앱은 일반적으로 클래스의 CreateMauiApp 메서드에서 MauiProgram 이 작업을 수행해야 합니다. 클래스는 MauiProgram 메서드를 CreateMauiApp 호출하여 개체를 만듭니다 MauiAppBuilder . 개체에는 MauiAppBuilderServices 종속성 주입을 위한 뷰, 뷰 모델 및 서비스와 같은 형식을 등록할 수 있는 위치를 제공하는 형식 IServiceCollection의 속성이 있습니다.

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

        builder.Services.AddTransient<ILoggingService, LoggingService>();
        builder.Services.AddTransient<ISettingsService, SettingsService>();
        builder.Services.AddSingleton<MainPageViewModel>();
        builder.Services.AddSingleton<MainPage>();

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

        return builder.Build();
    }
}

속성에 Services 등록된 형식은 호출될 때 MauiAppBuilder.Build() 종속성 주입 컨테이너에 제공됩니다.

종속성을 등록할 때 종속성이 필요한 형식을 포함하여 모든 종속성을 등록해야 합니다. 따라서 종속성을 생성자 매개 변수로 사용하는 뷰 모델이 있는 경우 모든 종속성과 함께 뷰 모델을 등록해야 합니다. 마찬가지로 뷰 모델 종속성을 생성자 매개 변수로 사용하는 뷰가 있는 경우 뷰와 뷰 모델을 모든 종속성과 함께 등록해야 합니다.

종속성 주입 컨테이너는 뷰 모델 인스턴스를 만드는 데 적합합니다. 뷰 모델에 종속성이 있는 경우 필요한 서비스의 생성 및 삽입을 관리합니다. 뷰 모델 및 클래스의 메서드 MauiProgramCreateMauiApp 있을 수 있는 종속성을 등록해야 합니다.

종속성 수명

앱의 요구 사항에 따라 종속성을 다른 수명에 등록해야 할 수 있습니다. 다음 표에서는 종속성을 등록하는 데 사용할 수 있는 기본 메서드와 해당 등록 수명을 나열합니다.

메서드 설명
AddSingleton<T> 앱의 수명 동안 다시 기본 개체의 단일 인스턴스를 만듭니다.
AddTransient<T> 확인 중에 요청되면 개체의 새 인스턴스를 만듭니다. 임시 개체에는 미리 정의된 수명이 없지만 일반적으로 호스트의 수명을 따릅니다.
AddScoped<T> 호스트의 수명을 공유하는 개체의 인스턴스를 만듭니다. 호스트가 범위를 벗어나면 해당 종속성도 마찬가지입니다. 따라서 동일한 범위 내에서 동일한 종속성을 여러 번 확인하면 동일한 인스턴스가 생성되지만 다른 범위에서 동일한 종속성을 확인하면 다른 인스턴스가 생성됩니다.

참고 항목

개체가 뷰 또는 뷰 모델과 같은 인터페이스에서 상속되지 않는 경우 구체적인 형식만 , AddTransient<T>또는 AddScoped<T> 메서드에 AddSingleton<T>제공해야 합니다.

클래스는 MainPageViewModel 앱의 루트 근처에서 사용되며 항상 사용할 수 있어야 하므로 등록하는 것이 AddSingleton<T> 좋습니다. 다른 뷰 모델은 상황적으로 앱으로 이동하거나 나중에 사용할 수 있습니다. 항상 사용되지 않을 수 있는 형식이 있거나 메모리 또는 계산 집약적이거나 Just-In-Time 데이터가 필요한 경우 등록에 AddTransient<T> 더 적합한 후보가 될 수 있습니다.

종속성을 등록하는 또 다른 일반적인 방법은 , AddTransient<TService, TImplementation>또는 AddScoped<TService, TImplementation> 메서드를 AddSingleton<TService, TImplementation>사용하는 것입니다. 이러한 메서드는 인터페이스 정의와 구체적인 구현의 두 가지 형식을 사용합니다. 이 형식의 등록은 인터페이스를 기준으로 서비스를 구현하는 경우에 가장 적합합니다.

모든 형식이 등록되면 MauiAppBuilder.Build() 개체를 만들고 MauiApp 종속성 주입 컨테이너를 등록된 모든 형식으로 채우도록 호출해야 합니다.

Important

MauiAppBuilder.Build() 호출되면 종속성 주입 컨테이너에 등록된 형식은 변경할 수 없으며 더 이상 업데이트하거나 수정할 수 없습니다.

확장 메서드를 사용하여 종속성 등록

이 메서드는 MauiApp.CreateBuilder 종속성을 등록하는 데 사용할 수 있는 개체를 만듭니다 MauiAppBuilder . 앱이 많은 종속성을 등록해야 하는 경우 확장 메서드를 만들어 구성되고 기본 달성 가능한 등록 워크플로를 제공할 수 있습니다.

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
        => MauiApp.CreateBuilder()
            .UseMauiApp<App>()
            .RegisterServices()
            .RegisterViewModels()
            .RegisterViews()
            .Build();

    public static MauiAppBuilder RegisterServices(this MauiAppBuilder mauiAppBuilder)
    {
        mauiAppBuilder.Services.AddTransient<ILoggingService, LoggingService>();
        mauiAppBuilder.Services.AddTransient<ISettingsService, SettingsService>();

        // More services registered here.

        return mauiAppBuilder;        
    }

    public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
    {
        mauiAppBuilder.Services.AddSingleton<MainPageViewModel>();

        // More view-models registered here.

        return mauiAppBuilder;        
    }

    public static MauiAppBuilder RegisterViews(this MauiAppBuilder mauiAppBuilder)
    {
        mauiAppBuilder.Services.AddSingleton<MainPage>();

        // More views registered here.

        return mauiAppBuilder;        
    }
}

이 예제에서 세 가지 등록 확장 메서드는 인스턴스를 MauiAppBuilder 사용하여 속성에 Services 액세스하여 종속성을 등록합니다.

해결 방법

형식을 등록한 후에는 종속성으로 확인하거나 주입할 수 있습니다. 형식이 확인되고 컨테이너가 새 인스턴스를 만들어야 하는 경우 모든 종속성이 인스턴스에 주입됩니다.

일반적으로 형식이 확인되면 다음 세 가지 시나리오 중 하나가 발생합니다.

  1. 형식이 등록되지 않은 경우 컨테이너는 예외를 throw합니다.
  2. 형식이 싱글톤으로 등록된 경우 컨테이너는 싱글톤 인스턴스를 반환합니다. 이번에 해당 형식을 처음 호출하는 경우라면 컨테이너가 필요한 경우 해당 형식을 만들고 이에 대한 참조를 유지 관리합니다.
  3. 형식이 임시로 등록된 경우 컨테이너는 새 인스턴스를 반환하고 해당 인스턴스에 대한 참조를 유지하지 않습니다.

.NET MAUI는 자동명시적 종속성 확인을 지원합니다. 자동 종속성 확인은 컨테이너에서 종속성을 명시적으로 요청하지 않고 생성자 삽입을 사용합니다. 명시적 종속성 확인은 컨테이너에서 종속성을 명시적으로 요청하여 요청 시 발생합니다.

자동 종속성 확인

종속성 주입 컨테이너에 종속성을 사용하는 형식과 종속성의 형식을 등록한 경우 .NET MAUI Shell을 사용하는 앱에서 자동 종속성 확인이 발생합니다.

셸 기반 탐색 중에 .NET MAUI는 페이지 등록을 찾습니다. 이 페이지가 있으면 해당 페이지를 만들고 해당 생성자에 종속성을 삽입합니다.

public MainPage(MainPageViewModel viewModel)
{
    InitializeComponent();

    BindingContext = viewModel;
}

이 예제 MainPage 에서 생성자는 삽입된 인스턴스를 MainPageViewModel 받습니다. 따라서 인스턴스 ILoggingServiceMainPageViewModel 삽입된 인스턴스와 ISettingsService 인스턴스는 다음과 같습니다.

public class MainPageViewModel
{
    readonly ILoggingService _loggingService;
    readonly ISettingsService _settingsService;

    public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
    {
        _loggingService = loggingService;
        _settingsService = settingsService;
    }
}

또한 셸 기반 앱에서 .NET MAUI는 메서드에 등록된 Routing.RegisterRoute 세부 페이지에 종속성을 삽입합니다.

명시적 종속성 확인

셸 기반 앱은 형식이 매개 변수가 없는 생성자만 노출하는 경우 생성자 삽입을 사용할 수 없습니다. 또는 앱이 Shell을 사용하지 않는 경우 명시적 종속성 확인을 사용해야 합니다.

종속성 주입 컨테이너는 다음과 같은 형식IServiceProviderElement 속성을 통해 Handler.MauiContext.Service 명시적으로 액세스할 수 있습니다.

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        HandlerChanged += OnHandlerChanged;
    }

    void OnHandlerChanged(object sender, EventArgs e)
    {
        BindingContext = Handler.MauiContext.Services.GetService<MainPageViewModel>();
    }
}

이 방법은 또는 생성자 Element외부에서 종속성을 Element확인해야 하는 경우에 유용할 수 있습니다. 이 예제에서는 이벤트 처리기에서 종속성 주입 컨테이너에 HandlerChanged 액세스하면 페이지에 대한 처리기가 설정되었으므로 속성이 Handler 설정되지 않습니다 null.

Warning

사용자의 Element 속성이 Handlernull수 있으므로 이 상황을 고려해야 할 수 있습니다. 자세한 내용은 처리기 수명 주기를 참조 하세요.

뷰 모델에서 종속성 주입 컨테이너는 다음의 속성을 Application.Current.MainPage통해 Handler.MauiContext.Service 명시적으로 액세스할 수 있습니다.

public class MainPageViewModel
{
    readonly ILoggingService _loggingService;
    readonly ISettingsService _settingsService;

    public MainPageViewModel()
    {
        _loggingService = Application.Current.MainPage.Handler.MauiContext.Services.GetService<ILoggingService>();
        _settingsService = Application.Current.MainPage.Handler.MauiContext.Services.GetService<ISettingsService>();
    }
}

이 방법의 단점은 뷰 모델이 이제 형식에 종속된다는 것입니다 Application . 그러나 이 단점은 뷰 모델 생성자에 인수를 IServiceProvider 전달하여 제거할 수 있습니다. 종 IServiceProvider 속성 주입 컨테이너에 등록하지 않고도 자동 종속성 확인을 통해 확인됩니다. 이 방법을 사용하면 형식이 종속성 주입 컨테이너에 등록된 경우 형식 및 해당 IServiceProvider 종속성을 자동으로 확인할 수 있습니다. 그런 IServiceProvider 다음 명시적 종속성 확인에 사용할 수 있습니다.

public class MainPageViewModel
{
    readonly ILoggingService _loggingService;
    readonly ISettingsService _settingsService;

    public MainPageViewModel(IServiceProvider serviceProvider)
    {
        _loggingService = serviceProvider.GetService<ILoggingService>();
        _settingsService = serviceProvider.GetService<ISettingsService>();
    }
}

또한 다음 네이 IServiceProvider 티브 속성을 통해 인스턴스에 액세스할 수 있습니다.

  • 안 드 로이드- MauiApplication.Current.Services
  • iOS 및 Mac Catalyst - MauiUIApplicationDelegate.Current.Services
  • Windows - MauiWinUIApplication.Current.Services

XAML 리소스의 제한 사항

일반적인 시나리오는 종속성 주입 컨테이너에 페이지를 등록하고 자동 종속성 확인을 사용하여 생성자에 삽입 App 하고 속성 값 MainPage 으로 설정하는 것입니다.

public App(MyFirstAppPage page)
{
    InitializeComponent();
    MainPage = page;
}

그러나 이 시나리오에서는 리소스 사전 XamlParseException 의 XAML App 에서 선언된 액세스하려는 StaticResource 경우 MyFirstAppPage 유사한 Position {row}:{column}. StaticResource not found for key {key}메시지와 함께 throw됩니다. 이는 애플리케이션 수준 XAML 리소스가 초기화되기 전에 생성자 삽입을 통해 확인된 페이지가 만들어졌기 때문에 발생합니다.

이 문제에 대한 해결 방법은 클래스에 App 삽입한 IServiceProvider 다음 이를 사용하여 클래스 내 App 의 페이지를 해결하는 것입니다.

public App(IServiceProvider serviceProvider)
{
    InitializeComponent();
    MainPage = serviceProvider.GetService<MyFirstAppPage>();
}

이 방법은 페이지를 확인하기 전에 XAML 개체 트리를 만들고 초기화하도록 강제합니다.