Skip to content

Commit e33eb6f

Browse files
committed
WebApiVersionEndpoint & GlobalWebApiEndpoint DI registration change to "Try..."
- WebApiVersionEndpoint - Change DI registration to 'TryAddEquatableKeyedSingleton', so only the first WebApiVersionEndpoint registration actually happens - GlobalWebApiEndpoint - Change DI registration to 'TryAddSingleton', so only the first GlobalWebApiEndpoint registration actually happens
1 parent 0fd78fc commit e33eb6f

File tree

15 files changed

+263
-17
lines changed

15 files changed

+263
-17
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ The entire API can be configured to set global parameters. This is an ideal plac
278278

279279
To configure the entire API, you need to create a class that implements the IGlobalWebApiEndpoint interface.
280280

281-
**Note: There can only be one class that implements IGlobalWebApiEndpoint. This is enforced by an analyzer.**
281+
**Note: There can only be one class that implements IGlobalWebApiEndpoint. This is enforced by an analyzer. Analyzers work per project, so if you have *GlobalWebApiEndpoint* in more than 1 project, only the first will be registered. The order is dictated by the order of the *[AddWebApiEndpointsFor](#addwebapiendpointsfor-per-project-containing-webapiendpoints)* calls.**
282282

283283
**Note: The configuration set in this class is applied before the version route is created.**
284284

@@ -305,7 +305,7 @@ To configure a specific API version, you need to create a class that:
305305
- Implements the IWebApiVersionEndpoint interface.
306306
- Is decorated with at least one WebApiVersionEndpointVersion attribute, indicating the version(s) it applies to.
307307

308-
**Note: There can only be one class that configures a specific API version. This is enforced by a Roslyn analyzer.**
308+
**Note: There can only be one class that configures a specific API version. This is enforced by a Roslyn analyzer. Analyzers work per project, so if you have the same *WebApiVersionEndpoint* for a version in more than 1 project, only the first will be registered. The order is dictated by the order of the *[AddWebApiEndpointsFor](#addwebapiendpointsfor-per-project-containing-webapiendpoints)* calls.**
309309

310310
**Note: The configuration set in this class is applied after the version route is created, but before the specific WebApiEndpoint route is created.**
311311

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Futurum.WebApiEndpoint.Micro.Sample.Addition;
2+
3+
/// <summary>
4+
/// This one will not be used, as when it is registered their will already be one registered.
5+
/// </summary>
6+
public class IgnoredGlobalWebApiEndpoint : IGlobalWebApiEndpoint
7+
{
8+
public IEndpointRouteBuilder Configure(IEndpointRouteBuilder builder, WebApiEndpointConfiguration configuration)
9+
{
10+
return builder.MapGroup("api-2");
11+
}
12+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Futurum.WebApiEndpoint.Micro.Sample.Addition;
2+
3+
/// <summary>
4+
/// This one will not be used, as when it is registered their will already be one registered for the version.
5+
/// </summary>
6+
[WebApiVersionEndpointVersion(3.0d)]
7+
public class IgnoredWebApiVersionEndpoint3_0 : IWebApiVersionEndpoint
8+
{
9+
public RouteGroupBuilder Configure(IEndpointRouteBuilder builder, WebApiEndpointConfiguration configuration)
10+
{
11+
return builder.MapGroup("test-api2");
12+
}
13+
}
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using Futurum.WebApiEndpoint.Micro.Sample.Security;
2-
31
namespace Futurum.WebApiEndpoint.Micro.Sample;
42

53
[WebApiVersionEndpointVersion(WebApiEndpointVersions.V3_0.Number)]
@@ -8,7 +6,7 @@ public class WebApiVersionEndpoint3_0a : IWebApiVersionEndpoint
86
{
97
public RouteGroupBuilder Configure(IEndpointRouteBuilder builder, WebApiEndpointConfiguration configuration)
108
{
11-
return builder.MapGroup("test-api").RequireAuthorization(Authorization.Permission.Admin);
9+
return builder.MapGroup("test-api1");
1210
}
1311
}
1412

@@ -17,6 +15,6 @@ public RouteGroupBuilder Configure(IEndpointRouteBuilder builder, WebApiEndpoint
1715
// {
1816
// public RouteGroupBuilder Configure(IEndpointRouteBuilder builder, WebApiEndpointConfiguration configuration)
1917
// {
20-
// return builder.MapGroup("test-api").RequireAuthorization(Authorization.Permission.Admin);
18+
// return builder.MapGroup("test-api");
2119
// }
2220
// }

src/Futurum.WebApiEndpoint.Micro.Generator/GlobalWebApiEndpointRegistrationWriter.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ private static void Write(IndentedStringBuilder codeBuilder, GlobalWebApiEndpoin
1414
if (globalWebApiEndpointDatum != null)
1515
{
1616
codeBuilder.AppendLine(
17-
$"serviceCollection.AddSingleton(typeof(global::Futurum.WebApiEndpoint.Micro.IGlobalWebApiEndpoint), typeof({globalWebApiEndpointDatum.NamespaceName}.{globalWebApiEndpointDatum.ImplementationType}));");
17+
$"serviceCollection.TryAddSingleton(typeof(global::Futurum.WebApiEndpoint.Micro.IGlobalWebApiEndpoint), typeof({globalWebApiEndpointDatum.NamespaceName}.{globalWebApiEndpointDatum.ImplementationType}));");
1818
}
1919
}
2020

@@ -26,6 +26,10 @@ private static string WriteWrapper(string className, string methodName, Action<I
2626
.AppendLine("#nullable enable")
2727
.AppendLine();
2828

29+
codeBuilder
30+
.AppendLine("using Microsoft.Extensions.DependencyInjection.Extensions;")
31+
.AppendLine();
32+
2933
codeBuilder
3034
.AppendLine("namespace Microsoft.Extensions.DependencyInjection")
3135
.AppendLine("{")

src/Futurum.WebApiEndpoint.Micro.Generator/WebApiEndpointApiVersion.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ namespace Futurum.WebApiEndpoint.Micro.Generator;
22

33
public abstract class WebApiEndpointApiVersion
44
{
5-
public class WebApiEndpointNumberApiVersion : WebApiEndpointApiVersion, IEquatable<WebApiEndpointNumberApiVersion>
5+
public class WebApiEndpointNumberApiVersion : WebApiEndpointApiVersion,
6+
IEquatable<WebApiEndpointNumberApiVersion>
67
{
78
public WebApiEndpointNumberApiVersion(double version, string? status = default)
89
{
@@ -35,9 +36,16 @@ public override int GetHashCode()
3536
return (Version.GetHashCode() * 397) ^ (Status != null ? Status.GetHashCode() : 0);
3637
}
3738
}
39+
40+
public static bool operator ==(WebApiEndpointNumberApiVersion? left, WebApiEndpointNumberApiVersion? right) =>
41+
Equals(left, right);
42+
43+
public static bool operator !=(WebApiEndpointNumberApiVersion? left, WebApiEndpointNumberApiVersion? right) =>
44+
!Equals(left, right);
3845
}
3946

40-
public class WebApiEndpointStringApiVersion : WebApiEndpointApiVersion, IEquatable<WebApiEndpointStringApiVersion>
47+
public class WebApiEndpointStringApiVersion : WebApiEndpointApiVersion,
48+
IEquatable<WebApiEndpointStringApiVersion>
4149
{
4250
public WebApiEndpointStringApiVersion(string version)
4351
{
@@ -63,5 +71,11 @@ public override bool Equals(object? obj)
6371

6472
public override int GetHashCode() =>
6573
Version.GetHashCode();
74+
75+
public static bool operator ==(WebApiEndpointStringApiVersion? left, WebApiEndpointStringApiVersion? right) =>
76+
Equals(left, right);
77+
78+
public static bool operator !=(WebApiEndpointStringApiVersion? left, WebApiEndpointStringApiVersion? right) =>
79+
!Equals(left, right);
6680
}
6781
}

src/Futurum.WebApiEndpoint.Micro.Generator/WebApiVersionEndpointRegistrationWriter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ private static void Write(IndentedStringBuilder codeBuilder, IEnumerable<WebApiV
1919

2020
if (webApiEndpointVersionDatum.ApiVersion is WebApiEndpointApiVersion.WebApiEndpointNumberApiVersion webApiEndpointNumberApiVersion)
2121
{
22-
version = $"new global::Futurum.WebApiEndpoint.Micro.WebApiEndpointVersion(new global::Futurum.WebApiEndpoint.Micro.Generator.WebApiEndpointApiVersion.WebApiEndpointNumberApiVersion({webApiEndpointNumberApiVersion.Version}d, {webApiEndpointNumberApiVersion.Status ?? "null"}))";
22+
version = $"((global::Asp.Versioning.ApiVersion)new global::Futurum.WebApiEndpoint.Micro.WebApiEndpointVersion(new global::Futurum.WebApiEndpoint.Micro.Generator.WebApiEndpointApiVersion.WebApiEndpointNumberApiVersion({webApiEndpointNumberApiVersion.Version}d, {webApiEndpointNumberApiVersion.Status ?? "null"}))).ToString()";
2323
}
2424
else if (webApiEndpointVersionDatum.ApiVersion is WebApiEndpointApiVersion.WebApiEndpointStringApiVersion webApiEndpointStringApiVersion)
2525
{
26-
version = $"new global::Futurum.WebApiEndpoint.Micro.WebApiEndpointVersion(new global::Futurum.WebApiEndpoint.Micro.Generator.WebApiEndpointApiVersion.WebApiEndpointStringApiVersion(\"{webApiEndpointStringApiVersion.Version}\"))";
26+
version = $"((global::Asp.Versioning.ApiVersion)new global::Futurum.WebApiEndpoint.Micro.WebApiEndpointVersion(new global::Futurum.WebApiEndpoint.Micro.Generator.WebApiEndpointApiVersion.WebApiEndpointStringApiVersion(\"{webApiEndpointStringApiVersion.Version}\"))).ToString()";
2727
}
2828

29-
codeBuilder.AppendLine($"serviceCollection.AddKeyedSingleton(typeof(global::Futurum.WebApiEndpoint.Micro.IWebApiVersionEndpoint), {version}, typeof({webApiVersionEndpointDatum.NamespaceName}.{webApiVersionEndpointDatum.ImplementationType}));");
29+
codeBuilder.AppendLine($"global::Futurum.WebApiEndpoint.Micro.ServiceCollectionUniqueKeyExtensions.TryAddEquatableKeyedSingleton(serviceCollection, typeof(global::Futurum.WebApiEndpoint.Micro.IWebApiVersionEndpoint), {version}, typeof({webApiVersionEndpointDatum.NamespaceName}.{webApiVersionEndpointDatum.ImplementationType}));");
3030
}
3131
}
3232
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Futurum.WebApiEndpoint.Micro;
2+
3+
public static class ServiceCollectionUniqueKeyExtensions
4+
{
5+
public static void TryAddEquatableKeyedSingleton(this IServiceCollection collection, Type service, object? serviceKey, Type implementationType)
6+
{
7+
var descriptor = ServiceDescriptor.KeyedSingleton(service, serviceKey, implementationType);
8+
collection.TryAddEquatableKeyed(descriptor);
9+
}
10+
11+
private static void TryAddEquatableKeyed(this IServiceCollection collection, ServiceDescriptor descriptor)
12+
{
13+
var count = collection.Count;
14+
for (var index = 0; index < count; ++index)
15+
{
16+
var serviceKey = collection[index].ServiceKey;
17+
if (serviceKey != null && collection[index].ServiceType == descriptor.ServiceType && serviceKey.Equals(descriptor.ServiceKey))
18+
return;
19+
}
20+
collection.Add(descriptor);
21+
}
22+
}

src/Futurum.WebApiEndpoint.Micro/WebApiEndpoint.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ private static IEndpointRouteBuilder ConfigureWithGlobalWebApiEndpoint(IEndpoint
6868
private static IEndpointRouteBuilder ConfigureWithWebApiVersionEndpoint(IEndpointRouteBuilder app, WebApiEndpointConfiguration configuration, WebApiEndpointVersion webApiEndpointVersion,
6969
IEndpointRouteBuilder workingEndpointRouteBuilder)
7070
{
71-
var webApiVersionEndpoint = app.ServiceProvider.GetKeyedService<IWebApiVersionEndpoint>(webApiEndpointVersion);
71+
var serviceKey = ((ApiVersion)webApiEndpointVersion).ToString();
72+
var webApiVersionEndpoint = app.ServiceProvider.GetKeyedService<IWebApiVersionEndpoint>(serviceKey);
7273

7374
if (webApiVersionEndpoint == null) return workingEndpointRouteBuilder;
7475

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Net;
2+
3+
using FluentAssertions;
4+
5+
using Microsoft.AspNetCore.Mvc.Testing;
6+
7+
namespace Futurum.WebApiEndpoint.Micro.EndToEndTests;
8+
9+
[Collection("Sequential")]
10+
public class GlobalWebApiEndpointTests
11+
{
12+
[Fact]
13+
public async Task check_v1_works()
14+
{
15+
var httpClient = CreateClient();
16+
17+
var name = Guid.NewGuid().ToString();
18+
19+
var httpResponseMessage = await httpClient.GetAsync($"/api/v1/greeting/hello?name={name}");
20+
21+
httpResponseMessage.EnsureSuccessStatusCode();
22+
var response = await httpResponseMessage.Content.ReadAsStringAsync();
23+
24+
response.Should().Be($"\"Hello {name}\"");
25+
}
26+
27+
[Fact]
28+
public async Task check_v2_does_not_works()
29+
{
30+
var httpClient = CreateClient();
31+
32+
var name = Guid.NewGuid().ToString();
33+
34+
var httpResponseMessage = await httpClient.GetAsync($"/api-2/v1/greeting/hello?name={name}");
35+
36+
httpResponseMessage.StatusCode.Should().Be(HttpStatusCode.NotFound);
37+
}
38+
39+
private static HttpClient CreateClient() =>
40+
new WebApplicationFactory<Sample.Program>()
41+
.CreateClient();
42+
}

0 commit comments

Comments
 (0)