Skip to content

Commit eb3245a

Browse files
authored
Merge pull request #2 from Hermanest/ms-di-migration
Migrated to Microsoft DI Container.
2 parents 57a5d9c + 02a69de commit eb3245a

20 files changed

Lines changed: 385 additions & 172 deletions

.dockerignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
**/.dockerignore
2+
**/.env
3+
**/.git
4+
**/.gitignore
5+
**/.project
6+
**/.settings
7+
**/.toolstarget
8+
**/.vs
9+
**/.vscode
10+
**/.idea
11+
**/*.user
12+
**/*.editorconfig
13+
**/*.*proj.user
14+
**/*.dbmdl
15+
**/*.jfm
16+
**/azds.yaml
17+
**/bin
18+
**/charts
19+
**/docker-compose*
20+
**/Dockerfile*
21+
**/node_modules
22+
**/npm-debug.log
23+
**/obj
24+
**/secrets.dev.yaml
25+
**/values.dev.yaml
26+
LICENSE
27+
README.md

.gitmodules

Lines changed: 0 additions & 3 deletions
This file was deleted.

Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
2+
3+
WORKDIR /app
4+
COPY SabaBot/SabaBot.csproj ./SabaBot/
5+
6+
WORKDIR /app/SabaBot
7+
RUN dotnet restore
8+
9+
COPY SabaBot/ .
10+
RUN dotnet publish "SabaBot.csproj" --configuration Release --runtime linux-x64 -o /app/publish
11+
12+
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS final
13+
14+
WORKDIR /app
15+
COPY --from=build /app/publish/ .
16+
17+
ENTRYPOINT ["dotnet", "SabaBot.dll"]

SabaBot/Application.cs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using Newtonsoft.Json;
2-
using Zenject;
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Newtonsoft.Json;
4+
using SabaBot.Database;
35

46
namespace SabaBot;
57

@@ -14,16 +16,22 @@ internal static class Application {
1416
"your-llama-prompt-here"
1517
);
1618

17-
private static readonly DiContainer applicationContainer = new();
1819
private static readonly TaskCompletionSource taskCompletionSource = new();
1920

2021
private static async Task Main(string[] args) {
21-
//loading config
22+
// Loading config
2223
var path = args.Length > 0 ? args[0] : DefaultConfigPath;
2324
if (!TryLoadConfig(path, out var config)) return;
24-
//installing
25-
applicationContainer.Bind<ApplicationConfig>().FromInstance(config!).AsSingle();
26-
applicationContainer.Install<ApplicationInstaller>();
25+
26+
// Installing
27+
var services = new ServiceCollection();
28+
services.AddSingleton(config!);
29+
var provider = ApplicationInstaller.Install(services);
30+
31+
// Migrating the DB if needed
32+
var db = provider.GetRequiredService<ApplicationContext>();
33+
await db.Database.MigrateAsync();
34+
2735
await taskCompletionSource.Task;
2836
}
2937

@@ -42,6 +50,32 @@ private static bool TryLoadConfig(string path, out ApplicationConfig? config) {
4250
try {
4351
var json = File.ReadAllText(path);
4452
config = JsonConvert.DeserializeObject<ApplicationConfig>(json);
53+
54+
if (config == null) {
55+
throw new Exception("Deserialization failure");
56+
}
57+
58+
// Note that Path.Combine takes into account rooted paths, so:
59+
// /home + /config.json = /config.json
60+
// /home + config.json = /home/config.json
61+
62+
var configDir = Path.GetDirectoryName(path) ?? string.Empty;
63+
var dbAddress = config.DbAddress;
64+
65+
{
66+
var dbFile = Path.Combine(configDir, dbAddress);
67+
68+
// DB can be either a path or a network address, so we check if the file exists first
69+
if (File.Exists(dbFile)) {
70+
dbAddress = dbFile;
71+
}
72+
}
73+
74+
config = config with {
75+
DbAddress = dbAddress,
76+
LocalizationFile = Path.Combine(configDir, config.LocalizationFile)
77+
};
78+
4579
return true;
4680
} catch (Exception ex) {
4781
Console.WriteLine($"Unable to load config:\n{ex}");

SabaBot/ApplicationInstaller.cs

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,67 @@
11
using Discord;
22
using Discord.Interactions;
3-
using Discord.Rest;
43
using Discord.WebSocket;
4+
using Microsoft.Extensions.DependencyInjection;
55
using Microsoft.Extensions.Logging;
66
using SabaBot.Database;
7-
using Zenject;
87

98
namespace SabaBot;
109

11-
internal class ApplicationInstaller : Installer {
12-
public override void InstallBindings() {
13-
//adapters
14-
Container.BindInterfacesAndSelfTo<ZenjectServiceScopeFactory>().AsSingle().Lazy();
15-
Container.BindInterfacesAndSelfTo<ZenjectServiceProvider>().AsSingle().Lazy();
16-
//base dependencies
10+
internal static class ApplicationInstaller {
11+
public static ServiceProvider Install(IServiceCollection services) {
12+
// Base dependencies
1713
var config = new DiscordSocketConfig {
1814
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent,
1915
MessageCacheSize = 1000
2016
};
17+
2118
var socketClient = new DiscordSocketClient(config);
22-
Container.Bind<DiscordSocketClient>().FromInstance(socketClient).AsSingle().Lazy();
23-
Container.Bind<DiscordRestClient>().FromInstance(socketClient.Rest).AsSingle().Lazy();
24-
Container.Bind<InteractionService>().AsSingle().Lazy();
25-
Container.Bind<ApplicationContext>().AsSingle().Lazy();
26-
//logging
27-
InstallLogger();
28-
//services
29-
InstallServices();
30-
//installing apis
31-
InstallAPI();
32-
//starting
33-
Container.Bind<ModuleLoader>().AsSingle().NonLazy();
34-
Container.Bind<Bootstrapper>().AsSingle().NonLazy();
35-
//a little workaround to start non-lazy bindings
36-
Bootstrap();
37-
}
19+
services.AddSingleton(socketClient);
20+
services.AddSingleton(socketClient.Rest);
3821

39-
private void InstallAPI() {
40-
Container.Bind<HttpClient>().AsSingle();
41-
Container.BindInterfacesTo<BeatLeaderAPI>().AsSingle();
42-
}
22+
var interactionService = new InteractionService(socketClient);
23+
services.AddSingleton(interactionService);
24+
services.AddDbContext<ApplicationContext>();
25+
services.AddSingleton<ILocalization, Localization>();
26+
27+
// Logging
28+
services.AddLoggingEnhanced();
29+
30+
// Services
31+
services.AddSingleton<InteractionManagementService>();
32+
services.AddService<DiscordLoggerService>();
33+
services.AddService<MessageService>();
34+
services.AddService<ReactionChampService>();
35+
services.AddService<LeaveNotifService>();
4336

44-
private void InstallServices() {
45-
Container.Bind(typeof(ISystemService), typeof(ILocalization)).To<Localization>().AsSingle();
46-
Container.BindInterfacesTo<DiscordLoggerService>().AsSingle();
47-
Container.BindInterfacesTo<InteractionManagementService>().AsSingle();
48-
//Container.BindInterfacesTo<OpenAIChatBot>().AsSingle();
49-
Container.BindInterfacesTo<MessageRewindChatBot>().AsSingle();
50-
Container.BindInterfacesTo<MessageService>().AsSingle();
51-
Container.BindInterfacesAndSelfTo<ReactionChampService>().AsSingle();
52-
Container.BindInterfacesAndSelfTo<LeaveNotifService>().AsSingle();
37+
// Installing apis
38+
services.AddSingleton<HttpClient>();
39+
services.AddSingleton<IBeatLeaderAPI, BeatLeaderAPI>();
40+
services.AddSingleton<IChatBot, MessageRewindChatBot>();
41+
42+
// Starting
43+
services.AddSingleton<ModuleLoader>();
44+
services.AddSingleton<Bootstrapper>();
45+
46+
// A little workaround to start non-lazy bindings
47+
var provider = services.BuildServiceProvider();
48+
provider.GetRequiredService<ModuleLoader>();
49+
provider.GetRequiredService<Bootstrapper>().Start();
50+
51+
return provider;
5352
}
54-
55-
private void InstallLogger() {
53+
54+
private static void AddLoggingEnhanced(this IServiceCollection services) {
5655
var factory = LoggerFactory.Create(x => x.AddConsole().SetMinimumLevel(LogLevel.Information));
5756
var logger = factory.CreateLogger("Bot");
58-
Container.Bind<ILoggerFactory>().FromInstance(factory).AsSingle();
59-
Container.Bind<ILogger>().FromInstance(logger).AsTransient();
57+
58+
services.AddSingleton(factory);
59+
services.AddSingleton(logger);
60+
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
6061
}
6162

62-
private void Bootstrap() {
63-
Container.Resolve<ModuleLoader>();
64-
Container.Resolve<Bootstrapper>();
63+
private static void AddService<T>(this IServiceCollection services) where T : class, IService {
64+
services.AddSingleton<T>();
65+
services.AddSingleton<IService, T>();
6566
}
6667
}

SabaBot/Core/Bootstrapper.cs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
using Discord;
22
using Discord.WebSocket;
3-
using SabaBot.Database;
4-
using Zenject;
53

64
namespace SabaBot;
75

8-
internal class Bootstrapper {
9-
[Inject]
10-
public async void Start(
11-
DiscordSocketClient client,
12-
ApplicationConfig config,
13-
ApplicationContext context,
14-
[InjectOptional] IEnumerable<ISystemService>? systemServices,
15-
[InjectOptional] IEnumerable<IService>? services
16-
) {
6+
internal class Bootstrapper(
7+
DiscordSocketClient client,
8+
ApplicationConfig config,
9+
IEnumerable<ISystemService>? systemServices = null,
10+
IEnumerable<IService>? services = null
11+
) {
12+
public async void Start() {
1713
//bootstrapping system services
1814
if (systemServices != null) {
1915
try {
@@ -33,7 +29,7 @@ [InjectOptional] IEnumerable<IService>? services
3329
}
3430
}
3531

36-
private void BootstrapServices(IEnumerable<IService> services) {
32+
private static void BootstrapServices(IEnumerable<IService> services) {
3733
foreach (var service in services) {
3834
service.Start();
3935
}

SabaBot/Core/Database/ApplicationContext.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
using JetBrains.Annotations;
22
using Microsoft.EntityFrameworkCore;
33
using Microsoft.Extensions.Logging;
4-
using Zenject;
54

65
namespace SabaBot.Database;
76

87
public class ApplicationContext : DbContext {
8+
public ApplicationContext(ApplicationConfig config, ILoggerFactory? loggerFactory = null) {
9+
_config = config;
10+
_loggerFactory = loggerFactory;
11+
}
12+
913
[UsedImplicitly]
1014
public ApplicationContext() {
1115
// do not remove, this constructor is used for migrations
1216
_migration = true;
1317
}
1418

15-
[Inject]
16-
public ApplicationContext(ApplicationConfig config, [InjectOptional] ILoggerFactory? loggerFactory) {
17-
_config = config;
18-
_loggerFactory = loggerFactory;
19-
// ReSharper disable once VirtualMemberCallInConstructor
20-
Database.Migrate();
21-
}
22-
2319
public required DbSet<GuildSettings> Guilds { get; set; }
2420

2521
private readonly ApplicationConfig? _config;
@@ -31,7 +27,7 @@ public async Task Modify(ulong guildId, Action<GuildSettings> action) {
3127
action(settings);
3228
await SaveChangesAsync();
3329
}
34-
30+
3531
public async Task<GuildSettings> EnsureSettingsCreated(ulong guildId) {
3632
var guild = await Guilds.FindAsync(guildId);
3733
if (guild == null) {
@@ -51,6 +47,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
5147
);
5248
modelBuilder.Entity<GuildSettings>()
5349
.OwnsOne(x => x.RewardSettings);
50+
modelBuilder.Entity<GuildSettings>()
51+
.OwnsOne(x => x.LeaveNotifSettings);
5452
modelBuilder.Entity<GuildSettings>()
5553
.OwnsOne(x => x.ReactionChampSettings)
5654
.OwnsMany(

SabaBot/Core/Models/AppInteractionModuleBase.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
using SabaBot.Database;
2-
using Zenject;
1+
using Discord.Interactions;
2+
using JetBrains.Annotations;
3+
using SabaBot.Database;
34

45
namespace SabaBot;
56

6-
public abstract class AppInteractionModuleBase : DiInteractionModuleBase {
7-
[Inject] private readonly ILocalization _localization = null!;
8-
[Inject] protected readonly ApplicationContext DbContext = null!;
7+
public abstract class AppInteractionModuleBase : InteractionModuleBase {
8+
[UsedImplicitly]
9+
public ILocalization Localization { get; init; } = null!;
10+
11+
[UsedImplicitly]
12+
public ApplicationContext DbContext { get; init; } = null!;
913

1014
protected async Task<string> GetLocalizedKey(string key) {
1115
var guildId = Context.Guild?.Id;
12-
var locale = _localization.DefaultLocale;
16+
var locale = Localization.DefaultLocale;
1317
if (guildId != null) {
1418
var settings = await GetSettingsAsync();
1519
locale = settings.Locale;
1620
}
17-
return _localization[locale, key];
21+
return Localization[locale, key];
1822
}
1923

2024
protected async Task ModifyAsync(string? text = null) {

SabaBot/Core/Models/DiInteractionModuleBase.cs

Lines changed: 0 additions & 15 deletions
This file was deleted.

SabaBot/Core/Models/ZenjectServiceProvider.cs

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)