diff --git a/.vsts-pipelines/builds/ci-internal.yml b/.vsts-pipelines/builds/ci-internal.yml index dc7b8a3cb9..c2c5336fd0 100644 --- a/.vsts-pipelines/builds/ci-internal.yml +++ b/.vsts-pipelines/builds/ci-internal.yml @@ -7,7 +7,7 @@ resources: - repository: buildtools type: git name: aspnet-BuildTools - ref: refs/heads/release/2.2 + ref: refs/heads/master phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/.vsts-pipelines/builds/ci-public.yml b/.vsts-pipelines/builds/ci-public.yml index f5087d9c30..507c89b025 100644 --- a/.vsts-pipelines/builds/ci-public.yml +++ b/.vsts-pipelines/builds/ci-public.yml @@ -9,7 +9,7 @@ resources: type: github endpoint: DotNet-Bot GitHub Connection name: aspnet/BuildTools - ref: refs/heads/release/2.2 + ref: refs/heads/master phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools 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/benchmarkapps/Benchmarks/benchmarks.json b/benchmarkapps/Benchmarks/benchmarks.json index 1779a80c8c..d69ee7afce 100644 --- a/benchmarkapps/Benchmarks/benchmarks.json +++ b/benchmarkapps/Benchmarks/benchmarks.json @@ -8,7 +8,7 @@ }, "Source": { "Repository": "https://github.com/aspnet/routing.git", - "BranchOrCommit": "release/2.2", + "BranchOrCommit": "master", "Project": "benchmarkapps/Benchmarks/Benchmarks.csproj" }, "Port": 8080 diff --git a/build/dependencies.props b/build/dependencies.props index bb0f911ee5..ecfcb0eadb 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,33 +4,33 @@ 0.10.13 - 2.2.0-preview1-20180807.2 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 + 3.0.0-alpha1-20180810.1 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 + 3.0.0-alpha1-10275 2.0.9 2.1.2 2.2.0-preview1-26618-02 diff --git a/build/repo.props b/build/repo.props index f1fe24dd27..17a98ac7e7 100644 --- a/build/repo.props +++ b/build/repo.props @@ -4,7 +4,6 @@ Internal.AspNetCore.Universe.Lineup - 2.2.0-* https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 3fbcc80189..e417a01b52 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180807.2 -commithash:11495dbd236104434e08cb1152fcb58cf2a20923 +version:3.0.0-alpha1-20180810.1 +commithash:45c32b4f020e14a9295be31866051a18d293309d diff --git a/korebuild.json b/korebuild.json index d217d06e3e..8a276a7f35 100644 --- a/korebuild.json +++ b/korebuild.json @@ -1,4 +1,4 @@ { - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.2/tools/korebuild.schema.json", - "channel": "release/2.2" + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", + "channel": "master" } 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 7736561627..d4f072352c 100644 --- a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs +++ b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs @@ -5,21 +5,18 @@ using System; using System.IO; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Internal; -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) { @@ -29,80 +26,74 @@ 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"), - new MatcherEndpoint((next) => (httpContext) => - { - using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true)) - { - var graphWriter = httpContext.RequestServices.GetRequiredService(); - var dataSource = httpContext.RequestServices.GetRequiredService(); - graphWriter.Write(dataSource, writer); - } - - return Task.CompletedTask; - }, - RoutePatternFactory.Parse("/graph"), - 0, - new EndpointMetadataCollection(new HttpMethodMetadata(new[]{ "GET", })), - "DFA Graph"), - }); - - services.TryAddEnumerable(ServiceDescriptor.Singleton(endpointDataSource)); } - public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app) + public void Configure(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"); + builder.MapEndpoint( + (next) => (httpContext) => + { + using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true)) + { + var graphWriter = httpContext.RequestServices.GetRequiredService(); + var dataSource = httpContext.RequestServices.GetRequiredService(); + graphWriter.Write(dataSource, writer); + } + + return Task.CompletedTask; + }, + "/graph", + "DFA Graph", + new object[] { new HttpMethodMetadata(new[] { "GET", }) }); + }); // 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 366f2d30fa..75b67ccd3e 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)); + } + } +} diff --git a/version.props b/version.props index 44985cedb3..71a78cddd8 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@ - + - 2.2.0 - preview1 + 3.0.0 + alpha1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000