はじめに
前回クラスをDIコンテナ化しDependency Injectionをする方法を機能書きました。
しかし、アプリが大きくなっていくと、前回の方法でBuilder.Services.Add~を何回も書きなぐるとするとどうなるでしょう。
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Services.AddSingleton<ISomeProductService,SomeProductService>();
builder.Services.AddSingleton<MainPage>();
builder.Services.AddSingleton<ProductsPage>();
builder.Services.AddSingleton<ProductDetailPage>();
builder.Services.AddSingleton<AboutPage>();
builder.Services.AddSingleton<AboutPageViewModel>();
builder.Services.AddSingleton<ProductDetailPageViewModel>();
builder.Services.AddSingleton<ProductsPageViewModel>();
builder.Services.AddSingleton<HomePageViewModel>();
このようにProgram.csファイルが肥大化してしまいます。。。
そうならないようにリフレクションを使用して、すべてのクラスとページをスキャン、収集し、それらをすべて自動でDIコンテナに登録します。
ISingletonDependency
まず、コンテナのシングルトンスコープに登録させるインターフェイスを作成します。
中身は何もない目印のようなものです。
これを、シングルトンサービスとして登録したいサービス、およびクラスにこのインターフェイスを継承させます。
public interface ISingletonDependency
{
}
下のような形でSingletonとしたいものに、ISingletonDependencyを継承させます。
//ViewModelの場合
public partial class LoginPageViewModel : ISingletonDependency//←ISingletonDependencyを継承させる
{
//View及びPageの場合
public partial class LoginPage : ContentPage,ISingletonDependency//←ほかのクラスを継承しているものであっても,をつけて継承
//Serviceの場合
public class SampleDataService:ISingletonDependency//←同様に継承
{
MauiAppBuilderExtensions
次にMAUIBuilderに追加の動作を行うための拡張メソッドを追加します。
この中でDI化をしたいクラスを呼び出しカプセル化をします。
そして、このメソッドのみをMAUIProgram.csクラスに呼び出すことで、DIコンテナとしたいクラスをすべて呼び出すことができるようにします。
次のようなMauiAppBuilderExtensionsという静的クラスを作成します。
public static class MauiAppBuilderExtensions
{
public static void ConfigureServices(this MauiAppBuilder builder)
{
RegisterSingletonServices(builder);//←下のメソッドを呼び出し、ConfigureServicesの中でカプセル化している
}
}
次に、ConfigureServicesクラスの中にRegisterSingletonServicesという静的メソッドを作成します。
このメソッドは全てのアセンブリをスキャンし、目印としたISingletonDependencyを継承するすべてのクラスを収集し、各クラスをDIコンテナに登録します。
private static void RegisterSingletonServices(MauiAppBuilder builder)
{
//↓ISingletonDependencyを継承しているクラスをすべて集めてリスト化している(LINQ)(Interfaceクラス及びAbstractクラスは除く)
var services = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes())
.Where(x => typeof(ISingletonDependency).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract).ToList();
//↓そのリストすべてにbuilder.Service.AddSingletonを追加している
foreach (var service in services)
builder.Services.AddSingleton(service);
}
続けて書くとこのようになります。
//上の2つを合成
public static class MauiAppBuilderExtensions
{
public static void ConfigureServices(this MauiAppBuilder builder)
{
RegisterSingletonServices(builder);
}
private static void RegisterSingletonServices(MauiAppBuilder builder)
{
var services = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes())
.Where(x => typeof(ISingletonDependency).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract).ToList();
foreach (var service in services)
builder.Services.AddSingleton(service);
}
}
MauiProgram.csに実装
あとはMAuiProgram.csにbuilder.ConfigureServicesメソッドを追加するだけで、指定されたクラス、サービスがDI化されます。
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.ConfigureServices();//←たったこれだけです。
return builder.Build();
}
かなりすっきりしました。
AddTransientの場合
AddTransientを登録したい場合は同様にITransientDependencyインターフェースを作成し、AddTransientさせたいクラスやサービスに継承させます。
public interface ITransientDependency
{
}
そして、MauiAppBuilderExtensionsクラスに以下のようにTransient用のメソッドを追加させます。
public static class MauiAppBuilderExtensions
{
public static void ConfigureServices(this MauiAppBuilder builder)
{
RegisterSingletonServices(builder);
//↓Transient用のメソッドをConfigureServices()に追加
RegisterTransientServices(builder);
}
private static void RegisterSingletonServices(MauiAppBuilder builder)
{
var services = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes())
.Where(x => typeof(ISingletonDependency).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract).ToList();
foreach (var service in services)
builder.Services.AddSingleton(service);
}
//Transient用のメソッド(Singletonの部分がTransientに変わっただけ)
private static void RegisterTransientServices(MauiAppBuilder builder)
{
var services = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes())
.Where(x => typeof(ITransientDependency).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract).ToList();
foreach (var service in services)
builder.Services.AddTransient(service);
}
}
これで、すでにMauiProgram.csでConfigureServicesが実行されるため、AddTransientも同時に登録されるようになります。
さいごに
この機能を実装することでよりきれいにDIコンテナが作られます。
しかし、リフレクションによってすべてのアセンブリをスキャンしているため、何らかのペナルティ(起動が遅くなる、使用メモリが増える等)はあるかもしれません。