Skip to content

Add new "MapAction" overloads #30556

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
4 commits merged into from
Mar 2, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public sealed class MapActionEndpointConventionBuilder : IEndpointConventionBuil
{
private readonly List<IEndpointConventionBuilder> _endpointConventionBuilders;

internal MapActionEndpointConventionBuilder(IEndpointConventionBuilder endpointConventionBuilder)
{
_endpointConventionBuilders = new List<IEndpointConventionBuilder>() { endpointConventionBuilder };
}

internal MapActionEndpointConventionBuilder(List<IEndpointConventionBuilder> endpointConventionBuilders)
{
_endpointConventionBuilders = endpointConventionBuilders;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Reflection;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Patterns;

namespace Microsoft.AspNetCore.Builder
{
Expand Down Expand Up @@ -76,5 +77,188 @@ public static MapActionEndpointConventionBuilder MapAction(

return new MapActionEndpointConventionBuilder(conventionBuilders);
}

/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="action">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static MapActionEndpointConventionBuilder MapGet(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't there already Map* extension methods on route builder? Do these not colide because of Delegate parameter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are overloads, but they should only be used for delegates that aren't RequestDelegates. This was also mentioned in dotnet/csharplang#4451 (comment).

this IEndpointRouteBuilder endpoints,
string pattern,
Delegate action)
{
return WrapConventionBuilder(
endpoints.MapGet(pattern, MapActionExpressionTreeBuilder.BuildRequestDelegate(action)),
pattern,
action);
}

/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="action">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapPost(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate action)
{
return WrapConventionBuilder(
endpoints.MapPost(pattern, MapActionExpressionTreeBuilder.BuildRequestDelegate(action)),
pattern,
action);
}

/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="action">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that canaction be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapPut(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate action)
{
return WrapConventionBuilder(
endpoints.MapPut(pattern, MapActionExpressionTreeBuilder.BuildRequestDelegate(action)),
pattern,
action);
}

/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="action">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapDelete(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate action)
{
return WrapConventionBuilder(
endpoints.MapDelete(pattern, MapActionExpressionTreeBuilder.BuildRequestDelegate(action)),
pattern,
action);
}

/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified HTTP methods and pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="action">The delegate executed when the endpoint is matched.</param>
/// <param name="httpMethods">HTTP methods that the endpoint will match.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapMethods(
this IEndpointRouteBuilder endpoints,
string pattern,
IEnumerable<string> httpMethods,
Delegate action)
{
return WrapConventionBuilder(
endpoints.MapMethods(pattern, httpMethods, MapActionExpressionTreeBuilder.BuildRequestDelegate(action)),
pattern,
action);
}

/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="action">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder Map(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate action)
{
return WrapConventionBuilder(
endpoints.Map(pattern, MapActionExpressionTreeBuilder.BuildRequestDelegate(action)),
pattern,
action);
}

/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="action">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder Map(
this IEndpointRouteBuilder endpoints,
RoutePattern pattern,
Delegate action)
{
return WrapConventionBuilder(
endpoints.Map(pattern, MapActionExpressionTreeBuilder.BuildRequestDelegate(action)),
pattern.RawText,
action);
}

private static MapActionEndpointConventionBuilder WrapConventionBuilder(
IEndpointConventionBuilder endpointConventionBuilder,
string? pattern,
Delegate action)
{
var attributes = action.Method.GetCustomAttributes();
string? routeName = null;
int? routeOrder = null;

foreach (var attribute in attributes)
{
if (attribute is IRoutePatternMetadata patternMetadata && patternMetadata.RoutePattern is { })
{
throw new InvalidOperationException($"'{attribute.GetType()}' implements {nameof(IRoutePatternMetadata)} which is not supported my this method.");
}
if (attribute is IHttpMethodMetadata methodMetadata && methodMetadata.HttpMethods.Any())
{
throw new InvalidOperationException($"'{attribute.GetType()}' implements {nameof(IHttpMethodMetadata)} which is not supported my this method.");
}

if (attribute is IRouteNameMetadata nameMetadata && nameMetadata.RouteName is string name)
{
routeName = name;
}
if (attribute is IRouteOrderMetadata orderMetadata && orderMetadata.RouteOrder is int order)
{
routeOrder = order;
}
}

const int defaultOrder = 0;

endpointConventionBuilder.Add(endpointBuilder =>
{
foreach (var attribute in attributes)
{
endpointBuilder.Metadata.Add(attribute);
}

endpointBuilder.DisplayName = routeName ?? pattern;

((RouteEndpointBuilder)endpointBuilder).Order = routeOrder ?? defaultOrder;
});

return new MapActionEndpointConventionBuilder(endpointConventionBuilder);
}

}
}
7 changes: 7 additions & 0 deletions src/Http/Routing/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,11 @@ Microsoft.AspNetCore.Routing.IRoutePatternMetadata
Microsoft.AspNetCore.Routing.IRoutePatternMetadata.RoutePattern.get -> string?
Microsoft.AspNetCore.Routing.RouteNameMetadata.RouteName.get -> string?
Microsoft.AspNetCore.Routing.RouteNameMetadata.RouteNameMetadata(string? routeName) -> void
static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, Microsoft.AspNetCore.Routing.Patterns.RoutePattern! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapAction(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder!
static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapDelete(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapGet(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder!
static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapMethods(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Collections.Generic.IEnumerable<string!>! httpMethods, System.Delegate! action) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapPost(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapPut(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
41 changes: 41 additions & 0 deletions src/Http/Routing/test/FunctionalTests/MapActionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,47 @@ public async Task MapAction_FromBodyWorksWithJsonPayload()
Assert.Equal(42, echoedTodo?.Id);
}

[Fact]
public async Task MapPost_FromBodyWorksWithJsonPayload()
{
Todo EchoTodo([FromRoute] int id, [FromBody] Todo todo) => todo with { Id = id };

using var host = new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder
.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(b => b.MapPost("/EchoTodo/{id}", (Func<int, Todo, Todo>)EchoTodo));
})
.UseTestServer();
})
.ConfigureServices(services =>
{
services.AddRouting();
})
.Build();

using var server = host.GetTestServer();
await host.StartAsync();
var client = server.CreateClient();

var todo = new Todo
{
Name = "Write tests!"
};

var response = await client.PostAsJsonAsync("/EchoTodo/42", todo);
response.EnsureSuccessStatusCode();

var echoedTodo = await response.Content.ReadFromJsonAsync<Todo>();

Assert.NotNull(echoedTodo);
Assert.Equal(todo.Name, echoedTodo?.Name);
Assert.Equal(42, echoedTodo?.Id);
}

private record Todo
{
public int Id { get; set; }
Expand Down
Loading