Add support for dependency injection

This commit is contained in:
Oliver Booth 2022-11-30 20:54:22 +00:00
parent 55eaa4b510
commit 5b6bbac689
No known key found for this signature in database
GPG Key ID: 32A00B35503AF634
5 changed files with 82 additions and 2 deletions

View File

@ -1,4 +1,5 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using VpSharp.ClientExtensions;
using VpSharp.Commands.Attributes;
using VpSharp.Entities;
@ -30,6 +31,7 @@ public sealed class CommandsExtension : VirtualParadiseClientExtension
: base(client)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_configuration.Services ??= client.Services;
}
/// <summary>
@ -37,6 +39,7 @@ public sealed class CommandsExtension : VirtualParadiseClientExtension
/// </summary>
/// <param name="assembly">The assembly whose command modules to register.</param>
/// <exception cref="ArgumentException">
/// A module in the specified assembly does not have a public constructor, or has more than one public constructor.
/// </exception>
/// <exception cref="TypeInitializationException">A command module could not be instantiated.</exception>
/// <exception cref="InvalidOperationException">
@ -48,6 +51,10 @@ public sealed class CommandsExtension : VirtualParadiseClientExtension
/// A command in the specified assembly has <see cref="RemainderAttribute" /> on a parameter that is not the last in
/// the parameter list.
/// </para>
/// -or-
/// <para>
/// A module is expecting services but no <see cref="IServiceProvider" /> was registered.
/// </para>
/// </exception>
public void RegisterCommands(Assembly assembly)
{
@ -66,7 +73,11 @@ public sealed class CommandsExtension : VirtualParadiseClientExtension
/// Registers the commands defined in the specified type.
/// </summary>
/// <exception cref="ArgumentException">
/// <typeparamref name="T" /> refers to a type that does not inherit <see cref="CommandModule" />.
/// <para><typeparamref name="T" /> refers to a type that does not inherit <see cref="CommandModule" />.</para>
/// -or-
/// <para>
/// <typeparamref name="T" /> does not have a public constructor, or has more than one public constructor.
/// </para>
/// </exception>
/// <exception cref="TypeInitializationException"><typeparamref name="T" /> could not be instantiated.</exception>
/// <exception cref="InvalidOperationException">
@ -78,6 +89,10 @@ public sealed class CommandsExtension : VirtualParadiseClientExtension
/// A command in the specified module has <see cref="RemainderAttribute" /> on a parameter that is not the last in the
/// parameter list.
/// </para>
/// -or-
/// <para>
/// The module is expecting services but no <see cref="IServiceProvider" /> was registered.
/// </para>
/// </exception>
public void RegisterCommands<T>() where T : CommandModule
{
@ -93,6 +108,10 @@ public sealed class CommandsExtension : VirtualParadiseClientExtension
/// <para><paramref name="moduleType" /> refers to an <c>abstract</c> type.</para>
/// -or-
/// <para><paramref name="moduleType" /> refers to a type that does not inherit <see cref="CommandModule" />.</para>
/// -or-
/// <para>
/// <paramref name="moduleType" /> does not have a public constructor, or has more than one public constructor.
/// </para>
/// </exception>
/// <exception cref="TypeInitializationException"><paramref name="moduleType" /> could not be instantiated.</exception>
/// <exception cref="InvalidOperationException">
@ -104,6 +123,10 @@ public sealed class CommandsExtension : VirtualParadiseClientExtension
/// A command in the specified module has <see cref="RemainderAttribute" /> on a parameter that is not the last in the
/// parameter list.
/// </para>
/// -or-
/// <para>
/// The module is expecting services but no <see cref="IServiceProvider" /> was registered.
/// </para>
/// </exception>
public void RegisterCommands(Type moduleType)
{
@ -119,7 +142,29 @@ public sealed class CommandsExtension : VirtualParadiseClientExtension
throw new ArgumentException($"Module type is not a subclass of {typeof(CommandModule)}");
}
if (Activator.CreateInstance(moduleType) is not CommandModule module)
ConstructorInfo[] constructors = moduleType.GetTypeInfo().DeclaredConstructors.Where(c => c.IsPublic).ToArray();
if (constructors.Length != 0)
{
throw new ArgumentException(
$"Constructor for {moduleType} is not public, or {moduleType} has more than one public constructor.");
}
ConstructorInfo constructor = constructors[0];
ParameterInfo[] parameters = constructor.GetParameters();
IServiceProvider? serviceProvider = _configuration.Services;
if (parameters.Length != 0 && serviceProvider is null)
{
throw new InvalidOperationException("No ServiceProvider has been registered!");
}
var args = new object[parameters.Length];
for (var index = 0; index < args.Length; index++)
{
args[index] = serviceProvider!.GetRequiredService(parameters[index].ParameterType);
}
if (Activator.CreateInstance(moduleType, args) is not CommandModule module)
{
throw new TypeInitializationException(moduleType.FullName, null);
}

View File

@ -5,9 +5,30 @@ namespace VpSharp.Commands;
/// </summary>
public sealed class CommandsExtensionConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="CommandsExtensionConfiguration" /> class.
/// </summary>
/// <param name="configuration">The configuration to copy.</param>
public CommandsExtensionConfiguration(CommandsExtensionConfiguration? configuration = null)
{
if (configuration is null)
{
return;
}
Prefixes = configuration.Prefixes;
Services = configuration.Services;
}
/// <summary>
/// Gets or sets the prefixes to be use for commands.
/// </summary>
/// <value>The command prefixes, as an array of <see cref="string" /> values.</value>
public IReadOnlyList<string> Prefixes { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the service provider.
/// </summary>
/// <value>The service provider.</value>
public IServiceProvider? Services { get; set; }
}

View File

@ -57,6 +57,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0"/>
<PackageReference Include="System.Drawing.Common" Version="7.0.0"/>
<PackageReference Include="X10D" Version="3.2.0-nightly.136"/>
<PackageReference Include="ZString" Version="2.5.0"/>

View File

@ -46,6 +46,7 @@ public sealed partial class VirtualParadiseClient : IDisposable
/// <value>The configuration for this client.</value>
public VirtualParadiseClient(VirtualParadiseConfiguration configuration)
{
Services = configuration.Services;
_configuration = new VirtualParadiseConfiguration(configuration);
Initialize();
}
@ -84,6 +85,12 @@ public sealed partial class VirtualParadiseClient : IDisposable
get => CurrentAvatar?.Location.World;
}
/// <summary>
/// Gets the service provider.
/// </summary>
/// <value>The service provider.</value>
public IServiceProvider? Services { get; }
/// <summary>
/// Gets a read-only view of the cached worlds.
/// </summary>

View File

@ -61,6 +61,12 @@ public sealed class VirtualParadiseConfiguration
/// </summary>
/// <value>The login password.</value>
public string Password { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the service provider.
/// </summary>
/// <value>The service provider.</value>
public IServiceProvider? Services { get; set; }
/// <summary>
/// Gets or sets the login username.