Skip to content

Commit 1780cd4

Browse files
authored
Added MapFallback overloads that take Delegate (#34531)
- These new APIs are enlightened with the new RequestDelegateFactory and get the binding and results features that were added in .NET 6. - Added some basic tests
1 parent df48473 commit 1780cd4

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,5 +205,86 @@ public static MinimalActionEndpointConventionBuilder Map(
205205

206206
return new MinimalActionEndpointConventionBuilder(dataSource.AddEndpointBuilder(builder));
207207
}
208+
209+
/// <summary>
210+
/// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
211+
/// requests for non-file-names with the lowest possible priority.
212+
/// </summary>
213+
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
214+
/// <param name="action">The delegate executed when the endpoint is matched.</param>
215+
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
216+
/// <remarks>
217+
/// <para>
218+
/// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> is intended to handle cases where URL path of
219+
/// the request does not contain a file name, and no other endpoint has matched. This is convenient for routing
220+
/// requests for dynamic content to a SPA framework, while also allowing requests for non-existent files to
221+
/// result in an HTTP 404.
222+
/// </para>
223+
/// <para>
224+
/// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> registers an endpoint using the pattern
225+
/// <c>{*path:nonfile}</c>. The order of the registered endpoint will be <c>int.MaxValue</c>.
226+
/// </para>
227+
/// </remarks>
228+
public static MinimalActionEndpointConventionBuilder MapFallback(this IEndpointRouteBuilder endpoints, Delegate action)
229+
{
230+
if (endpoints == null)
231+
{
232+
throw new ArgumentNullException(nameof(endpoints));
233+
}
234+
235+
if (action == null)
236+
{
237+
throw new ArgumentNullException(nameof(action));
238+
}
239+
240+
return endpoints.MapFallback("{*path:nonfile}", action);
241+
}
242+
243+
/// <summary>
244+
/// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
245+
/// the provided pattern with the lowest possible priority.
246+
/// </summary>
247+
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
248+
/// <param name="pattern">The route pattern.</param>
249+
/// <param name="action">The delegate executed when the endpoint is matched.</param>
250+
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
251+
/// <remarks>
252+
/// <para>
253+
/// <see cref="MapFallback(IEndpointRouteBuilder, string, Delegate)"/> is intended to handle cases where no
254+
/// other endpoint has matched. This is convenient for routing requests to a SPA framework.
255+
/// </para>
256+
/// <para>
257+
/// The order of the registered endpoint will be <c>int.MaxValue</c>.
258+
/// </para>
259+
/// <para>
260+
/// This overload will use the provided <paramref name="pattern"/> verbatim. Use the <c>:nonfile</c> route constraint
261+
/// to exclude requests for static files.
262+
/// </para>
263+
/// </remarks>
264+
public static MinimalActionEndpointConventionBuilder MapFallback(
265+
this IEndpointRouteBuilder endpoints,
266+
string pattern,
267+
Delegate action)
268+
{
269+
if (endpoints == null)
270+
{
271+
throw new ArgumentNullException(nameof(endpoints));
272+
}
273+
274+
if (pattern == null)
275+
{
276+
throw new ArgumentNullException(nameof(pattern));
277+
}
278+
279+
if (action == null)
280+
{
281+
throw new ArgumentNullException(nameof(action));
282+
}
283+
284+
var conventionBuilder = endpoints.Map(pattern, action);
285+
conventionBuilder.WithDisplayName("Fallback " + pattern);
286+
conventionBuilder.Add(b => ((RouteEndpointBuilder)b).Order = int.MaxValue);
287+
return conventionBuilder;
288+
}
208289
}
209290
}

src/Http/Routing/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Microsoft.AspNetCore.Routing.RouteNameMetadata.RouteNameMetadata(string? routeNa
1717
static Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, Microsoft.AspNetCore.Routing.Patterns.RoutePattern! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder!
1818
static Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder!
1919
static Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.MapDelete(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder!
20+
static Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.MapFallback(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder!
21+
static Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.MapFallback(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder!
2022
static Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.MapGet(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder!
2123
static Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.MapMethods(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Collections.Generic.IEnumerable<string!>! httpMethods, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder!
2224
static Microsoft.AspNetCore.Builder.MinimalActionEndpointRouteBuilderExtensions.MapPost(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder!

src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,43 @@ public void MapDelete_BuildsEndpointWithCorrectMethod()
183183
Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
184184
}
185185

186+
[Fact]
187+
public void MapFallback_BuildsEndpointWithLowestRouteOrder()
188+
{
189+
var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvdier()));
190+
_ = builder.MapFallback("/", () => { });
191+
192+
var dataSource = GetBuilderEndpointDataSource(builder);
193+
// Trigger Endpoint build by calling getter.
194+
var endpoint = Assert.Single(dataSource.Endpoints);
195+
196+
var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
197+
Assert.Equal("Fallback /", routeEndpointBuilder.DisplayName);
198+
Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
199+
Assert.Equal(int.MaxValue, routeEndpointBuilder.Order);
200+
}
201+
202+
[Fact]
203+
public void MapFallbackWithoutPath_BuildsEndpointWithLowestRouteOrder()
204+
{
205+
var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvdier()));
206+
_ = builder.MapFallback(() => { });
207+
208+
var dataSource = GetBuilderEndpointDataSource(builder);
209+
// Trigger Endpoint build by calling getter.
210+
var endpoint = Assert.Single(dataSource.Endpoints);
211+
212+
var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
213+
Assert.Equal("Fallback {*path:nonfile}", routeEndpointBuilder.DisplayName);
214+
Assert.Equal("{*path:nonfile}", routeEndpointBuilder.RoutePattern.RawText);
215+
Assert.Single(routeEndpointBuilder.RoutePattern.Parameters);
216+
Assert.True(routeEndpointBuilder.RoutePattern.Parameters[0].IsCatchAll);
217+
Assert.Equal(int.MaxValue, routeEndpointBuilder.Order);
218+
}
219+
186220
class FromRoute : Attribute, IFromRouteMetadata
187221
{
188-
public string? Name { get; set; }
222+
public string? Name { get; set; }
189223
}
190224

191225
private class HttpMethodAttribute : Attribute, IHttpMethodMetadata

0 commit comments

Comments
 (0)