diff --git a/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs b/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs index 2c5fa5a7d9..2a14f9d36d 100644 --- a/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs +++ b/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text; -using Microsoft.AspNetCore.Internal; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Patterns; diff --git a/samples/RoutingSample.Web/HelloExtension/EndpointDataSourceBuilderExtensions.cs b/samples/RoutingSample.Web/HelloExtension/EndpointDataSourceBuilderExtensions.cs new file mode 100644 index 0000000000..6c2a4639a5 --- /dev/null +++ b/samples/RoutingSample.Web/HelloExtension/EndpointDataSourceBuilderExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// 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.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Builder +{ + public static class EndpointDataSourceBuilderExtensions + { + public static EndpointBuilder MapHello(this EndpointDataSourceBuilder builder, string template, string greeter) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + var pipeline = builder.CreateApplicationBuilder() + .UseHello(greeter) + .Build(); + + return builder.MapEndpoint( + (next) => pipeline, + template, + "Hello"); + } + } +} diff --git a/samples/RoutingSample.Web/HelloExtension/HelloAppBuilderExtensions.cs b/samples/RoutingSample.Web/HelloExtension/HelloAppBuilderExtensions.cs new file mode 100644 index 0000000000..e1a587d5ca --- /dev/null +++ b/samples/RoutingSample.Web/HelloExtension/HelloAppBuilderExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Options; +using RoutingSample.Web.HelloExtension; + +namespace Microsoft.AspNetCore.Builder +{ + public static class HelloAppBuilderExtensions + { + public static IApplicationBuilder UseHello(this IApplicationBuilder app, string greeter) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + return app.UseMiddleware(Options.Create(new HelloOptions + { + Greeter = greeter + })); + } + } +} diff --git a/samples/RoutingSample.Web/HelloExtension/HelloMiddleware.cs b/samples/RoutingSample.Web/HelloExtension/HelloMiddleware.cs new file mode 100644 index 0000000000..6a4587e50c --- /dev/null +++ b/samples/RoutingSample.Web/HelloExtension/HelloMiddleware.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace RoutingSample.Web.HelloExtension +{ + public class HelloMiddleware + { + private readonly RequestDelegate _next; + private readonly HelloOptions _helloOptions; + private readonly byte[] _helloPayload; + + public HelloMiddleware(RequestDelegate next, IOptions helloOptions) + { + _next = next; + _helloOptions = helloOptions.Value; + + var payload = new List(); + payload.AddRange(Encoding.UTF8.GetBytes("Hello")); + if (!string.IsNullOrEmpty(_helloOptions.Greeter)) + { + payload.Add((byte)' '); + payload.AddRange(Encoding.UTF8.GetBytes(_helloOptions.Greeter)); + } + _helloPayload = payload.ToArray(); + } + + public Task InvokeAsync(HttpContext context) + { + var response = context.Response; + var payloadLength = _helloPayload.Length; + response.StatusCode = 200; + response.ContentType = "text/plain"; + response.ContentLength = payloadLength; + return response.Body.WriteAsync(_helloPayload, 0, payloadLength); + } + } +} diff --git a/samples/RoutingSample.Web/HelloExtension/HelloOptions.cs b/samples/RoutingSample.Web/HelloExtension/HelloOptions.cs new file mode 100644 index 0000000000..49f8b5c5df --- /dev/null +++ b/samples/RoutingSample.Web/HelloExtension/HelloOptions.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace RoutingSample.Web.HelloExtension +{ + public class HelloOptions + { + public string Greeter { get; set; } + } +} diff --git a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs index 77b41e87a8..c909990ba6 100644 --- a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs +++ b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs @@ -3,20 +3,16 @@ using System; using System.Text; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Internal; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Matching; -using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; namespace RoutingSample.Web { public class UseEndpointRoutingStartup { private static readonly byte[] _homePayload = Encoding.UTF8.GetBytes("Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext"); - private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!"); + private static readonly byte[] _plainTextPayload = Encoding.UTF8.GetBytes("Plain text!"); public void ConfigureServices(IServiceCollection services) { @@ -26,65 +22,59 @@ namespace RoutingSample.Web { options.ConstraintMap.Add("endsWith", typeof(EndsWithStringMatchProcessor)); }); - - var endpointDataSource = new DefaultEndpointDataSource(new[] - { - new MatcherEndpoint((next) => (httpContext) => - { - var response = httpContext.Response; - var payloadLength = _homePayload.Length; - response.StatusCode = 200; - response.ContentType = "text/plain"; - response.ContentLength = payloadLength; - return response.Body.WriteAsync(_homePayload, 0, payloadLength); - }, - RoutePatternFactory.Parse("/"), - 0, - EndpointMetadataCollection.Empty, - "Home"), - new MatcherEndpoint((next) => (httpContext) => - { - var response = httpContext.Response; - var payloadLength = _helloWorldPayload.Length; - response.StatusCode = 200; - response.ContentType = "text/plain"; - response.ContentLength = payloadLength; - return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength); - }, - RoutePatternFactory.Parse("/plaintext"), - 0, - EndpointMetadataCollection.Empty, - "Plaintext"), - new MatcherEndpoint((next) => (httpContext) => - { - var response = httpContext.Response; - response.StatusCode = 200; - response.ContentType = "text/plain"; - return response.WriteAsync("WithConstraints"); - }, - RoutePatternFactory.Parse("/withconstraints/{id:endsWith(_001)}"), - 0, - EndpointMetadataCollection.Empty, - "withconstraints"), - new MatcherEndpoint((next) => (httpContext) => - { - var response = httpContext.Response; - response.StatusCode = 200; - response.ContentType = "text/plain"; - return response.WriteAsync("withoptionalconstraints"); - }, - RoutePatternFactory.Parse("/withoptionalconstraints/{id:endsWith(_001)?}"), - 0, - EndpointMetadataCollection.Empty, - "withoptionalconstraints"), - }); - - services.TryAddEnumerable(ServiceDescriptor.Singleton(endpointDataSource)); } public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app) { - app.UseEndpointRouting(); + app.UseEndpointRouting(builder => + { + builder.MapHello("/helloworld", "World"); + + builder.MapEndpoint( + (next) => (httpContext) => + { + var response = httpContext.Response; + var payloadLength = _homePayload.Length; + response.StatusCode = 200; + response.ContentType = "text/plain"; + response.ContentLength = payloadLength; + return response.Body.WriteAsync(_homePayload, 0, payloadLength); + }, + "/", + "Home"); + builder.MapEndpoint( + (next) => (httpContext) => + { + var response = httpContext.Response; + var payloadLength = _plainTextPayload.Length; + response.StatusCode = 200; + response.ContentType = "text/plain"; + response.ContentLength = payloadLength; + return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength); + }, + "/plaintext", + "Plaintext"); + builder.MapEndpoint( + (next) => (httpContext) => + { + var response = httpContext.Response; + response.StatusCode = 200; + response.ContentType = "text/plain"; + return response.WriteAsync("WithConstraints"); + }, + "/withconstraints/{id:endsWith(_001)}", + "withconstraints"); + builder.MapEndpoint( + (next) => (httpContext) => + { + var response = httpContext.Response; + response.StatusCode = 200; + response.ContentType = "text/plain"; + return response.WriteAsync("withoptionalconstraints"); + }, + "/withoptionalconstraints/{id:endsWith(_001)?}", + "withoptionalconstraints"); + }); // Imagine some more stuff here... diff --git a/src/Microsoft.AspNetCore.Routing/Internal/EndpointRoutingApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs similarity index 79% rename from src/Microsoft.AspNetCore.Routing/Internal/EndpointRoutingApplicationBuilderExtensions.cs rename to src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs index 281607cfba..f7c14801f3 100644 --- a/src/Microsoft.AspNetCore.Routing/Internal/EndpointRoutingApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs @@ -7,16 +7,28 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.AspNetCore.Internal +namespace Microsoft.AspNetCore.Builder { public static class EndpointRoutingApplicationBuilderExtensions { private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered"; public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder) + { + return builder.UseEndpointRouting(null); + } + + public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder, Action configure) { VerifyRoutingIsRegistered(builder); + if (configure != null) + { + var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)builder.ApplicationServices.GetRequiredService(); + dataSourceBuilder.ApplicationBuilder = builder; + configure(dataSourceBuilder); + } + builder.Properties[EndpointRoutingRegisteredKey] = true; return builder.UseMiddleware(); @@ -50,4 +62,4 @@ namespace Microsoft.AspNetCore.Internal } } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/Builder/MapEndpointEndpointDataSourceBuilderExtensions.cs b/src/Microsoft.AspNetCore.Routing/Builder/MapEndpointEndpointDataSourceBuilderExtensions.cs new file mode 100644 index 0000000000..2a47a1b87b --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/Builder/MapEndpointEndpointDataSourceBuilderExtensions.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Builder +{ + public static class MapEndpointEndpointDataSourceBuilderExtensions + { + public static MatcherEndpointBuilder MapEndpoint( + this EndpointDataSourceBuilder builder, + Func invoker, + string pattern, + string displayName) + { + return MapEndpoint(builder, invoker, pattern, displayName, metadata: null); + } + + public static MatcherEndpointBuilder MapEndpoint( + this EndpointDataSourceBuilder builder, + Func invoker, + RoutePattern pattern, + string displayName) + { + return MapEndpoint(builder, invoker, pattern, displayName, metadata: null); + } + + public static MatcherEndpointBuilder MapEndpoint( + this EndpointDataSourceBuilder builder, + Func invoker, + string pattern, + string displayName, + IList metadata) + { + return MapEndpoint(builder, invoker, RoutePatternFactory.Parse(pattern), displayName, metadata); + } + + public static MatcherEndpointBuilder MapEndpoint( + this EndpointDataSourceBuilder builder, + Func invoker, + RoutePattern pattern, + string displayName, + IList metadata) + { + const int defaultOrder = 0; + + var endpointBuilder = new MatcherEndpointBuilder( + invoker, + pattern, + defaultOrder); + endpointBuilder.DisplayName = displayName; + if (metadata != null) + { + foreach (var item in metadata) + { + endpointBuilder.Metadata.Add(item); + } + } + + builder.Endpoints.Add(endpointBuilder); + return endpointBuilder; + } + } +} diff --git a/src/Microsoft.AspNetCore.Routing/BuilderEndpointDataSource.cs b/src/Microsoft.AspNetCore.Routing/BuilderEndpointDataSource.cs new file mode 100644 index 0000000000..bf66ab8719 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/BuilderEndpointDataSource.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// 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.Linq; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Routing +{ + internal class BuilderEndpointDataSource : EndpointDataSource + { + private readonly EndpointDataSourceBuilder _builder; + + public BuilderEndpointDataSource(EndpointDataSourceBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + _builder = builder; + } + + public override IChangeToken GetChangeToken() + { + return NullChangeToken.Singleton; + } + + public override IReadOnlyList Endpoints => _builder.Endpoints.Select(b => b.Build()).ToArray(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSourceBuilder.cs b/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSourceBuilder.cs new file mode 100644 index 0000000000..922a744cb7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSourceBuilder.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; + +namespace Microsoft.AspNetCore.Routing +{ + internal class DefaultEndpointDataSourceBuilder : EndpointDataSourceBuilder + { + public IApplicationBuilder ApplicationBuilder { get; set; } + + public override ICollection Endpoints { get; } = new List(); + + public override IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs index 19f3d0e77a..0c1e241a8c 100644 --- a/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs @@ -59,6 +59,12 @@ namespace Microsoft.Extensions.DependencyInjection return new CompositeEndpointDataSource(options.Value.DataSources); }); + // + // Endpoint Infrastructure + // + services.TryAddSingleton(); + services.TryAddSingleton(); + // // Default matcher implementation // diff --git a/src/Microsoft.AspNetCore.Routing/EndpointBuilder.cs b/src/Microsoft.AspNetCore.Routing/EndpointBuilder.cs new file mode 100644 index 0000000000..2187e387a3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/EndpointBuilder.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Routing +{ + public abstract class EndpointBuilder + { + public string DisplayName { get; set; } + + public IList Metadata { get; } = new List(); + + public abstract Endpoint Build(); + } +} diff --git a/src/Microsoft.AspNetCore.Routing/EndpointDataSourceBuilder.cs b/src/Microsoft.AspNetCore.Routing/EndpointDataSourceBuilder.cs new file mode 100644 index 0000000000..eea8b9afb2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/EndpointDataSourceBuilder.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; + +namespace Microsoft.AspNetCore.Routing +{ + public abstract class EndpointDataSourceBuilder + { + public abstract ICollection Endpoints { get; } + + public abstract IApplicationBuilder CreateApplicationBuilder(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpointBuilder.cs b/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpointBuilder.cs new file mode 100644 index 0000000000..c53112fea5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/Matching/MatcherEndpointBuilder.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Routing.Matching +{ + public sealed class MatcherEndpointBuilder : EndpointBuilder + { + public Func Invoker { get; set; } + + public RoutePattern RoutePattern { get; set; } + + public int Order { get; set; } + + public MatcherEndpointBuilder( + Func invoker, + RoutePattern routePattern, + int order) + { + Invoker = invoker; + RoutePattern = routePattern; + Order = order; + } + + public override Endpoint Build() + { + var matcherEndpoint = new MatcherEndpoint( + Invoker, + RoutePattern, + Order, + new EndpointMetadataCollection(Metadata), + DisplayName); + + return matcherEndpoint; + } + } +} diff --git a/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs b/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs index 5f059cae60..820841a9f4 100644 --- a/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs +++ b/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests { // Arrange var expectedContentType = "text/plain"; - var expectedContent = "Hello, World!"; + var expectedContent = "Plain text!"; // Act var response = await _client.GetAsync("/plaintext"); @@ -62,6 +62,25 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests Assert.Equal(expectedContent, actualContent); } + [Fact] + public async Task MatchesHelloMiddleware_AndReturnsPlaintext() + { + // Arrange + var expectedContentType = "text/plain"; + var expectedContent = "Hello World"; + + // Act + var response = await _client.GetAsync("/helloworld"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.NotNull(response.Content.Headers.ContentType); + Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType); + var actualContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedContent, actualContent); + } + [Fact] public async Task MatchesEndpoint_WithSuccessfulConstraintMatch() { diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs similarity index 83% rename from test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingBuilderExtensionsTest.cs rename to test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs index dd85fb11f6..8476676473 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs @@ -5,7 +5,6 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder.Internal; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -13,7 +12,7 @@ using Xunit; namespace Microsoft.AspNetCore.Builder { - public class EndpointRoutingBuilderExtensionsTest + public class EndpointRoutingApplicationBuilderExtensionsTest { [Fact] public void UseEndpointRouting_ServicesNotRegistered_Throws() @@ -109,6 +108,26 @@ namespace Microsoft.AspNetCore.Builder Assert.NotNull(httpContext.Features.Get()); } + [Fact] + public void UseEndpointRouting_CallWithBuilder_SetsEndpointBuilder() + { + // Arrange + var services = CreateServices(); + + var app = new ApplicationBuilder(services); + + // Act + app.UseEndpointRouting(builder => + { + builder.MapEndpoint(d => null, "/", "Test endpoint"); + }); + + // Assert + var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)services.GetRequiredService(); + var endpointBuilder = Assert.Single(dataSourceBuilder.Endpoints); + Assert.Equal("Test endpoint", endpointBuilder.DisplayName); + } + private IServiceProvider CreateServices() { var services = new ServiceCollection(); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs new file mode 100644 index 0000000000..e854851fc8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; +using Xunit; + +namespace Microsoft.AspNetCore.Builder +{ + public class MapEndpointEndpointDataSourceBuilderExtensionsTest + { + [Fact] + public void MapEndpoint_StringPattern_BuildsEndpoint() + { + // Arrange + var builder = new DefaultEndpointDataSourceBuilder(); + Func invoker = (d) => null; + + // Act + var endpointBuilder = builder.MapEndpoint(invoker, "/", "Display name!"); + + // Assert + Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); + Assert.Equal(invoker, endpointBuilder.Invoker); + Assert.Equal("Display name!", endpointBuilder.DisplayName); + Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + } + + [Fact] + public void MapEndpoint_TypedPattern_BuildsEndpoint() + { + // Arrange + var builder = new DefaultEndpointDataSourceBuilder(); + Func invoker = (d) => null; + + // Act + var endpointBuilder = builder.MapEndpoint(invoker, RoutePatternFactory.Parse("/"), "Display name!"); + + // Assert + Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); + Assert.Equal(invoker, endpointBuilder.Invoker); + Assert.Equal("Display name!", endpointBuilder.DisplayName); + Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + } + + [Fact] + public void MapEndpoint_StringPatternAndMetadata_BuildsEndpoint() + { + // Arrange + var metadata = new object(); + var builder = new DefaultEndpointDataSourceBuilder(); + Func invoker = (d) => null; + + // Act + var endpointBuilder = builder.MapEndpoint(invoker, "/", "Display name!", new[] { metadata }); + + // Assert + Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); + Assert.Equal(invoker, endpointBuilder.Invoker); + Assert.Equal("Display name!", endpointBuilder.DisplayName); + Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata)); + } + + [Fact] + public void MapEndpoint_TypedPatternAndMetadata_BuildsEndpoint() + { + // Arrange + var metadata = new object(); + var builder = new DefaultEndpointDataSourceBuilder(); + Func invoker = (d) => null; + + // Act + var endpointBuilder = builder.MapEndpoint(invoker, RoutePatternFactory.Parse("/"), "Display name!", new[] { metadata }); + + // Assert + Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); + Assert.Equal(invoker, endpointBuilder.Invoker); + Assert.Equal("Display name!", endpointBuilder.DisplayName); + Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata)); + } + } +} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointBuilderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointBuilderTest.cs new file mode 100644 index 0000000000..acfc096914 --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointBuilderTest.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Patterns; +using Xunit; + +namespace Microsoft.AspNetCore.Routing.Matching +{ + public class MatcherEndpointBuilderTest + { + [Fact] + public void Build_AllValuesSet_EndpointCreated() + { + const int defaultOrder = 0; + object metadata = new object(); + Func invoker = (d) => null; + + var builder = new MatcherEndpointBuilder(invoker, RoutePatternFactory.Parse("/"), defaultOrder) + { + DisplayName = "Display name!", + Metadata = { metadata } + }; + + var endpoint = Assert.IsType(builder.Build()); + Assert.Equal("Display name!", endpoint.DisplayName); + Assert.Equal(defaultOrder, endpoint.Order); + Assert.Equal(invoker, endpoint.Invoker); + Assert.Equal("/", endpoint.RoutePattern.RawText); + Assert.Equal(metadata, Assert.Single(endpoint.Metadata)); + } + } +}