.NET MAUI - MVVM Dependency Injection 2/2 (AutoScan with Scrutor)

2022. 8. 19. 00:00MAUI

반응형

dotnet dependency injection 시스템을 사용하면 아주 간단하게 의존성을 주입하고 활용할 수 있다. 

그러나 이런 시스템을 사용해도 각각의 class 들을 등록해야 하는 과정은 필요하다. 

class 들이 얼마 없는 간단한 program (app) 이라면 괜찮겠지만 엄청나게 많은 class 들의 존재하는

아주 복잡하고 큰 program 이라면 class 를 등록해야 하는 과정 자체가 하나의 일이 된다. 

이런 과정을 도와주는 library 로 Scrutor 이 있다.  

Scrutor 는 autoscan 을 통해 필요한 class 들을 자동으로 등록할 수 있다.  

Scrutor 를 MAUI 에서 활용하는 방법에 대해 알아보자 

 

Visual Studio 에서 개발자 명령 프롬프트로 이동 한후.   {프로젝트명}.csproj 파일이 있는 폴더로 이동한다.

아래 명령 실행

dotnet add package Scrutor 

 

InjectableServices 폴더 추가 --> Injectables.cs 파일 추가

namespace MauiApp1.InjectableServices;
public interface IInjectableService { }
public interface ITransientService : IInjectableService { }
public interface IScopedService : IInjectableService { }
public interface ISingletonService : IInjectableService { }

빈 interface 를 만들어 LifeTime 별로 구분 할 수 있도록 3가지로 분류한다. 

 

MauiProgram.cs

using MauiApp1.InjectableServices;

namespace MauiApp1;

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<IHelloWorldClass, HelloWorldClass>();
        //builder.Services.AddTransient<IncrementCounterViewModel>();
        //builder.Services.AddTransient<IncrementCounterPage>();

        builder.Services.Scan(scan => scan
                            .FromAssemblyOf<ITransientService>()
                            .AddClasses(classes => classes.AssignableTo<ITransientService>())
                            .AsSelfWithInterfaces()
                            .WithTransientLifetime()
                            .AddClasses(classes => classes.AssignableTo<IScopedService>())
                            .AsSelfWithInterfaces()
                            .WithScopedLifetime()
                            .AddClasses(classes => classes.AssignableTo<ISingletonService>())
                            .AsSelfWithInterfaces()
                            .WithSingletonLifetime()
                            );

        return builder.Build();

    }
}

ITransientService 를 정의한 Assembly를 자동으로 찾는다. 

ITransientService 를 상속 받은 모든 type 은 Transient LifeTime 을 갖는다. 

또한 Dependency Injection 시스템에 자동으로 등록 되어 어디서든 사용가능 하게된다. 

// 이형태와
services.AddTransient<ITransientService, TestService>();
// 이형태가 둘다 등록된다.
services.AddTransient<TestService>();

IScopedService 은 Scoped LifeTime,  ISingletonService 는 Sigleton LifeTime 을 갖는다. 

 

이제 이전 강좌에서 사용했던 IncrementCounterPage 및 ViewModel 을 Scrutor 를 이용하여 수정해보자

 

HelloWorldClass.cs

using MauiApp1.InjectableServices;

namespace MauiApp1.MVVM.AutoScan;
public interface IHelloWorldClass:ITransientService
{
    string Execute();
}
public class HelloWorldClass : IHelloWorldClass
{
    public string Execute() => $"{DateTime.Now} Hello World!";
}

IHelloWorldClass 가 ITransientService 를  상속받도록 수정 한다.

 

IncrementCounterPage.xaml.cs

using MauiApp1.InjectableServices;

namespace MauiApp1.MVVM.AutoScan;

public partial class IncrementCounterPage : ContentPage, ITransientService
{
	public IncrementCounterPage(IncrementCounterViewModel incrementCounterViewModel)
	{
		InitializeComponent();
		BindingContext = incrementCounterViewModel;
	}
}

IncrementCounterPage가 ITransientService 를  상속받도록 수정 한다.

 

IncrementCounterViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MauiApp1.InjectableServices;

namespace MauiApp1.MVVM.AutoScan;
public partial class IncrementCounterViewModel : ObservableObject, ITransientService
{
    public IncrementCounterViewModel(IHelloWorldClass helloWorld)
    {
        _helloWorld = helloWorld;
    }

    [ObservableProperty]
    private int _counter;

    [ObservableProperty]
    private string _message;

    private readonly IHelloWorldClass _helloWorld;

    [RelayCommand]
    private void IncrementCounter() => Counter++;

    [RelayCommand]
    private void ChangeMessage()
    {
        Message = _helloWorld.Execute();
    }
}

IncrementCounterViewModel이 ITransientService 를  상속받도록 수정 한다.

 

또한 위에 Scrutor.Scan 한 부분에서 Assembly 를 자동으로 scan 하여 처리 하는 방법도 있다. 

dll 이 저장되는 위치를 모두 scan 한다. 

scan 한 dll 을 모두 load 한다. 

load 된 dll 에서 ITransientService,IScopedService,ISingletonService 를 찾아 등록한다. 

 

이럴경우 아래와 같이 사용한다. 

Helper/AssemblyHelper.cs

using System.Reflection;
using System.Runtime.Loader;

namespace MauiApp1.Helper;
public class AssemblyHelper
{
    public static List<Assembly> GetAllAssemblies
        (SearchOption searchOption = SearchOption.TopDirectoryOnly)
    {
        List<Assembly> assemblies = new List<Assembly>();
        var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
        var assemblyFiles = Directory.GetFiles(baseDirectory
        , "*.dll"
        , searchOption);

        var path = Directory.GetFiles(baseDirectory);
        foreach (string assemblyPath in assemblyFiles)
        {
            try
            {
                var assembly = AssemblyLoadContext
                .Default
                .LoadFromAssemblyPath(assemblyPath);
                if (assemblies.Find(a => a == assembly) != null)
                    continue;
                assemblies.Add(assembly);
            }
            catch (Exception ex)
            {
                //Debug.WriteLine(ex.ToString());
            }
        }
        return assemblies;
    }

}

MauiProgram.cs

using MauiApp1.InjectableServices;

namespace MauiApp1;

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.Scan(scan => scan
                            //.FromAssemblyOf<ITransientService>()
                            .FromAssemblies(Helper.AssemblyHelper.GetAllAssemblies(SearchOption.AllDirectories))
                            .AddClasses(classes => classes.AssignableTo<ITransientService>())
                            .AsSelfWithInterfaces()
                            .WithTransientLifetime()
                            .AddClasses(classes => classes.AssignableTo<IScopedService>())
                            .AsSelfWithInterfaces()
                            .WithScopedLifetime()
                            .AddClasses(classes => classes.AssignableTo<ISingletonService>())
                            .AsSelfWithInterfaces()
                            .WithSingletonLifetime()
                            );

        return builder.Build();

    }
}

FromAssemblies (AssemblyHelper.GetAllAssemblies(모든폴더확인)) 을 통해 처리 한다. 

그러나 문제는 dll 을 load 하는 초기에 좀 오랜 시간이 걸리므로 이부분 참고하자

 

 

 

관련영상

https://youtu.be/QSROnIpcQEE

 

 

 

 

 

반응형