diff --git a/GenHTTP.slnx b/GenHTTP.slnx
index d22941ea4..a1d3af5d7 100644
--- a/GenHTTP.slnx
+++ b/GenHTTP.slnx
@@ -1,19 +1,15 @@
-
-
-
-
@@ -23,6 +19,7 @@
+
@@ -46,29 +43,23 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/Modules/Controllers/Provider/ControllerBuilder.cs b/Modules/Controllers/Provider/ControllerBuilder.cs
index 7ede317e8..471afdcd9 100644
--- a/Modules/Controllers/Provider/ControllerBuilder.cs
+++ b/Modules/Controllers/Provider/ControllerBuilder.cs
@@ -1,6 +1,9 @@
using System.Diagnostics.CodeAnalysis;
+
using GenHTTP.Api.Content;
using GenHTTP.Api.Infrastructure;
+using GenHTTP.Api.Protocol;
+
using GenHTTP.Modules.Conversion;
using GenHTTP.Modules.Conversion.Formatters;
using GenHTTP.Modules.Conversion.Serializers;
@@ -9,16 +12,18 @@
namespace GenHTTP.Modules.Controllers.Provider;
-public sealed class ControllerBuilder : IHandlerBuilder
+public sealed class ControllerBuilder : IHandlerBuilder, IRegistryBuilder
{
private readonly List _Concerns = [];
+ private Type? _Type;
+
+ private Func>? _InstanceProvider;
+
private IBuilder? _Formatters;
private IBuilder? _Injection;
- private object? _Instance;
-
private IBuilder? _Serializers;
#region Functionality
@@ -42,14 +47,25 @@ public ControllerBuilder Formatters(IBuilder registry)
}
public ControllerBuilder Type<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new()
+ => Instance(new T());
+
+ public ControllerBuilder Type([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type)
{
- _Instance = new T();
+ _Type = type;
return this;
}
public ControllerBuilder Instance(object instance)
{
- _Instance = instance;
+ _Type = instance.GetType();
+ _InstanceProvider = (_) => ValueTask.FromResult(instance);
+
+ return this;
+ }
+
+ public ControllerBuilder InstanceProvider(Func> provider)
+ {
+ _InstanceProvider = provider;
return this;
}
@@ -67,11 +83,13 @@ public IHandler Build()
var formatters = (_Formatters ?? Formatting.Default()).Build();
- var instance = _Instance ?? throw new BuilderMissingPropertyException("Instance or Type");
+ var instanceProvider = _InstanceProvider ?? throw new BuilderMissingPropertyException("Instance provider has not been set");
+
+ var type = _Type ?? throw new BuilderMissingPropertyException("Type has not been set");
var extensions = new MethodRegistry(serializers, injectors, formatters);
- return Concerns.Chain(_Concerns, new ControllerHandler(instance, extensions));
+ return Concerns.Chain(_Concerns, new ControllerHandler(type, instanceProvider, extensions));
}
#endregion
diff --git a/Modules/Controllers/Provider/ControllerHandler.cs b/Modules/Controllers/Provider/ControllerHandler.cs
index b463f2be2..5b100b26f 100644
--- a/Modules/Controllers/Provider/ControllerHandler.cs
+++ b/Modules/Controllers/Provider/ControllerHandler.cs
@@ -13,59 +13,74 @@ public sealed partial class ControllerHandler : IHandler, IServiceMethodProvider
{
private static readonly Regex HyphenMatcher = CreateHyphenMatcher();
+ private MethodCollection? _Methods;
+
#region Get-/Setters
- public MethodCollection Methods { get; }
+ private Type Type { get; }
- private ResponseProvider ResponseProvider { get; }
+ private Func> InstanceProvider { get; }
private MethodRegistry Registry { get; }
- private object Instance { get; }
-
#endregion
#region Initialization
- public ControllerHandler(object instance, MethodRegistry registry)
+ public ControllerHandler(Type type, Func> instanceProvider, MethodRegistry registry)
{
+ Type = type;
+ InstanceProvider = instanceProvider;
Registry = registry;
+ }
+
+ #endregion
- Instance = instance;
+ #region Functionality
- ResponseProvider = new ResponseProvider(registry);
+ public ValueTask PrepareAsync() => ValueTask.CompletedTask;
- Methods = new MethodCollection(AnalyzeMethods(instance.GetType(), registry));
- }
+ public async ValueTask HandleAsync(IRequest request) => await (await GetMethodsAsync(request)).HandleAsync(request);
- private IEnumerable AnalyzeMethods(Type type, MethodRegistry registry)
+ public async ValueTask GetMethodsAsync(IRequest request)
{
- foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
+ if (_Methods != null) return _Methods;
+
+ var found = new List();
+
+
+ foreach (var method in Type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
var annotation = method.GetCustomAttribute(true) ?? new MethodAttribute();
var arguments = FindPathArguments(method);
- var operation = CreateOperation(method, arguments);
+ var operation = CreateOperation(request, method, arguments, Registry);
- yield return new MethodHandler(operation, Instance, annotation, registry);
+ found.Add(new MethodHandler(operation, InstanceProvider, annotation, Registry));
}
+
+ var result = new MethodCollection(found);
+
+ await result.PrepareAsync();
+
+ return _Methods = result;
}
- private Operation CreateOperation(MethodInfo method, List arguments)
+ private static Operation CreateOperation(IRequest request, MethodInfo method, List arguments, MethodRegistry registry)
{
var pathArguments = string.Join('/', arguments.Select(a => $":{a}"));
if (method.Name == "Index")
{
- return OperationBuilder.Create(pathArguments.Length > 0 ? $"/{pathArguments}/" : null, method, Registry, true);
+ return OperationBuilder.Create(request, pathArguments.Length > 0 ? $"/{pathArguments}/" : null, method, registry, true);
}
var name = HypenCase(method.Name);
var path = $"/{name}";
- return OperationBuilder.Create(pathArguments.Length > 0 ? $"{path}/{pathArguments}/" : $"{path}/", method, Registry, true);
+ return OperationBuilder.Create(request, pathArguments.Length > 0 ? $"{path}/{pathArguments}/" : $"{path}/", method, registry, true);
}
private static List FindPathArguments(MethodInfo method)
@@ -93,12 +108,4 @@ private static List FindPathArguments(MethodInfo method)
#endregion
- #region Functionality
-
- public ValueTask PrepareAsync() => Methods.PrepareAsync();
-
- public ValueTask HandleAsync(IRequest request) => Methods.HandleAsync(request);
-
- #endregion
-
}
diff --git a/Modules/DependencyInjection/Basics/ConcernIntegration.cs b/Modules/DependencyInjection/Basics/ConcernIntegration.cs
new file mode 100644
index 000000000..1c96ff8f4
--- /dev/null
+++ b/Modules/DependencyInjection/Basics/ConcernIntegration.cs
@@ -0,0 +1,38 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+using GenHTTP.Modules.DependencyInjection.Infrastructure;
+
+namespace GenHTTP.Modules.DependencyInjection.Basics;
+
+internal class ConcernIntegration : IConcern where T : class, IDependentConcern
+{
+
+ #region Getters/Setters
+
+ public IHandler Content { get; }
+
+ #endregion
+
+ #region Initialization
+
+ internal ConcernIntegration(IHandler content)
+ {
+ Content = content;
+ }
+
+ #endregion
+
+ #region Functionality
+
+ public ValueTask PrepareAsync() => Content.PrepareAsync();
+
+ public async ValueTask HandleAsync(IRequest request)
+ {
+ var instance = await InstanceProvider.ProvideAsync(request);
+
+ return await instance.HandleAsync(Content, request);
+ }
+
+ #endregion
+
+}
diff --git a/Modules/DependencyInjection/Basics/ConcernIntegrationBuilder.cs b/Modules/DependencyInjection/Basics/ConcernIntegrationBuilder.cs
new file mode 100644
index 000000000..2947cbbcb
--- /dev/null
+++ b/Modules/DependencyInjection/Basics/ConcernIntegrationBuilder.cs
@@ -0,0 +1,10 @@
+using GenHTTP.Api.Content;
+
+namespace GenHTTP.Modules.DependencyInjection.Basics;
+
+internal class ConcernIntegrationBuilder : IConcernBuilder where T : class, IDependentConcern
+{
+
+ public IConcern Build(IHandler content) => new ConcernIntegration(content);
+
+}
diff --git a/Modules/DependencyInjection/Basics/HandlerIntegration.cs b/Modules/DependencyInjection/Basics/HandlerIntegration.cs
new file mode 100644
index 000000000..df3e87104
--- /dev/null
+++ b/Modules/DependencyInjection/Basics/HandlerIntegration.cs
@@ -0,0 +1,20 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+
+using GenHTTP.Modules.DependencyInjection.Infrastructure;
+
+namespace GenHTTP.Modules.DependencyInjection.Basics;
+
+internal class HandlerIntegration : IHandler where T: class, IDependentHandler
+{
+
+ public ValueTask PrepareAsync() => ValueTask.CompletedTask;
+
+ public async ValueTask HandleAsync(IRequest request)
+ {
+ var instance = await InstanceProvider.ProvideAsync(request);
+
+ return await instance.HandleAsync(request);
+ }
+
+}
diff --git a/Modules/DependencyInjection/Basics/HandlerIntegrationBuilder.cs b/Modules/DependencyInjection/Basics/HandlerIntegrationBuilder.cs
new file mode 100644
index 000000000..172402db8
--- /dev/null
+++ b/Modules/DependencyInjection/Basics/HandlerIntegrationBuilder.cs
@@ -0,0 +1,10 @@
+using GenHTTP.Api.Content;
+
+namespace GenHTTP.Modules.DependencyInjection.Basics;
+
+internal class HandlerIntegrationBuilder : IHandlerBuilder where T : class, IDependentHandler
+{
+
+ public IHandler Build() => new HandlerIntegration();
+
+}
diff --git a/Modules/DependencyInjection/Dependent.cs b/Modules/DependencyInjection/Dependent.cs
new file mode 100644
index 000000000..fac38d366
--- /dev/null
+++ b/Modules/DependencyInjection/Dependent.cs
@@ -0,0 +1,25 @@
+using GenHTTP.Api.Content;
+
+using GenHTTP.Modules.DependencyInjection.Basics;
+
+namespace GenHTTP.Modules.DependencyInjection;
+
+///
+/// Allows to enable dependency injection for concerns and handlers.
+///
+public static class Dependent
+{
+
+ ///
+ /// Allows the given class to be used as a regular concern with dependency injection enabled.
+ ///
+ /// The class implementing the concern
+ public static IConcernBuilder Concern() where T : class, IDependentConcern => new ConcernIntegrationBuilder();
+
+ ///
+ /// Allows the given class to be used as a regular handler with dependency injection enabled.
+ ///
+ /// The class implementing the handler
+ public static IHandlerBuilder Handler() where T : class, IDependentHandler => new HandlerIntegrationBuilder();
+
+}
diff --git a/Modules/DependencyInjection/DependentController.cs b/Modules/DependencyInjection/DependentController.cs
new file mode 100644
index 000000000..5dd0c1800
--- /dev/null
+++ b/Modules/DependencyInjection/DependentController.cs
@@ -0,0 +1,39 @@
+using System.Diagnostics.CodeAnalysis;
+
+using GenHTTP.Api.Infrastructure;
+
+using GenHTTP.Modules.Controllers.Provider;
+using GenHTTP.Modules.Conversion.Formatters;
+using GenHTTP.Modules.Conversion.Serializers;
+using GenHTTP.Modules.DependencyInjection.Infrastructure;
+using GenHTTP.Modules.Layouting.Provider;
+using GenHTTP.Modules.Reflection.Injectors;
+
+namespace GenHTTP.Modules.DependencyInjection;
+
+public static class DependentController
+{
+
+ ///
+ /// Adds the given controller to the layout with dependency injection enabled.
+ ///
+ /// The layout to add the controller to
+ /// The path to register the controller at
+ /// Additional parameter injections to be used by the controller
+ /// Additional serializers to be used by the controller
+ /// Additional formatters to be used by the controller
+ /// The class implementing the controller
+ /// The given layout with the specified controller attached
+ public static LayoutBuilder AddDependentController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder layout, string path, InjectionRegistryBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : class
+ {
+ var builder = new ControllerBuilder();
+
+ builder.Type(typeof(T));
+ builder.InstanceProvider(async (r) => await InstanceProvider.ProvideAsync(r));
+
+ builder.Configure(injectors, serializers, formatters);
+
+ return layout.Add(path, builder);
+ }
+
+}
diff --git a/Modules/DependencyInjection/DependentInline.cs b/Modules/DependencyInjection/DependentInline.cs
new file mode 100644
index 000000000..07282a48f
--- /dev/null
+++ b/Modules/DependencyInjection/DependentInline.cs
@@ -0,0 +1,25 @@
+using GenHTTP.Api.Infrastructure;
+
+using GenHTTP.Modules.Conversion.Formatters;
+using GenHTTP.Modules.Conversion.Serializers;
+using GenHTTP.Modules.DependencyInjection.Infrastructure;
+using GenHTTP.Modules.Functional;
+using GenHTTP.Modules.Functional.Provider;
+using GenHTTP.Modules.Reflection.Injectors;
+
+namespace GenHTTP.Modules.DependencyInjection;
+
+public static class DependentInline
+{
+
+ ///
+ /// Creates a new line handler with dependency injection enabled on the declared methods.
+ ///
+ /// Additional parameter injections to be used by the inline handler
+ /// Additional serializers to be used by the inline handler
+ /// Additional formatters to be used by the inline handler
+ /// The newly created inline handler
+ public static InlineBuilder Create(InjectionRegistryBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null)
+ => Inline.Create().Configure(injectors, serializers, formatters);
+
+}
diff --git a/Modules/DependencyInjection/DependentWebservice.cs b/Modules/DependencyInjection/DependentWebservice.cs
new file mode 100644
index 000000000..94e058a5d
--- /dev/null
+++ b/Modules/DependencyInjection/DependentWebservice.cs
@@ -0,0 +1,39 @@
+using System.Diagnostics.CodeAnalysis;
+
+using GenHTTP.Api.Infrastructure;
+
+using GenHTTP.Modules.Conversion.Formatters;
+using GenHTTP.Modules.Conversion.Serializers;
+using GenHTTP.Modules.DependencyInjection.Infrastructure;
+using GenHTTP.Modules.Layouting.Provider;
+using GenHTTP.Modules.Reflection.Injectors;
+using GenHTTP.Modules.Webservices.Provider;
+
+namespace GenHTTP.Modules.DependencyInjection;
+
+public static class DependentWebservice
+{
+
+ ///
+ /// Adds the given webservice to the layout with dependency injection enabled.
+ ///
+ /// The layout to add the webservice to
+ /// The path to register the webservice at
+ /// Additional parameter injections to be used by the webservice
+ /// Additional serializers to be used by the webservice
+ /// Additional formatters to be used by the webservice
+ /// The class implementing the webservice
+ /// The given layout with the specified webservice attached
+ public static LayoutBuilder AddDependentService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder layout, string path, InjectionRegistryBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : class
+ {
+ var builder = new ServiceResourceBuilder();
+
+ builder.Type(typeof(T));
+ builder.InstanceProvider(async (r) => await InstanceProvider.ProvideAsync(r));
+
+ builder.Configure(injectors, serializers, formatters);
+
+ return layout.Add(path, builder);
+ }
+
+}
diff --git a/Modules/DependencyInjection/GenHTTP.Modules.DependencyInjection.csproj b/Modules/DependencyInjection/GenHTTP.Modules.DependencyInjection.csproj
new file mode 100644
index 000000000..34d4aa418
--- /dev/null
+++ b/Modules/DependencyInjection/GenHTTP.Modules.DependencyInjection.csproj
@@ -0,0 +1,34 @@
+
+
+
+
+ Enables dependency injection for all framework services.
+ HTTP Webserver C# Module DI Dependency Injection
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Modules/DependencyInjection/IDependentConcern.cs b/Modules/DependencyInjection/IDependentConcern.cs
new file mode 100644
index 000000000..14b2bcc10
--- /dev/null
+++ b/Modules/DependencyInjection/IDependentConcern.cs
@@ -0,0 +1,30 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+
+namespace GenHTTP.Modules.DependencyInjection;
+
+///
+/// Represents a dependency injection enabled concern that can be used to
+/// serve requests.
+///
+///
+/// In contrast to regular concerns there is no concern or content preparation as
+/// the lifecycle of the handler is managed by the dependency injection
+/// container.
+///
+public interface IDependentConcern
+{
+
+ ///
+ /// Handles the given request and returns a response, if applicable.
+ ///
+ ///
+ /// Not returning a response causes the server to respond with a not found
+ /// response code.
+ ///
+ /// The request to be handled
+ /// The (initialized) content to be served by the concern
+ /// The response to be sent to the requesting client
+ ValueTask HandleAsync(IHandler content, IRequest request);
+
+}
diff --git a/Modules/DependencyInjection/IDependentHandler.cs b/Modules/DependencyInjection/IDependentHandler.cs
new file mode 100644
index 000000000..fec2ccbac
--- /dev/null
+++ b/Modules/DependencyInjection/IDependentHandler.cs
@@ -0,0 +1,28 @@
+using GenHTTP.Api.Protocol;
+
+namespace GenHTTP.Modules.DependencyInjection;
+
+///
+/// Represents a dependency injection enabled handler that can be used to
+/// serve requests.
+///
+///
+/// In contrast to regular handlers there is no handler preparation as
+/// the lifecycle of the handler is managed by the dependency injection
+/// container.
+///
+public interface IDependentHandler
+{
+
+ ///
+ /// Handles the given request and returns a response, if applicable.
+ ///
+ ///
+ /// Not returning a response causes the server to respond with a not found
+ /// response code.
+ ///
+ /// The request to be handled
+ /// The response to be sent to the requesting client
+ ValueTask HandleAsync(IRequest request);
+
+}
diff --git a/Modules/DependencyInjection/Infrastructure/DependencyInjector.cs b/Modules/DependencyInjection/Infrastructure/DependencyInjector.cs
new file mode 100644
index 000000000..4c80bd97d
--- /dev/null
+++ b/Modules/DependencyInjection/Infrastructure/DependencyInjector.cs
@@ -0,0 +1,34 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+
+using GenHTTP.Modules.Reflection.Injectors;
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace GenHTTP.Modules.DependencyInjection.Infrastructure;
+
+public class DependencyInjector : IParameterInjector
+{
+
+ public bool Supports(IRequest request, Type type)
+ {
+ var scope = request.GetServiceScope();
+
+ var supportService = scope.ServiceProvider.GetService();
+
+ if (supportService != null)
+ {
+ return supportService.IsService(type);
+ }
+
+ return (scope.ServiceProvider.GetService(type) != null);
+ }
+
+ public object? GetValue(IHandler handler, IRequest request, Type targetType)
+ {
+ var scope = request.GetServiceScope();
+
+ return scope.ServiceProvider.GetService(targetType);
+ }
+
+}
diff --git a/Modules/DependencyInjection/Infrastructure/FrameworkConfiguration.cs b/Modules/DependencyInjection/Infrastructure/FrameworkConfiguration.cs
new file mode 100644
index 000000000..bbaafb1de
--- /dev/null
+++ b/Modules/DependencyInjection/Infrastructure/FrameworkConfiguration.cs
@@ -0,0 +1,33 @@
+using GenHTTP.Api.Infrastructure;
+using GenHTTP.Modules.Conversion.Formatters;
+using GenHTTP.Modules.Conversion.Serializers;
+using GenHTTP.Modules.Reflection;
+using GenHTTP.Modules.Reflection.Injectors;
+
+namespace GenHTTP.Modules.DependencyInjection.Infrastructure;
+
+internal static class FrameworkConfiguration
+{
+
+ internal static T Configure(this T builder, InjectionRegistryBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : IRegistryBuilder
+ {
+ var injectionOverlay = injectors ?? Injection.Default();
+
+ injectionOverlay.Add(new DependencyInjector());
+
+ builder.Injectors(injectionOverlay);
+
+ if (serializers != null)
+ {
+ builder.Serializers(serializers);
+ }
+
+ if (formatters != null)
+ {
+ builder.Formatters(formatters);
+ }
+
+ return builder;
+ }
+
+}
diff --git a/Modules/DependencyInjection/Infrastructure/InjectionConcern.cs b/Modules/DependencyInjection/Infrastructure/InjectionConcern.cs
new file mode 100644
index 000000000..93f16f635
--- /dev/null
+++ b/Modules/DependencyInjection/Infrastructure/InjectionConcern.cs
@@ -0,0 +1,43 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace GenHTTP.Modules.DependencyInjection.Infrastructure;
+
+internal class InjectionConcern : IConcern
+{
+
+ #region Get-/Setters
+
+ public IHandler Content { get; }
+
+ internal IServiceProvider Services { get; }
+
+ #endregion
+
+ #region Initialization
+
+ internal InjectionConcern(IHandler content, IServiceProvider services)
+ {
+ Content = content;
+ Services = services;
+ }
+
+ #endregion
+
+ #region Functionality
+
+ public ValueTask PrepareAsync() => Content.PrepareAsync();
+
+ public ValueTask HandleAsync(IRequest request)
+ {
+ using var scope = Services.CreateScope();
+
+ request.Configure(Services, scope);
+
+ return Content.HandleAsync(request);
+ }
+
+ #endregion
+
+}
diff --git a/Modules/DependencyInjection/Infrastructure/InjectionConcernBuilder.cs b/Modules/DependencyInjection/Infrastructure/InjectionConcernBuilder.cs
new file mode 100644
index 000000000..4530ed3f5
--- /dev/null
+++ b/Modules/DependencyInjection/Infrastructure/InjectionConcernBuilder.cs
@@ -0,0 +1,20 @@
+using GenHTTP.Api.Content;
+
+namespace GenHTTP.Modules.DependencyInjection.Infrastructure;
+
+internal class InjectionConcernBuilder : IConcernBuilder
+{
+ private readonly IServiceProvider _Services;
+
+ #region Initialization
+
+ internal InjectionConcernBuilder(IServiceProvider services)
+ {
+ _Services = services;
+ }
+
+ #endregion
+
+ public IConcern Build(IHandler content) => new InjectionConcern(content, _Services);
+
+}
diff --git a/Modules/DependencyInjection/Infrastructure/InstanceProvider.cs b/Modules/DependencyInjection/Infrastructure/InstanceProvider.cs
new file mode 100644
index 000000000..105816e51
--- /dev/null
+++ b/Modules/DependencyInjection/Infrastructure/InstanceProvider.cs
@@ -0,0 +1,24 @@
+using GenHTTP.Api.Protocol;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace GenHTTP.Modules.DependencyInjection.Infrastructure;
+
+public static class InstanceProvider
+{
+
+ public static ValueTask ProvideAsync(IRequest request) where T : class
+ {
+ var scope = request.GetServiceScope();
+
+ var instance = scope.ServiceProvider.GetService(typeof(T))
+ ?? ActivatorUtilities.CreateInstance(scope.ServiceProvider);
+
+ if (instance == null)
+ {
+ throw new InvalidOperationException($"Unable to resolve or construct instance of type '{typeof(T)}'");
+ }
+
+ return ValueTask.FromResult((T)instance);
+ }
+
+}
diff --git a/Modules/DependencyInjection/Integration.cs b/Modules/DependencyInjection/Integration.cs
new file mode 100644
index 000000000..d46bad06f
--- /dev/null
+++ b/Modules/DependencyInjection/Integration.cs
@@ -0,0 +1,62 @@
+using GenHTTP.Api.Infrastructure;
+using GenHTTP.Api.Protocol;
+
+using GenHTTP.Modules.DependencyInjection.Infrastructure;
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace GenHTTP.Modules.DependencyInjection;
+
+public static class Integration
+{
+ private const string ProviderVar = "__DI_SERVICE_PROVIDER";
+
+ private const string ScopeVar = "__DI_SERVICE_SCOPE";
+
+ ///
+ /// Uses the given provider to injected dependencies within the server instance.
+ ///
+ /// The host to add dependency injection to
+ /// The service collection to be used for injection
+ /// The updated host
+ public static IServerHost AddDependencyInjection(this IServerHost host, IServiceProvider services) => host.Add(new InjectionConcernBuilder(services));
+
+ internal static void Configure(this IRequest request, IServiceProvider provider, IServiceScope scope)
+ {
+ request.Properties[ProviderVar] = provider;
+ request.Properties[ScopeVar] = scope;
+ }
+
+ ///
+ /// Retrieves the service provider used to resolve dependencies from the given request.
+ ///
+ /// The request to obtain the service provider from
+ /// The service provider retrieved from the request
+ /// Thrown if dependency injection is not enabled on the host
+ public static IServiceProvider GetServiceProvider(this IRequest request)
+ {
+ if (!request.Properties.TryGet(ScopeVar, out IServiceProvider? provider))
+ {
+ throw new InvalidOperationException("Unable to retrieve service provider from the request. Ensure dependency injection has been configured.");
+ }
+
+ return provider!;
+ }
+
+ ///
+ /// Retrieves the service scope used to resolve dependencies from the given request.
+ ///
+ /// The request to obtain the service scope from
+ /// The service scope retrieved from the request
+ /// Thrown if dependency injection is not enabled on the host
+ public static IServiceScope GetServiceScope(this IRequest request)
+ {
+ if (!request.Properties.TryGet(ScopeVar, out IServiceScope? scope))
+ {
+ throw new InvalidOperationException("Unable to retrieve service scope from the request. Ensure dependency injection has been configured.");
+ }
+
+ return scope!;
+ }
+
+}
diff --git a/Modules/Functional/Provider/InlineBuilder.cs b/Modules/Functional/Provider/InlineBuilder.cs
index 7f1d1e8be..ed553d652 100644
--- a/Modules/Functional/Provider/InlineBuilder.cs
+++ b/Modules/Functional/Provider/InlineBuilder.cs
@@ -10,7 +10,7 @@
namespace GenHTTP.Modules.Functional.Provider;
-public class InlineBuilder : IHandlerBuilder
+public class InlineBuilder : IHandlerBuilder, IRegistryBuilder
{
private static readonly HashSet AllMethods = [..Enum.GetValues().Select(FlexibleRequestMethod.Get)];
@@ -174,7 +174,7 @@ public IHandler Build()
var extensions = new MethodRegistry(serializers, injectors, formatters);
- return Concerns.Chain(_Concerns, new InlineHandler( _Functions, extensions));
+ return Concerns.Chain(_Concerns, new InlineHandler(_Functions, extensions));
}
#endregion
diff --git a/Modules/Functional/Provider/InlineHandler.cs b/Modules/Functional/Provider/InlineHandler.cs
index eb0d99c7c..d6518c7d8 100644
--- a/Modules/Functional/Provider/InlineHandler.cs
+++ b/Modules/Functional/Provider/InlineHandler.cs
@@ -8,12 +8,13 @@ namespace GenHTTP.Modules.Functional.Provider;
public class InlineHandler : IHandler, IServiceMethodProvider
{
+ private MethodCollection? _Methods;
#region Get-/Setters
- public MethodCollection Methods { get; }
+ private List Functions { get; }
- private ResponseProvider ResponseProvider { get; }
+ private MethodRegistry Registry { get; }
#endregion
@@ -21,32 +22,43 @@ public class InlineHandler : IHandler, IServiceMethodProvider
public InlineHandler(List functions, MethodRegistry registry)
{
- ResponseProvider = new ResponseProvider(registry);
-
- Methods = new MethodCollection(AnalyzeMethods(functions, registry));
+ Functions = functions;
+ Registry = registry;
}
- private static IEnumerable AnalyzeMethods(List functions, MethodRegistry registry)
+ #endregion
+
+ #region Functionality
+
+ public ValueTask PrepareAsync() => ValueTask.CompletedTask;
+
+ public async ValueTask HandleAsync(IRequest request) => await (await GetMethodsAsync(request)).HandleAsync(request);
+
+ public async ValueTask GetMethodsAsync(IRequest request)
{
- foreach (var function in functions)
+ if (_Methods != null) return _Methods;
+
+ var found = new List();
+
+ foreach (var function in Functions)
{
var method = function.Delegate.Method;
- var operation = OperationBuilder.Create(function.Path, method, registry);
+ var operation = OperationBuilder.Create(request, function.Path, method, Registry);
var target = function.Delegate.Target ?? throw new InvalidOperationException("Delegate target must not be null");
- yield return new MethodHandler(operation, target, function.Configuration, registry);
- }
- }
+ var instanceProvider = (IRequest _) => ValueTask.FromResult(target);
- #endregion
+ found.Add(new MethodHandler(operation, instanceProvider, function.Configuration, Registry));
+ }
- #region Functionality
+ var result = new MethodCollection(found);
- public ValueTask PrepareAsync() => Methods.PrepareAsync();
+ await result.PrepareAsync();
- public ValueTask HandleAsync(IRequest request) => Methods.HandleAsync(request);
+ return _Methods = result;
+ }
#endregion
diff --git a/Modules/OpenApi/Discovery/ApiDiscoveryRegistry.cs b/Modules/OpenApi/Discovery/ApiDiscoveryRegistry.cs
index 1ab1a71c7..08260884a 100644
--- a/Modules/OpenApi/Discovery/ApiDiscoveryRegistry.cs
+++ b/Modules/OpenApi/Discovery/ApiDiscoveryRegistry.cs
@@ -1,4 +1,6 @@
using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+
using NSwag;
namespace GenHTTP.Modules.OpenApi.Discovery;
@@ -27,17 +29,18 @@ public ApiDiscoveryRegistry(List explorers)
/// Iterates through the registered explorers to find a responsible one to analyze
/// the given handler instance.
///
+ /// The currently executed request
/// The handler to get analyzed
/// The current stack of path segments that have already been analyzed, relative to the location of the OpenAPI concern
/// The document to be adjusted and enriched
/// The manager to generate JSON schemas with
- public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata)
+ public async ValueTask ExploreAsync(IRequest request, IHandler handler, List path, OpenApiDocument document, SchemaManager schemata)
{
foreach (var explorer in Explorers)
{
if (explorer.CanExplore(handler))
{
- explorer.Explore(handler, path, document, schemata, this);
+ await explorer.ExploreAsync(request, handler, path, document, schemata, this);
break;
}
}
diff --git a/Modules/OpenApi/Discovery/ConcernExplorer.cs b/Modules/OpenApi/Discovery/ConcernExplorer.cs
index 84e2bcf91..639e4d8ce 100644
--- a/Modules/OpenApi/Discovery/ConcernExplorer.cs
+++ b/Modules/OpenApi/Discovery/ConcernExplorer.cs
@@ -1,4 +1,6 @@
using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+
using NSwag;
namespace GenHTTP.Modules.OpenApi.Discovery;
@@ -8,11 +10,11 @@ public sealed class ConcernExplorer : IApiExplorer
public bool CanExplore(IHandler handler) => handler is IConcern;
- public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ public async ValueTask ExploreAsync(IRequest request, IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
{
if (handler is IConcern concern)
{
- registry.Explore(concern.Content, path, document, schemata);
+ await registry.ExploreAsync(request, concern.Content, path, document, schemata);
}
}
diff --git a/Modules/OpenApi/Discovery/LayoutExplorer.cs b/Modules/OpenApi/Discovery/LayoutExplorer.cs
index 6f14217a6..5c06e4f68 100644
--- a/Modules/OpenApi/Discovery/LayoutExplorer.cs
+++ b/Modules/OpenApi/Discovery/LayoutExplorer.cs
@@ -1,5 +1,8 @@
using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+
using GenHTTP.Modules.Layouting.Provider;
+
using NSwag;
namespace GenHTTP.Modules.OpenApi.Discovery;
@@ -9,18 +12,18 @@ public sealed class LayoutExplorer : IApiExplorer
public bool CanExplore(IHandler handler) => handler is LayoutRouter;
- public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ public async ValueTask ExploreAsync(IRequest request, IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
{
if (handler is LayoutRouter layout)
{
foreach (var root in layout.RootHandlers)
{
- registry.Explore(root, path, document, schemata);
+ await registry.ExploreAsync(request, root, path, document, schemata);
}
foreach (var (route, routeHandler) in layout.RoutedHandlers)
{
- registry.Explore(routeHandler, [..path, route], document, schemata);
+ await registry.ExploreAsync(request, routeHandler, [..path, route], document, schemata);
}
}
}
diff --git a/Modules/OpenApi/Discovery/MethodCollectionExplorer.cs b/Modules/OpenApi/Discovery/MethodCollectionExplorer.cs
index 9b53ebb36..2937c0efb 100644
--- a/Modules/OpenApi/Discovery/MethodCollectionExplorer.cs
+++ b/Modules/OpenApi/Discovery/MethodCollectionExplorer.cs
@@ -1,5 +1,8 @@
using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+
using GenHTTP.Modules.Reflection;
+
using NSwag;
namespace GenHTTP.Modules.OpenApi.Discovery;
@@ -9,13 +12,13 @@ public sealed class MethodCollectionExplorer : IApiExplorer
public bool CanExplore(IHandler handler) => handler is MethodCollection;
- public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ public async ValueTask ExploreAsync(IRequest request, IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
{
if (handler is MethodCollection collection)
{
foreach (var method in collection.Methods)
{
- registry.Explore(method, path, document, schemata);
+ await registry.ExploreAsync(request, method, path, document, schemata);
}
}
}
diff --git a/Modules/OpenApi/Discovery/MethodHandlerExplorer.cs b/Modules/OpenApi/Discovery/MethodHandlerExplorer.cs
index e6fa5026b..96e833a1d 100644
--- a/Modules/OpenApi/Discovery/MethodHandlerExplorer.cs
+++ b/Modules/OpenApi/Discovery/MethodHandlerExplorer.cs
@@ -12,7 +12,7 @@ public sealed class MethodHandlerExplorer : IApiExplorer
public bool CanExplore(IHandler handler) => handler is MethodHandler;
- public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ public ValueTask ExploreAsync(IRequest request, IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
{
if (handler is MethodHandler methodHandler)
{
@@ -123,6 +123,8 @@ public void Explore(IHandler handler, List path, OpenApiDocument documen
pathItem.Add(method.RawMethod, operation);
}
}
+
+ return ValueTask.CompletedTask;
}
private static OpenApiParameterKind MapArgumentType(OperationArgumentSource source) => source switch
diff --git a/Modules/OpenApi/Discovery/ServiceExplorer.cs b/Modules/OpenApi/Discovery/ServiceExplorer.cs
index 0205d0826..a1091b957 100644
--- a/Modules/OpenApi/Discovery/ServiceExplorer.cs
+++ b/Modules/OpenApi/Discovery/ServiceExplorer.cs
@@ -1,5 +1,8 @@
using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+
using GenHTTP.Modules.Reflection;
+
using NSwag;
namespace GenHTTP.Modules.OpenApi.Discovery;
@@ -9,11 +12,11 @@ public sealed class ServiceExplorer : IApiExplorer
public bool CanExplore(IHandler handler) => handler is IServiceMethodProvider;
- public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ public async ValueTask ExploreAsync(IRequest request, IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
{
if (handler is IServiceMethodProvider serviceProvider)
{
- registry.Explore(serviceProvider.Methods, path, document, schemata);
+ await registry.ExploreAsync(request, await serviceProvider.GetMethodsAsync(request), path, document, schemata);
}
}
diff --git a/Modules/OpenApi/Handler/OpenApiConcern.cs b/Modules/OpenApi/Handler/OpenApiConcern.cs
index c6c501e87..f38032b99 100644
--- a/Modules/OpenApi/Handler/OpenApiConcern.cs
+++ b/Modules/OpenApi/Handler/OpenApiConcern.cs
@@ -55,14 +55,14 @@ public OpenApiConcern(IHandler content, ApiDiscoveryRegistry discovery, bool ena
{
response = accept.ToLowerInvariant() switch
{
- "application/json" or "application/application/vnd.oai.openapi+json" => GetDocument(request, OpenApiFormat.Json),
- "application/yaml" or "application/application/vnd.oai.openapi+yaml" => GetDocument(request, OpenApiFormat.Yaml),
+ "application/json" or "application/application/vnd.oai.openapi+json" => await GetDocumentAsync(request, OpenApiFormat.Json),
+ "application/yaml" or "application/application/vnd.oai.openapi+yaml" => await GetDocumentAsync(request, OpenApiFormat.Yaml),
_ => throw new ProviderException(ResponseStatus.BadRequest, $"Generating API specifications of format '{accept}' is not supported")
};
}
else
{
- response = GetDocument(request, OpenApiFormat.Json);
+ response = await GetDocumentAsync(request, OpenApiFormat.Json);
}
response.Headers.Add("Vary", "Accept");
@@ -71,20 +71,20 @@ public OpenApiConcern(IHandler content, ApiDiscoveryRegistry discovery, bool ena
}
if (string.Compare(path, "openapi.json", StringComparison.OrdinalIgnoreCase) == 0)
{
- return GetDocument(request, OpenApiFormat.Json);
+ return await GetDocumentAsync(request, OpenApiFormat.Json);
}
if (string.Compare(path, "openapi.yaml", StringComparison.OrdinalIgnoreCase) == 0 || string.Compare(path, "openapi.yml", StringComparison.OrdinalIgnoreCase) == 0)
{
- return GetDocument(request, OpenApiFormat.Yaml);
+ return await GetDocumentAsync(request, OpenApiFormat.Yaml);
}
}
return await Content.HandleAsync(request);
}
- private IResponse GetDocument(IRequest request, OpenApiFormat format)
+ private async ValueTask GetDocumentAsync(IRequest request, OpenApiFormat format)
{
- var document = Discover(request, Discovery);
+ var document = await DiscoverAsync(request, Discovery);
var content = new OpenApiContent(document, format);
@@ -96,7 +96,7 @@ private IResponse GetDocument(IRequest request, OpenApiFormat format)
.Build();
}
- private ReturnDocument Discover(IRequest request, ApiDiscoveryRegistry registry)
+ private async ValueTask DiscoverAsync(IRequest request, ApiDiscoveryRegistry registry)
{
if (EnableCaching && _Cached != null)
{
@@ -119,7 +119,7 @@ private ReturnDocument Discover(IRequest request, ApiDiscoveryRegistry registry)
});
}
- registry.Explore(Content, [], document, schemata);
+ await registry.ExploreAsync(request, Content, [], document, schemata);
PostProcessor.Invoke(request, document);
diff --git a/Modules/OpenApi/IApiExplorer.cs b/Modules/OpenApi/IApiExplorer.cs
index f5560c987..67657f7de 100644
--- a/Modules/OpenApi/IApiExplorer.cs
+++ b/Modules/OpenApi/IApiExplorer.cs
@@ -1,4 +1,5 @@
using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
using GenHTTP.Modules.OpenApi.Discovery;
using NSwag;
@@ -27,11 +28,12 @@ public interface IApiExplorer
/// Analyzes the given handler and adds the endpoints defined by this handler to the resulting
/// OpenAPI document.
///
+ /// The currently executed request
/// The handler to be analyzed
/// The current stack of path segments that have already been analyzed, relative to the location of the OpenAPI concern
/// The document to be adjusted and enriched
/// The manager to generate JSON schemas with
/// The registry containing all active explorers which can be used to further analyze any child handler of the given handler instance
- void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry);
+ ValueTask ExploreAsync(IRequest request, IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry);
}
diff --git a/Modules/Reflection/IRegistryBuilder.cs b/Modules/Reflection/IRegistryBuilder.cs
new file mode 100644
index 000000000..62ee3990e
--- /dev/null
+++ b/Modules/Reflection/IRegistryBuilder.cs
@@ -0,0 +1,40 @@
+using GenHTTP.Api.Infrastructure;
+
+using GenHTTP.Modules.Conversion.Formatters;
+using GenHTTP.Modules.Conversion.Serializers;
+using GenHTTP.Modules.Reflection.Injectors;
+
+namespace GenHTTP.Modules.Reflection;
+
+///
+/// A protocol for builders that will internally create a
+/// instance.
+///
+/// The builder type to be returned
+public interface IRegistryBuilder
+{
+
+ ///
+ /// Configures the serialization formats (such as JSON or XML) that are
+ /// supported by this service to read or generate bodies.
+ ///
+ /// The serialization registry to be used
+ T Serializers(IBuilder registry);
+
+ ///
+ /// Configures the parameter injectors that are available within this
+ /// service. This allows you to inject custom values into the methods
+ /// of your service, such as the currently logged-in user or session.
+ ///
+ /// The injection registry to be used
+ T Injectors(IBuilder registry);
+
+ ///
+ /// Configures the formatters that will be used by this service to
+ /// format primitive types (such as numbers or dates). This allows
+ /// you to use custom formats within paths, queries or bodies.
+ ///
+ /// The formatter registry to be used
+ T Formatters(IBuilder registry);
+
+}
diff --git a/Modules/Reflection/IServiceMethodProvider.cs b/Modules/Reflection/IServiceMethodProvider.cs
index 3798415d7..b43afce82 100644
--- a/Modules/Reflection/IServiceMethodProvider.cs
+++ b/Modules/Reflection/IServiceMethodProvider.cs
@@ -1,4 +1,6 @@
-namespace GenHTTP.Modules.Reflection;
+using GenHTTP.Api.Protocol;
+
+namespace GenHTTP.Modules.Reflection;
///
/// Implemented by handlers that use the handler
@@ -9,8 +11,8 @@ public interface IServiceMethodProvider
{
///
- /// The method collection actually serving requests.
+ /// Retrieves the methods that are provided by this handler.
///
- MethodCollection Methods { get; }
+ ValueTask GetMethodsAsync(IRequest request);
}
diff --git a/Modules/Reflection/Injectors/HandlerInjector.cs b/Modules/Reflection/Injectors/HandlerInjector.cs
index a37e16038..f990e2a1a 100644
--- a/Modules/Reflection/Injectors/HandlerInjector.cs
+++ b/Modules/Reflection/Injectors/HandlerInjector.cs
@@ -6,7 +6,7 @@ namespace GenHTTP.Modules.Reflection.Injectors;
public class HandlerInjector : IParameterInjector
{
- public bool Supports(Type type) => type == typeof(IHandler);
+ public bool Supports(IRequest request, Type type) => type == typeof(IHandler);
public object GetValue(IHandler handler, IRequest request, Type targetType) => handler;
}
diff --git a/Modules/Reflection/Injectors/IParameterInjector.cs b/Modules/Reflection/Injectors/IParameterInjector.cs
index 4656a0223..7c33e2c6c 100644
--- a/Modules/Reflection/Injectors/IParameterInjector.cs
+++ b/Modules/Reflection/Injectors/IParameterInjector.cs
@@ -6,7 +6,7 @@ namespace GenHTTP.Modules.Reflection.Injectors;
public interface IParameterInjector
{
- bool Supports(Type type);
+ bool Supports(IRequest request, Type type);
object? GetValue(IHandler handler, IRequest request, Type targetType);
diff --git a/Modules/Reflection/Injectors/RequestInjector.cs b/Modules/Reflection/Injectors/RequestInjector.cs
index ade6a8399..350496e8d 100644
--- a/Modules/Reflection/Injectors/RequestInjector.cs
+++ b/Modules/Reflection/Injectors/RequestInjector.cs
@@ -6,7 +6,7 @@ namespace GenHTTP.Modules.Reflection.Injectors;
public class RequestInjector : IParameterInjector
{
- public bool Supports(Type type) => type == typeof(IRequest);
+ public bool Supports(IRequest request, Type type) => type == typeof(IRequest);
public object GetValue(IHandler handler, IRequest request, Type targetType) => request;
}
diff --git a/Modules/Reflection/Injectors/UserInjector.cs b/Modules/Reflection/Injectors/UserInjector.cs
index 7367091b8..e9a13e426 100644
--- a/Modules/Reflection/Injectors/UserInjector.cs
+++ b/Modules/Reflection/Injectors/UserInjector.cs
@@ -9,7 +9,7 @@ public class UserInjector : IParameterInjector where T : IUser
#region Get-/Setters
- public bool Supports(Type type) => type == typeof(T);
+ public bool Supports(IRequest request, Type type) => type == typeof(T);
#endregion
diff --git a/Modules/Reflection/MethodHandler.cs b/Modules/Reflection/MethodHandler.cs
index 858b0183d..dd9580bda 100644
--- a/Modules/Reflection/MethodHandler.cs
+++ b/Modules/Reflection/MethodHandler.cs
@@ -29,7 +29,7 @@ public sealed class MethodHandler : IHandler
public IMethodConfiguration Configuration { get; }
- private object Instance { get; }
+ private Func> InstanceProvider { get; }
public MethodRegistry Registry { get; }
@@ -43,13 +43,13 @@ public sealed class MethodHandler : IHandler
/// Creates a new handler to serve a single API operation.
///
/// The operation to be executed and provided (use to create an operation)
- /// The object to execute the operation on
+ /// A factory that will provide an instance to actually execute the operation on
/// Additional, use-specified information about the operation
/// The customized registry to be used to read and write data
- public MethodHandler(Operation operation, object instance, IMethodConfiguration metaData, MethodRegistry registry)
+ public MethodHandler(Operation operation, Func> instanceProvider, IMethodConfiguration metaData, MethodRegistry registry)
{
Configuration = metaData;
- Instance = instance;
+ InstanceProvider = instanceProvider;
Operation = operation;
Registry = registry;
@@ -72,7 +72,7 @@ public MethodHandler(Operation operation, object instance, IMethodConfiguration
return interception;
}
- var result = Invoke(arguments.Values.ToArray());
+ var result = await InvokeAsync(request, arguments.Values.ToArray());
return await ResponseProvider.GetResponseAsync(request, Operation, await UnwrapAsync(result), null);
}
@@ -144,11 +144,13 @@ public MethodHandler(Operation operation, object instance, IMethodConfiguration
return null;
}
- private object? Invoke(object?[] arguments)
+ private async ValueTask