Skip to content

Commit 3f87d16

Browse files
Add support for dependency injection (#675)
1 parent 0f0e228 commit 3f87d16

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1083
-132
lines changed

GenHTTP.slnx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
<Solution>
2-
32
<Folder Name="/Adapters/">
43
<Project Path="Adapters\AspNetCore\GenHTTP.Adapters.AspNetCore.csproj" />
54
</Folder>
6-
75
<Folder Name="/API/">
86
<Project Path="API\GenHTTP.Api.csproj" />
97
</Folder>
10-
118
<Folder Name="/Engine/">
129
<Project Path="Engine\Internal\GenHTTP.Engine.Internal.csproj" />
1310
<Project Path="Engine\Kestrel\GenHTTP.Engine.Kestrel.csproj" />
1411
<Project Path="Engine\Shared\GenHTTP.Engine.Shared.csproj" />
1512
</Folder>
16-
1713
<Folder Name="/Modules/">
1814
<Project Path="Modules\ApiBrowsing\GenHTTP.Modules.ApiBrowsing.csproj" />
1915
<Project Path="Modules\Authentication\GenHTTP.Modules.Authentication.csproj" />
@@ -23,6 +19,7 @@
2319
<Project Path="Modules\Compression\GenHTTP.Modules.Compression.csproj" />
2420
<Project Path="Modules\Controllers\GenHTTP.Modules.Controllers.csproj" />
2521
<Project Path="Modules\Conversion\GenHTTP.Modules.Conversion.csproj" />
22+
<Project Path="Modules\DependencyInjection\GenHTTP.Modules.DependencyInjection.csproj" />
2623
<Project Path="Modules\DirectoryBrowsing\GenHTTP.Modules.DirectoryBrowsing.csproj" />
2724
<Project Path="Modules\ErrorHandling\GenHTTP.Modules.ErrorHandling.csproj" />
2825
<Project Path="Modules\Functional\GenHTTP.Modules.Functional.csproj" />
@@ -46,29 +43,23 @@
4643
<Project Path="Modules\Webservices\GenHTTP.Modules.Webservices.csproj" />
4744
<Project Path="Modules\Websockets\GenHTTP.Modules.Websockets.csproj" />
4845
</Folder>
49-
5046
<Folder Name="/Playground/">
5147
<Project Path="Playground\GenHTTP.Playground.csproj" />
5248
</Folder>
53-
54-
<Folder Name="/Testing/">
55-
<Project Path="Testing\Acceptance\GenHTTP.Testing.Acceptance.csproj" />
56-
<Project Path="Testing\Testing\GenHTTP.Testing.csproj" />
57-
</Folder>
58-
5949
<Folder Name="/Solution Items/">
6050
<File Path="CONTRIBUTING.md" />
6151
<File Path="LICENSE" />
6252
<File Path="README.md" />
6353
</Folder>
64-
6554
<Folder Name="/Solution Items/Packages/">
6655
<File Path="Packages\GenHTTP.Core.Kestrel.nuspec" />
6756
<File Path="Packages\GenHTTP.Core.nuspec" />
6857
</Folder>
69-
7058
<Folder Name="/Solution Items/Resources/">
7159
<File Path="Resources\icon.png" />
7260
</Folder>
73-
61+
<Folder Name="/Testing/">
62+
<Project Path="Testing\Acceptance\GenHTTP.Testing.Acceptance.csproj" />
63+
<Project Path="Testing\Testing\GenHTTP.Testing.csproj" />
64+
</Folder>
7465
</Solution>

Modules/Controllers/Provider/ControllerBuilder.cs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System.Diagnostics.CodeAnalysis;
2+
23
using GenHTTP.Api.Content;
34
using GenHTTP.Api.Infrastructure;
5+
using GenHTTP.Api.Protocol;
6+
47
using GenHTTP.Modules.Conversion;
58
using GenHTTP.Modules.Conversion.Formatters;
69
using GenHTTP.Modules.Conversion.Serializers;
@@ -9,16 +12,18 @@
912

1013
namespace GenHTTP.Modules.Controllers.Provider;
1114

12-
public sealed class ControllerBuilder : IHandlerBuilder<ControllerBuilder>
15+
public sealed class ControllerBuilder : IHandlerBuilder<ControllerBuilder>, IRegistryBuilder<ControllerBuilder>
1316
{
1417
private readonly List<IConcernBuilder> _Concerns = [];
1518

19+
private Type? _Type;
20+
21+
private Func<IRequest, ValueTask<object>>? _InstanceProvider;
22+
1623
private IBuilder<FormatterRegistry>? _Formatters;
1724

1825
private IBuilder<InjectionRegistry>? _Injection;
1926

20-
private object? _Instance;
21-
2227
private IBuilder<SerializationRegistry>? _Serializers;
2328

2429
#region Functionality
@@ -42,14 +47,25 @@ public ControllerBuilder Formatters(IBuilder<FormatterRegistry> registry)
4247
}
4348

4449
public ControllerBuilder Type<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new()
50+
=> Instance(new T());
51+
52+
public ControllerBuilder Type([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type)
4553
{
46-
_Instance = new T();
54+
_Type = type;
4755
return this;
4856
}
4957

5058
public ControllerBuilder Instance(object instance)
5159
{
52-
_Instance = instance;
60+
_Type = instance.GetType();
61+
_InstanceProvider = (_) => ValueTask.FromResult(instance);
62+
63+
return this;
64+
}
65+
66+
public ControllerBuilder InstanceProvider(Func<IRequest, ValueTask<object>> provider)
67+
{
68+
_InstanceProvider = provider;
5369
return this;
5470
}
5571

@@ -67,11 +83,13 @@ public IHandler Build()
6783

6884
var formatters = (_Formatters ?? Formatting.Default()).Build();
6985

70-
var instance = _Instance ?? throw new BuilderMissingPropertyException("Instance or Type");
86+
var instanceProvider = _InstanceProvider ?? throw new BuilderMissingPropertyException("Instance provider has not been set");
87+
88+
var type = _Type ?? throw new BuilderMissingPropertyException("Type has not been set");
7189

7290
var extensions = new MethodRegistry(serializers, injectors, formatters);
7391

74-
return Concerns.Chain(_Concerns, new ControllerHandler(instance, extensions));
92+
return Concerns.Chain(_Concerns, new ControllerHandler(type, instanceProvider, extensions));
7593
}
7694

7795
#endregion

Modules/Controllers/Provider/ControllerHandler.cs

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,59 +13,74 @@ public sealed partial class ControllerHandler : IHandler, IServiceMethodProvider
1313
{
1414
private static readonly Regex HyphenMatcher = CreateHyphenMatcher();
1515

16+
private MethodCollection? _Methods;
17+
1618
#region Get-/Setters
1719

18-
public MethodCollection Methods { get; }
20+
private Type Type { get; }
1921

20-
private ResponseProvider ResponseProvider { get; }
22+
private Func<IRequest, ValueTask<object>> InstanceProvider { get; }
2123

2224
private MethodRegistry Registry { get; }
2325

24-
private object Instance { get; }
25-
2626
#endregion
2727

2828
#region Initialization
2929

30-
public ControllerHandler(object instance, MethodRegistry registry)
30+
public ControllerHandler(Type type, Func<IRequest, ValueTask<object>> instanceProvider, MethodRegistry registry)
3131
{
32+
Type = type;
33+
InstanceProvider = instanceProvider;
3234
Registry = registry;
35+
}
36+
37+
#endregion
3338

34-
Instance = instance;
39+
#region Functionality
3540

36-
ResponseProvider = new ResponseProvider(registry);
41+
public ValueTask PrepareAsync() => ValueTask.CompletedTask;
3742

38-
Methods = new MethodCollection(AnalyzeMethods(instance.GetType(), registry));
39-
}
43+
public async ValueTask<IResponse?> HandleAsync(IRequest request) => await (await GetMethodsAsync(request)).HandleAsync(request);
4044

41-
private IEnumerable<MethodHandler> AnalyzeMethods(Type type, MethodRegistry registry)
45+
public async ValueTask<MethodCollection> GetMethodsAsync(IRequest request)
4246
{
43-
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
47+
if (_Methods != null) return _Methods;
48+
49+
var found = new List<MethodHandler>();
50+
51+
52+
foreach (var method in Type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
4453
{
4554
var annotation = method.GetCustomAttribute<ControllerActionAttribute>(true) ?? new MethodAttribute();
4655

4756
var arguments = FindPathArguments(method);
4857

49-
var operation = CreateOperation(method, arguments);
58+
var operation = CreateOperation(request, method, arguments, Registry);
5059

51-
yield return new MethodHandler(operation, Instance, annotation, registry);
60+
found.Add(new MethodHandler(operation, InstanceProvider, annotation, Registry));
5261
}
62+
63+
var result = new MethodCollection(found);
64+
65+
await result.PrepareAsync();
66+
67+
return _Methods = result;
5368
}
5469

55-
private Operation CreateOperation(MethodInfo method, List<string> arguments)
70+
private static Operation CreateOperation(IRequest request, MethodInfo method, List<string> arguments, MethodRegistry registry)
5671
{
5772
var pathArguments = string.Join('/', arguments.Select(a => $":{a}"));
5873

5974
if (method.Name == "Index")
6075
{
61-
return OperationBuilder.Create(pathArguments.Length > 0 ? $"/{pathArguments}/" : null, method, Registry, true);
76+
return OperationBuilder.Create(request, pathArguments.Length > 0 ? $"/{pathArguments}/" : null, method, registry, true);
6277
}
6378

6479
var name = HypenCase(method.Name);
6580

6681
var path = $"/{name}";
6782

68-
return OperationBuilder.Create(pathArguments.Length > 0 ? $"{path}/{pathArguments}/" : $"{path}/", method, Registry, true);
83+
return OperationBuilder.Create(request, pathArguments.Length > 0 ? $"{path}/{pathArguments}/" : $"{path}/", method, registry, true);
6984
}
7085

7186
private static List<string> FindPathArguments(MethodInfo method)
@@ -93,12 +108,4 @@ private static List<string> FindPathArguments(MethodInfo method)
93108

94109
#endregion
95110

96-
#region Functionality
97-
98-
public ValueTask PrepareAsync() => Methods.PrepareAsync();
99-
100-
public ValueTask<IResponse?> HandleAsync(IRequest request) => Methods.HandleAsync(request);
101-
102-
#endregion
103-
104111
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using GenHTTP.Api.Content;
2+
using GenHTTP.Api.Protocol;
3+
using GenHTTP.Modules.DependencyInjection.Infrastructure;
4+
5+
namespace GenHTTP.Modules.DependencyInjection.Basics;
6+
7+
internal class ConcernIntegration<T> : IConcern where T : class, IDependentConcern
8+
{
9+
10+
#region Getters/Setters
11+
12+
public IHandler Content { get; }
13+
14+
#endregion
15+
16+
#region Initialization
17+
18+
internal ConcernIntegration(IHandler content)
19+
{
20+
Content = content;
21+
}
22+
23+
#endregion
24+
25+
#region Functionality
26+
27+
public ValueTask PrepareAsync() => Content.PrepareAsync();
28+
29+
public async ValueTask<IResponse?> HandleAsync(IRequest request)
30+
{
31+
var instance = await InstanceProvider.ProvideAsync<T>(request);
32+
33+
return await instance.HandleAsync(Content, request);
34+
}
35+
36+
#endregion
37+
38+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using GenHTTP.Api.Content;
2+
3+
namespace GenHTTP.Modules.DependencyInjection.Basics;
4+
5+
internal class ConcernIntegrationBuilder<T> : IConcernBuilder where T : class, IDependentConcern
6+
{
7+
8+
public IConcern Build(IHandler content) => new ConcernIntegration<T>(content);
9+
10+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using GenHTTP.Api.Content;
2+
using GenHTTP.Api.Protocol;
3+
4+
using GenHTTP.Modules.DependencyInjection.Infrastructure;
5+
6+
namespace GenHTTP.Modules.DependencyInjection.Basics;
7+
8+
internal class HandlerIntegration<T> : IHandler where T: class, IDependentHandler
9+
{
10+
11+
public ValueTask PrepareAsync() => ValueTask.CompletedTask;
12+
13+
public async ValueTask<IResponse?> HandleAsync(IRequest request)
14+
{
15+
var instance = await InstanceProvider.ProvideAsync<T>(request);
16+
17+
return await instance.HandleAsync(request);
18+
}
19+
20+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using GenHTTP.Api.Content;
2+
3+
namespace GenHTTP.Modules.DependencyInjection.Basics;
4+
5+
internal class HandlerIntegrationBuilder<T> : IHandlerBuilder where T : class, IDependentHandler
6+
{
7+
8+
public IHandler Build() => new HandlerIntegration<T>();
9+
10+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using GenHTTP.Api.Content;
2+
3+
using GenHTTP.Modules.DependencyInjection.Basics;
4+
5+
namespace GenHTTP.Modules.DependencyInjection;
6+
7+
/// <summary>
8+
/// Allows to enable dependency injection for concerns and handlers.
9+
/// </summary>
10+
public static class Dependent
11+
{
12+
13+
/// <summary>
14+
/// Allows the given class to be used as a regular concern with dependency injection enabled.
15+
/// </summary>
16+
/// <typeparam name="T">The class implementing the concern</typeparam>
17+
public static IConcernBuilder Concern<T>() where T : class, IDependentConcern => new ConcernIntegrationBuilder<T>();
18+
19+
/// <summary>
20+
/// Allows the given class to be used as a regular handler with dependency injection enabled.
21+
/// </summary>
22+
/// <typeparam name="T">The class implementing the handler</typeparam>
23+
public static IHandlerBuilder Handler<T>() where T : class, IDependentHandler => new HandlerIntegrationBuilder<T>();
24+
25+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
using GenHTTP.Api.Infrastructure;
4+
5+
using GenHTTP.Modules.Controllers.Provider;
6+
using GenHTTP.Modules.Conversion.Formatters;
7+
using GenHTTP.Modules.Conversion.Serializers;
8+
using GenHTTP.Modules.DependencyInjection.Infrastructure;
9+
using GenHTTP.Modules.Layouting.Provider;
10+
using GenHTTP.Modules.Reflection.Injectors;
11+
12+
namespace GenHTTP.Modules.DependencyInjection;
13+
14+
public static class DependentController
15+
{
16+
17+
/// <summary>
18+
/// Adds the given controller to the layout with dependency injection enabled.
19+
/// </summary>
20+
/// <param name="layout">The layout to add the controller to</param>
21+
/// <param name="path">The path to register the controller at</param>
22+
/// <param name="injectors">Additional parameter injections to be used by the controller</param>
23+
/// <param name="serializers">Additional serializers to be used by the controller</param>
24+
/// <param name="formatters">Additional formatters to be used by the controller</param>
25+
/// <typeparam name="T">The class implementing the controller</typeparam>
26+
/// <returns>The given layout with the specified controller attached</returns>
27+
public static LayoutBuilder AddDependentController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder layout, string path, InjectionRegistryBuilder? injectors = null, IBuilder<SerializationRegistry>? serializers = null, IBuilder<FormatterRegistry>? formatters = null) where T : class
28+
{
29+
var builder = new ControllerBuilder();
30+
31+
builder.Type(typeof(T));
32+
builder.InstanceProvider(async (r) => await InstanceProvider.ProvideAsync<T>(r));
33+
34+
builder.Configure(injectors, serializers, formatters);
35+
36+
return layout.Add(path, builder);
37+
}
38+
39+
}

0 commit comments

Comments
 (0)