diff --git a/DisCatSharp.Hosting/DiscordHostedService.cs b/DisCatSharp.Hosting/DiscordHostedService.cs
index c3f1bd91d..dc3de879c 100644
--- a/DisCatSharp.Hosting/DiscordHostedService.cs
+++ b/DisCatSharp.Hosting/DiscordHostedService.cs
@@ -1,201 +1,209 @@
// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Hosting
{
///
/// Simple implementation for to work as a
///
public abstract class DiscordHostedService : BackgroundService, IDiscordHostedService
{
///
public DiscordClient Client { get; private set; }
protected readonly ILogger Logger;
protected readonly IHostApplicationLifetime ApplicationLifetime;
protected readonly IConfiguration Configuration;
protected readonly IServiceProvider ServiceProvider;
private readonly string _botSection;
#pragma warning disable 8618
///
/// Initializes a new instance of the class.
///
/// The config.
/// The logger.
/// The provider.
/// Current hosting environment. This will be used for shutting down the application on error
/// Name within the configuration which contains the config info for our bot. Default is DisCatSharp
protected DiscordHostedService(IConfiguration config, ILogger logger, IServiceProvider provider, IHostApplicationLifetime applicationLifetime, string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DefaultRootLib)
{
this.Logger = logger;
this.ApplicationLifetime = applicationLifetime;
this.Configuration = config;
- this.ServiceProvider = provider;
this._botSection = configBotSection;
+ this.ServiceProvider = provider;
+ this.Initialize();
}
#pragma warning restore 8618
///
/// When the bot fails to start, this method will be invoked. (Default behavior is to shutdown)
///
/// The exception/reason the bot couldn't start
protected virtual void OnInitializationError(Exception ex)
{
this.ApplicationLifetime.StopApplication();
}
///
- /// Automatically search for and configure
+ /// Dynamically loads extensions by using , and
+ ///
///
- ///
- ///
- /// Name within the configuration which contains the config info for our bot
- private void Initialize()
+ protected virtual void InitializeExtensions()
{
var typeMap = this.Configuration.FindImplementedExtensions(this._botSection);
this.Logger.LogDebug($"Found the following config types: {string.Join("\n\t", typeMap.Keys)}");
- try
- {
- this.Client = this.Configuration.BuildClient(this._botSection);
- this.Client.Services = this.ServiceProvider;
- }
- catch (Exception ex)
- {
- this.Logger.LogError($"Was unable to build {nameof(DiscordClient)} for {this.GetType().Name}");
- this.OnInitializationError(ex);
- }
-
- foreach (var typePair in typeMap)
+ foreach (var typePair in typeMap)
try
{
/*
If section is null --> utilize the default constructor
This means the extension was explicitly added in the 'Using' array,
but user did not wish to override any value(s) in the extension's config
*/
var configInstance = typePair.Value.Section.HasValue
? typePair.Value.Section.Value.ExtractConfig(() =>
ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType))
: ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType);
/*
Explanation for bindings
Internal Constructors --> NonPublic
Public Constructors --> Public
Constructors --> Instance
*/
var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
var ctors = typePair.Value.ImplementationType.GetConstructors(flags);
var instance = ctors.Any(x => x.GetParameters().Length == 1 && x.GetParameters().First().ParameterType == typePair.Value.ConfigType)
? Activator.CreateInstance(typePair.Value.ImplementationType, flags, null,
new[] { configInstance }, null)
: Activator.CreateInstance(typePair.Value.ImplementationType, true);
/*
Certain extensions do not require a configuration argument
Those who do -- pass config instance in,
Those who don't -- simply instantiate
ActivatorUtilities requires a public constructor, anything with internal breaks
*/
if (instance == null)
{
this.Logger.LogError($"Unable to instantiate '{typePair.Value.ImplementationType.Name}'");
continue;
}
// Add an easy reference to our extensions for later use
this.Client.AddExtension((BaseExtension)instance);
}
catch (Exception ex)
{
this.Logger.LogError($"Unable to register '{typePair.Value.ImplementationType.Name}': \n\t{ex.Message}");
this.OnInitializationError(ex);
}
}
+ ///
+ /// Automatically search for and configure
+ ///
+ ///
+ ///
+ /// Name within the configuration which contains the config info for our bot
+ private void Initialize()
+ {
+ try
+ {
+ this.Client = this.Configuration.BuildClient(this._botSection);
+ this.Client.Services = this.ServiceProvider;
+ }
+ catch (Exception ex)
+ {
+ this.Logger.LogError($"Was unable to build {nameof(DiscordClient)} for {this.GetType().Name}");
+ this.OnInitializationError(ex);
+ }
+ }
+
///
/// Executes the bot.
///
/// The stopping token.
/// A Task.
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
if (this.Client == null)
throw new NullReferenceException("Discord Client cannot be null");
await this.PreConnect();
await this.Client.ConnectAsync();
- this.Initialize();
+ this.InitializeExtensions();
await this.PostConnect();
}
catch (Exception ex)
{
/*
* Anything before DOTNET 6 will
* fail silently despite throwing an exception in this method
* So to overcome this obstacle we need to log what happened and manually exit
*/
this.Logger.LogError($"Was unable to start {this.GetType().Name} Bot as a hosted service.");
this.OnInitializationError(ex);
}
// Wait indefinitely -- but use stopping token so we can properly cancel if needed
await Task.Delay(-1, stoppingToken);
}
///
/// Runs just prior to the bot connecting
///
protected virtual Task PreConnect() => Task.CompletedTask;
///
/// Runs immediately after the bot connects
///
protected virtual Task PostConnect() => Task.CompletedTask;
}
}