Hoist method attributes from the request delegate as metadata. (#6911)

- This should allow a more declarative approach to declaring endpoint metadata using the default methods.
- Attributes are applied first and can be overridden imperatively
This commit is contained in:
David Fowler 2019-01-28 20:38:49 +00:00 committed by GitHub
parent 14d8e33a93
commit 9f71e60283
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 9 deletions

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
@ -21,7 +22,6 @@ namespace Microsoft.AspNetCore.Builder
private static readonly string[] PutVerb = new[] { "PUT" };
private static readonly string[] DeleteVerb = new[] { "DELETE" };
#region MapVerbs
/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests
/// for the specified pattern.
@ -227,9 +227,7 @@ namespace Microsoft.AspNetCore.Builder
return Map(builder, pattern, displayName ?? $"{pattern} HTTP: {string.Join(", ", httpMethods)}", requestDelegate, metadata: resolvedMetadata.ToArray());
}
#endregion
#region Map
/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified pattern.
@ -323,8 +321,17 @@ namespace Microsoft.AspNetCore.Builder
var routeEndpointBuilder = new RouteEndpointBuilder(
requestDelegate,
pattern,
defaultOrder);
routeEndpointBuilder.DisplayName = displayName;
defaultOrder)
{
DisplayName = displayName
};
// Add delegate attributes as metadata
foreach (var attribute in requestDelegate.Method.GetCustomAttributes())
{
routeEndpointBuilder.Metadata.Add(attribute);
}
if (metadata != null)
{
foreach (var item in metadata)
@ -343,6 +350,5 @@ namespace Microsoft.AspNetCore.Builder
return modelEndpointDataSource.AddEndpointBuilder(routeEndpointBuilder);
}
#endregion
}
}

View File

@ -2,11 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
@ -97,5 +95,59 @@ namespace Microsoft.AspNetCore.Builder
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata));
}
[Fact]
public void MapEndpoint_AttributesCollectedAsMetadata()
{
// Arrange
var builder = new DefaultEndpointRouteBuilder();
// Act
var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", Handle);
// Assert
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
Assert.Equal(2, endpointBuilder1.Metadata.Count);
Assert.IsType<Attribute1>(endpointBuilder1.Metadata[0]);
Assert.IsType<Attribute2>(endpointBuilder1.Metadata[1]);
}
[Fact]
public void MapEndpoint_ExplicitMetadataAddedAfterAttributeMetadata()
{
// Arrange
var builder = new DefaultEndpointRouteBuilder();
// Act
var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", Handle, new Metadata());
// Assert
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
Assert.Equal(3, endpointBuilder1.Metadata.Count);
Assert.IsType<Attribute1>(endpointBuilder1.Metadata[0]);
Assert.IsType<Attribute2>(endpointBuilder1.Metadata[1]);
Assert.IsType<Metadata>(endpointBuilder1.Metadata[2]);
}
[Attribute1]
[Attribute2]
private static Task Handle(HttpContext context) => Task.CompletedTask;
private class Attribute1 : Attribute
{
}
private class Attribute2 : Attribute
{
}
private class Metadata
{
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@ -46,6 +47,10 @@ namespace RoutingSandbox
foreach (var endpoint in dataSource.Endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
{
sb.AppendLine($"- {endpoint.RoutePattern.RawText}");
foreach (var metadata in endpoint.Metadata)
{
sb.AppendLine(" " + metadata);
}
}
var response = httpContext.Response;
@ -79,6 +84,10 @@ namespace RoutingSandbox
return Task.CompletedTask;
});
builder.MapGet("/attributes", HandlerWithAttributes);
builder.Map("/getwithattributes", Handler);
builder.MapFramework(frameworkBuilder =>
{
frameworkBuilder.AddPattern("/transform/{hub:slugify=TestHub}/{method:slugify=TestMethod}");
@ -92,5 +101,29 @@ namespace RoutingSandbox
app.UseStaticFiles();
}
[Authorize]
private Task HandlerWithAttributes(HttpContext context)
{
return context.Response.WriteAsync("I have ann authorize attribute");
}
[HttpGet]
private Task Handler(HttpContext context)
{
return context.Response.WriteAsync("I have a method metadata attribute");
}
private class AuthorizeAttribute : Attribute
{
}
private class HttpGetAttribute : Attribute, IHttpMethodMetadata
{
public bool AcceptCorsPreflight => false;
public IReadOnlyList<string> HttpMethods { get; } = new List<string> { "GET" };
}
}
}