Skip to content

Commit 06af900

Browse files
authored
Merge pull request #106 from cnblogs/allow-head-for-query
feat: allow head method for MapQuery()
2 parents 09959e4 + 69db31d commit 06af900

File tree

4 files changed

+65
-7
lines changed

4 files changed

+65
-7
lines changed

src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ public static class CqrsRouteMapper
1818

1919
private static readonly List<Type> CommandTypes = new() { typeof(ICommand<>), typeof(ICommand<,>) };
2020

21+
private static readonly string[] GetAndHeadMethods = { "GET", "HEAD" };
22+
2123
/// <summary>
2224
/// Map a query API, using GET method. <typeparamref name="T"/> would been constructed from route and query string.
2325
/// </summary>
2426
/// <param name="app"><see cref="IApplicationBuilder"/></param>
2527
/// <param name="route">The route template for API.</param>
2628
/// <param name="mapNullableRouteParameters">Multiple routes should be mapped when for nullable route parameters.</param>
2729
/// <param name="nullRouteParameterPattern">Replace route parameter with given string to represent null.</param>
30+
/// <param name="enableHead">Map HEAD method for the same routes.</param>
2831
/// <typeparam name="T">The type of the query.</typeparam>
2932
/// <returns></returns>
3033
/// <example>
@@ -44,13 +47,15 @@ public static IEndpointConventionBuilder MapQuery<T>(
4447
this IEndpointRouteBuilder app,
4548
[StringSyntax("Route")] string route,
4649
MapNullableRouteParameter mapNullableRouteParameters = MapNullableRouteParameter.Disable,
47-
string nullRouteParameterPattern = "-")
50+
string nullRouteParameterPattern = "-",
51+
bool enableHead = false)
4852
{
4953
return app.MapQuery(
5054
route,
5155
([AsParameters] T query) => query,
5256
mapNullableRouteParameters,
53-
nullRouteParameterPattern);
57+
nullRouteParameterPattern,
58+
enableHead);
5459
}
5560

5661
/// <summary>
@@ -61,6 +66,7 @@ public static IEndpointConventionBuilder MapQuery<T>(
6166
/// <param name="handler">The delegate that returns a <see cref="IQuery{TView}"/> instance.</param>
6267
/// <param name="mapNullableRouteParameters">Multiple routes should be mapped when for nullable route parameters.</param>
6368
/// <param name="nullRouteParameterPattern">Replace route parameter with given string to represent null.</param>
69+
/// <param name="enableHead">Allow HEAD for the same routes.</param>
6470
/// <returns></returns>
6571
/// <example>
6672
/// The following code:
@@ -80,7 +86,8 @@ public static IEndpointConventionBuilder MapQuery(
8086
[StringSyntax("Route")] string route,
8187
Delegate handler,
8288
MapNullableRouteParameter mapNullableRouteParameters = MapNullableRouteParameter.Disable,
83-
string nullRouteParameterPattern = "-")
89+
string nullRouteParameterPattern = "-",
90+
bool enableHead = false)
8491
{
8592
var isQuery = handler.Method.ReturnType.GetInterfaces().Where(x => x.IsGenericType)
8693
.Any(x => QueryTypes.Contains(x.GetGenericTypeDefinition()));
@@ -92,7 +99,7 @@ public static IEndpointConventionBuilder MapQuery(
9299

93100
if (mapNullableRouteParameters is MapNullableRouteParameter.Disable)
94101
{
95-
return app.MapGet(route, handler).AddEndpointFilter<QueryEndpointHandler>();
102+
return MapRoutes(route);
96103
}
97104

98105
if (string.IsNullOrWhiteSpace(nullRouteParameterPattern))
@@ -125,10 +132,16 @@ public static IEndpointConventionBuilder MapQuery(
125132
var regex = new Regex("{" + x.Name + "[^}]*?}", RegexOptions.IgnoreCase);
126133
return regex.Replace(r, nullRouteParameterPattern);
127134
});
128-
app.MapGet(newRoute, handler).AddEndpointFilter<QueryEndpointHandler>();
135+
MapRoutes(newRoute);
129136
}
130137

131-
return app.MapGet(route, handler).AddEndpointFilter<QueryEndpointHandler>();
138+
return MapRoutes(route);
139+
140+
IEndpointConventionBuilder MapRoutes(string r)
141+
{
142+
var endpoint = enableHead ? app.MapMethods(r, GetAndHeadMethods, handler) : app.MapGet(r, handler);
143+
return endpoint.AddEndpointFilter<QueryEndpointHandler>();
144+
}
132145
}
133146

134147
/// <summary>

src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,29 @@ public async Task<CommandResponse<TResponse, ServiceAgentError>> PutCommandAsync
146146
}
147147
}
148148

149+
/// <summary>
150+
/// Query item with HEAD method.
151+
/// </summary>
152+
/// <param name="url">The route of the API.</param>
153+
/// <returns>True if status code is 2xx, False if status code is 404.</returns>
154+
public async Task<bool> HasItemAsync(string url)
155+
{
156+
var request = new HttpRequestMessage(HttpMethod.Head, url);
157+
var response = await HttpClient.SendAsync(request);
158+
if (response.IsSuccessStatusCode)
159+
{
160+
return true;
161+
}
162+
163+
if (response.StatusCode == HttpStatusCode.NotFound)
164+
{
165+
return false;
166+
}
167+
168+
response.EnsureSuccessStatusCode(); // throw for other status code
169+
return false;
170+
}
171+
149172
/// <summary>
150173
/// Batch get items with GET method.
151174
/// </summary>

test/Cnblogs.Architecture.IntegrationTestProject/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
var apis = app.NewVersionedApi();
3737
var v1 = apis.MapGroup("/api/v{version:apiVersion}").HasApiVersion(1);
38-
v1.MapQuery<GetStringQuery>("apps/{appId}/strings/{stringId:int}/value", MapNullableRouteParameter.Enable);
38+
v1.MapQuery<GetStringQuery>("apps/{appId}/strings/{stringId:int}/value", MapNullableRouteParameter.Enable, enableHead: true);
3939
v1.MapQuery<GetStringQuery>("strings/{id:int}");
4040
v1.MapQuery<ListStringsQuery>("strings");
4141
v1.MapCommand("strings", (CreatePayload payload) => new CreateCommand(payload.NeedError));

test/Cnblogs.Architecture.IntegrationTests/CqrsRouteMapperTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,26 @@ await builder.CreateClient().GetAsync("/api/v1/apps/someApp/strings/1/value")
9696
// Assert
9797
responses.Should().Match(x => x.All(y => y.IsSuccessStatusCode));
9898
}
99+
100+
[Fact]
101+
public async Task GetItem_MapHeadAndGet_SuccessAsync()
102+
{
103+
// Arrange
104+
var builder = new WebApplicationFactory<Program>();
105+
106+
// Act
107+
var uris = new[]
108+
{
109+
"/api/v1/apps/-/strings/-/value", "/api/v1/apps/-/strings/1/value",
110+
"/api/v1/apps/someApp/strings/-/value", "/api/v1/apps/someApp/strings/1/value"
111+
}.Select(x => new HttpRequestMessage(HttpMethod.Head, x));
112+
var responses = new List<HttpResponseMessage>();
113+
foreach (var uri in uris)
114+
{
115+
responses.Add(await builder.CreateClient().SendAsync(uri, HttpCompletionOption.ResponseHeadersRead));
116+
}
117+
118+
// Assert
119+
responses.Should().Match(x => x.All(y => y.IsSuccessStatusCode));
120+
}
99121
}

0 commit comments

Comments
 (0)