Skip to content

Commit 53cf2f5

Browse files
authored
Dev (#35)
* - make component registry behaviour consistent with how services are added to service collection (TryAdd vs. Add) * - make component registry async local * - fix component registration when add DI method is called multiple times * - code cleanup * - make testserver project include additional modules * - bump version
1 parent a1d1905 commit 53cf2f5

20 files changed

+149
-49
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1919
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
2020

21-
<Version>1.3.2</Version>
21+
<Version>1.3.3</Version>
2222
</PropertyGroup>
2323
</Project>

ModEndpoints.sln

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9FD33E1B-1
5252
docs\EndpointTypes.md = docs\EndpointTypes.md
5353
docs\EndpointTypes_BusinessResultEndpoint.md = docs\EndpointTypes_BusinessResultEndpoint.md
5454
docs\EndpointTypes_MinimalEndpoint.md = docs\EndpointTypes_MinimalEndpoint.md
55-
docs\EndpointTypes_WebResultEndpoint.md = docs\EndpointTypes_WebResultEndpoint.md
5655
docs\EndpointTypes_ServiceEndpoint.md = docs\EndpointTypes_ServiceEndpoint.md
56+
docs\EndpointTypes_WebResultEndpoint.md = docs\EndpointTypes_WebResultEndpoint.md
5757
docs\HandlingFiles.md = docs\HandlingFiles.md
5858
docs\IAsyncEnumerableResponse.md = docs\IAsyncEnumerableResponse.md
5959
docs\OpenApiDocumentation.md = docs\OpenApiDocumentation.md
@@ -65,6 +65,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9FD33E1B-1
6565
docs\WebResultEndpointResponseMapping.md = docs\WebResultEndpointResponseMapping.md
6666
EndProjectSection
6767
EndProject
68+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModEndpoints.TestServer.Customers", "tests\ModEndpoints.TestServer.Customers\ModEndpoints.TestServer.Customers.csproj", "{7D3F2885-9DBE-30EC-4E67-5BCC3AD2EC8F}"
69+
EndProject
6870
Global
6971
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7072
Debug|Any CPU = Debug|Any CPU
@@ -115,6 +117,10 @@ Global
115117
{6789D27E-ECA5-4332-A51C-A68626859465}.Debug|Any CPU.Build.0 = Debug|Any CPU
116118
{6789D27E-ECA5-4332-A51C-A68626859465}.Release|Any CPU.ActiveCfg = Release|Any CPU
117119
{6789D27E-ECA5-4332-A51C-A68626859465}.Release|Any CPU.Build.0 = Release|Any CPU
120+
{7D3F2885-9DBE-30EC-4E67-5BCC3AD2EC8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
121+
{7D3F2885-9DBE-30EC-4E67-5BCC3AD2EC8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
122+
{7D3F2885-9DBE-30EC-4E67-5BCC3AD2EC8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
123+
{7D3F2885-9DBE-30EC-4E67-5BCC3AD2EC8F}.Release|Any CPU.Build.0 = Release|Any CPU
118124
EndGlobalSection
119125
GlobalSection(SolutionProperties) = preSolution
120126
HideSolutionNode = FALSE
@@ -132,6 +138,7 @@ Global
132138
{338C85BC-44D4-D6DA-437B-EE2BC45F041A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
133139
{31B5E39A-9973-1BFE-42E6-485AE83C4910} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
134140
{6789D27E-ECA5-4332-A51C-A68626859465} = {74B81852-D3A9-49BA-A62F-A653FBB36665}
141+
{7D3F2885-9DBE-30EC-4E67-5BCC3AD2EC8F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
135142
EndGlobalSection
136143
GlobalSection(ExtensibilityGlobals) = postSolution
137144
SolutionGuid = {8AD0451A-6029-4173-B7C5-737D09139323}

src/ModEndpoints.Core/DependencyInjectionExtensions.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public static IServiceCollection AddModEndpointsCoreFromAssembly(
5252

5353
services.TryAddScoped<IComponentDiscriminator, ComponentDiscriminator>();
5454

55+
ComponentRegistryAccessor.Instance.Initialize();
56+
5557
return services
5658
.AddRouteGroupsCoreFromAssembly(assembly, options)
5759
.AddEndpointsCoreFromAssembly(assembly, options);
@@ -80,7 +82,7 @@ private static IServiceCollection AddRouteGroupsCoreFromAssembly(
8082
.ToArray();
8183

8284
services.TryAddEnumerable(serviceDescriptors);
83-
ComponentRegistry.Instance.TryAddRouteGroups(routeGroupTypes);
85+
ComponentRegistryAccessor.Instance.Registry?.TryAddRouteGroups(routeGroupTypes);
8486

8587
return services;
8688
}
@@ -161,12 +163,13 @@ static void AddServiceEndpoint(
161163
}
162164
}
163165
services.Add(descriptor);
166+
ComponentRegistryAccessor.Instance.Registry?.AddEndpoint(requestType, endpointType);
164167
}
165168
else
166169
{
167170
services.TryAdd(descriptor);
171+
ComponentRegistryAccessor.Instance.Registry?.TryAddEndpoint(requestType, endpointType);
168172
}
169-
ComponentRegistry.Instance.TryAddEndpoint(requestType, endpointType);
170173
}
171174

172175
static void AddEndpoint(
@@ -179,7 +182,7 @@ static void AddEndpoint(
179182
endpointType,
180183
endpointType,
181184
options.EndpointLifetime));
182-
ComponentRegistry.Instance.TryAddEndpoint(endpointType, endpointType);
185+
ComponentRegistryAccessor.Instance.Registry?.TryAddEndpoint(endpointType, endpointType);
183186
}
184187

185188
static Type[] GetGenericArgumentsOfBase(Type derivedType, Type baseType)
@@ -238,8 +241,14 @@ public static WebApplication MapModEndpointsCore(
238241
IEndpointRouteBuilder builder = app;
239242
using (var scope = builder.ServiceProvider.CreateScope())
240243
{
241-
var routeGroups = ComponentRegistry.Instance.GetRouteGroups().Select(t => RuntimeHelpers.GetUninitializedObject(t) as IRouteGroupConfigurator).Where(i => i is not null).Select(i => i!); ;
242-
var endpoints = ComponentRegistry.Instance.GetEndpoints().Select(t => RuntimeHelpers.GetUninitializedObject(t) as IEndpointConfigurator).Where(i => i is not null).Select(i => i!);
244+
var routeGroups = ComponentRegistryAccessor.Instance.Registry?.GetRouteGroups()
245+
.Select(t => RuntimeHelpers.GetUninitializedObject(t) as IRouteGroupConfigurator)
246+
.Where(i => i is not null)
247+
.Select(i => i!) ?? [];
248+
var endpoints = ComponentRegistryAccessor.Instance.Registry?.GetEndpoints()
249+
.Select(t => RuntimeHelpers.GetUninitializedObject(t) as IEndpointConfigurator)
250+
.Where(i => i is not null)
251+
.Select(i => i!) ?? [];
243252

244253
//Items that don't have a membership to any route group or
245254
//items that have a membership to root route group (items at root)

src/ModEndpoints.Core/[Configuration]/ComponentRegistry.cs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,32 @@ namespace ModEndpoints.Core;
44

55
internal class ComponentRegistry
66
{
7-
private readonly ConcurrentDictionary<Type, Type> _routeGroups;
8-
private readonly ConcurrentDictionary<Type, Type> _endpoints;
9-
10-
private static Lazy<ComponentRegistry> _instance =
11-
new Lazy<ComponentRegistry>(
12-
() => new ComponentRegistry(),
13-
LazyThreadSafetyMode.ExecutionAndPublication);
14-
15-
public static ComponentRegistry Instance => _instance.Value;
16-
17-
private ComponentRegistry()
18-
{
19-
_routeGroups = new();
20-
_endpoints = new();
21-
}
7+
private readonly ConcurrentDictionary<Type, Type> _routeGroups = new();
8+
private readonly ConcurrentDictionary<Type, Type> _endpoints = new();
229

2310
public void TryAddRouteGroups(IEnumerable<Type> implementationTypes)
2411
{
2512
foreach (var type in implementationTypes)
2613
{
27-
_routeGroups[type] = type;
14+
TryAddRouteGroup(type);
2815
}
2916
}
3017

31-
public void TryAddEndpoint(Type key, Type implementationType)
18+
private void TryAddRouteGroup(Type implementationType)
19+
{
20+
_routeGroups.TryAdd(implementationType, implementationType);
21+
}
22+
23+
public void AddEndpoint(Type key, Type implementationType)
3224
{
3325
_endpoints[key] = implementationType;
3426
}
3527

28+
public void TryAddEndpoint(Type key, Type implementationType)
29+
{
30+
_endpoints.TryAdd(key, implementationType);
31+
}
32+
3633
public ICollection<Type> GetRouteGroups()
3734
{
3835
return _routeGroups.Values;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace ModEndpoints.Core;
2+
3+
internal class ComponentRegistryAccessor
4+
{
5+
private static readonly AsyncLocal<ComponentRegistryHolder> _registry = new();
6+
7+
/// <inheritdoc/>
8+
public ComponentRegistry? Registry
9+
{
10+
get
11+
{
12+
return _registry.Value?.Registry;
13+
}
14+
set
15+
{
16+
// Clear current ComponentRegistry trapped in the AsyncLocals, as its done.
17+
if (_registry.Value is not null)
18+
{
19+
_registry.Value.Registry = null;
20+
}
21+
22+
if (value != null)
23+
{
24+
// Use an object indirection to hold the ComponentRegistry in the AsyncLocal,
25+
// so it can be cleared in all ExecutionContexts when its cleared.
26+
_registry.Value = new ComponentRegistryHolder { Registry = value };
27+
}
28+
}
29+
}
30+
31+
private sealed class ComponentRegistryHolder
32+
{
33+
public ComponentRegistry? Registry;
34+
}
35+
36+
private static Lazy<ComponentRegistryAccessor> _instance =
37+
new Lazy<ComponentRegistryAccessor>(
38+
() => new ComponentRegistryAccessor(),
39+
LazyThreadSafetyMode.ExecutionAndPublication);
40+
41+
public static ComponentRegistryAccessor Instance => _instance.Value;
42+
43+
private ComponentRegistryAccessor()
44+
{
45+
}
46+
47+
public void Initialize()
48+
{
49+
if (Registry is null)
50+
{
51+
Registry = new ComponentRegistry();
52+
}
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
using ModEndpoints.Core;
22

3-
namespace ModEndpoints.TestServer.Features.Customers.Configuration;
3+
namespace ModEndpoints.TestServer.Customers.Configuration;
44

5-
[MapToGroup<FeaturesRouteGroup>]
65
internal class CustomersRouteGroup : RouteGroupConfigurator
76
{
87
protected override void Configure(
98
RouteGroupConfigurationBuilder builder,
109
ConfigurationContext<RouteGroupConfigurationParameters> configurationContext)
1110
{
12-
builder.MapGroup("/customers");
11+
builder.MapGroup("/api/customers");
1312
}
1413
}

tests/ModEndpoints.TestServer/Features/Customers/CreateCustomer.cs renamed to tests/ModEndpoints.TestServer.Customers/CreateCustomer.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
using FluentValidation;
2+
using Microsoft.AspNetCore.Http;
23
using Microsoft.AspNetCore.Http.HttpResults;
34
using Microsoft.AspNetCore.Mvc;
45
using ModEndpoints.Core;
5-
using ModEndpoints.TestServer.Features.Customers.Configuration;
6+
using ModEndpoints.TestServer.Customers.Configuration;
67

7-
namespace ModEndpoints.TestServer.Features.Customers;
8+
namespace ModEndpoints.TestServer.Customers;
89
public record CreateCustomerRequest([FromBody] CreateCustomerRequestBody Body);
910

1011
public record CreateCustomerRequestBody(string FirstName, string? MiddleName, string LastName);

tests/ModEndpoints.TestServer/Features/Customers/DeleteCustomer.cs renamed to tests/ModEndpoints.TestServer.Customers/DeleteCustomer.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using FluentValidation;
2+
using Microsoft.AspNetCore.Http;
23
using ModEndpoints.Core;
3-
using ModEndpoints.TestServer.Features.Customers.Configuration;
4+
using ModEndpoints.TestServer.Customers.Configuration;
45

5-
namespace ModEndpoints.TestServer.Features.Customers;
6+
namespace ModEndpoints.TestServer.Customers;
67
public record DeleteCustomerRequest(Guid Id);
78

89
internal class DeleteCustomerRequestValidator : AbstractValidator<DeleteCustomerRequest>

tests/ModEndpoints.TestServer/Features/Customers/DisabledCustomerFeature.cs renamed to tests/ModEndpoints.TestServer.Customers/DisabledCustomerFeature.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using ModEndpoints.Core;
1+
using Microsoft.AspNetCore.Http;
2+
using ModEndpoints.Core;
23
using ModEndpoints.RemoteServices.Core;
3-
using ModEndpoints.TestServer.Features.Customers.Configuration;
4+
using ModEndpoints.TestServer.Customers.Configuration;
45

5-
namespace ModEndpoints.TestServer.Features.Customers;
6+
namespace ModEndpoints.TestServer.Customers;
67

78
[MapToGroup<CustomersRouteGroup>()]
89
[DoNotRegister]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
using FluentValidation;
33
using Microsoft.AspNetCore.Mvc;
44
using ModEndpoints.Core;
5-
using ModEndpoints.TestServer.Features.Customers.Configuration;
5+
using ModEndpoints.TestServer.Customers.Configuration;
66

7-
namespace ModEndpoints.TestServer.Features.Customers;
7+
namespace ModEndpoints.TestServer.Customers;
88

99
public record FilterAndStreamCustomerListRequest([FromBody] FilterAndStreamCustomerListRequestBody Body);
1010

0 commit comments

Comments
 (0)