diff --git a/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs b/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs index 954a143b0e..f6062f0531 100644 --- a/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs +++ b/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; namespace Benchmarks { @@ -18,8 +17,13 @@ namespace Benchmarks public void ConfigureServices(IServiceCollection services) { services.AddRouting(); + } - var endpointDataSource = new DefaultEndpointDataSource(new[] + public void Configure(IApplicationBuilder app) + { + app.UseEndpointRouting(builder => + { + var endpointDataSource = new DefaultEndpointDataSource(new[] { new RouteEndpoint( requestDelegate: (httpContext) => @@ -37,12 +41,8 @@ namespace Benchmarks displayName: "Plaintext"), }); - services.TryAddEnumerable(ServiceDescriptor.Singleton(endpointDataSource)); - } - - public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app) - { - app.UseEndpointRouting(); + builder.DataSources.Add(endpointDataSource); + }); app.UseEndpoint(); } diff --git a/samples/RoutingSample.Web/AuthorizationMiddleware/AuthorizationAppBuilderExtensions.cs b/samples/RoutingSample.Web/AuthorizationMiddleware/AuthorizationAppBuilderExtensions.cs new file mode 100644 index 0000000000..1566f8d127 --- /dev/null +++ b/samples/RoutingSample.Web/AuthorizationMiddleware/AuthorizationAppBuilderExtensions.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 System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using RoutingSample.Web.AuthorizationMiddleware; + +namespace Microsoft.AspNetCore.Builder +{ + public static class AuthorizationAppBuilderExtensions + { + public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + return app.UseMiddleware(); + } + } +} diff --git a/samples/RoutingSample.Web/AuthorizationMiddleware/AuthorizationMetadata.cs b/samples/RoutingSample.Web/AuthorizationMiddleware/AuthorizationMetadata.cs new file mode 100644 index 0000000000..0e492ddc24 --- /dev/null +++ b/samples/RoutingSample.Web/AuthorizationMiddleware/AuthorizationMetadata.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 System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace RoutingSample.Web.AuthorizationMiddleware +{ + public class AuthorizationMetadata + { + public AuthorizationMetadata(IEnumerable roles) + { + if (roles == null) + { + throw new ArgumentNullException(nameof(roles)); + } + + Roles = roles.ToArray(); + } + + public IReadOnlyList Roles { get; } + } +} diff --git a/samples/RoutingSample.Web/AuthorizationMiddleware/AuthorizationMiddleware.cs b/samples/RoutingSample.Web/AuthorizationMiddleware/AuthorizationMiddleware.cs new file mode 100644 index 0000000000..594b18c43b --- /dev/null +++ b/samples/RoutingSample.Web/AuthorizationMiddleware/AuthorizationMiddleware.cs @@ -0,0 +1,59 @@ +// 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.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; + +namespace RoutingSample.Web.AuthorizationMiddleware +{ + public class AuthorizationMiddleware + { + private readonly ILogger _logger; + private readonly RequestDelegate _next; + + public AuthorizationMiddleware(ILogger logger, RequestDelegate next) + { + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + _logger = logger; + _next = next; + } + + public async Task Invoke(HttpContext httpContext) + { + var endpoint = httpContext.Features.Get()?.Endpoint; + if (endpoint != null) + { + var metadata = endpoint.Metadata.GetMetadata(); + // Only run authorization if endpoint has metadata + if (metadata != null) + { + if (!httpContext.Request.Query.TryGetValue("x-role", out var role) || + !metadata.Roles.Contains(role.ToString())) + { + httpContext.Response.StatusCode = 401; + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync($"Unauthorized access to '{endpoint.DisplayName}'."); + return; + } + } + } + + await _next(httpContext); + } + } +} diff --git a/samples/RoutingSample.Web/AuthorizationMiddleware/EndpointConventionBuilderExtensions .cs b/samples/RoutingSample.Web/AuthorizationMiddleware/EndpointConventionBuilderExtensions .cs new file mode 100644 index 0000000000..51a5c9c469 --- /dev/null +++ b/samples/RoutingSample.Web/AuthorizationMiddleware/EndpointConventionBuilderExtensions .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 Microsoft.AspNetCore.Routing; +using RoutingSample.Web.AuthorizationMiddleware; + +namespace Microsoft.AspNetCore.Builder +{ + public static class EndpointConventionBuilderExtensions + { + public static IEndpointConventionBuilder RequireAuthorization(this IEndpointConventionBuilder builder, params string[] roles) + { + builder.Apply(endpointBuilder => endpointBuilder.Metadata.Add(new AuthorizationMetadata(roles))); + return builder; + } + } +} \ No newline at end of file diff --git a/samples/RoutingSample.Web/HelloExtension/EndpointDataSourceBuilderExtensions.cs b/samples/RoutingSample.Web/HelloExtension/EndpointRouteBuilderExtensions.cs similarity index 72% rename from samples/RoutingSample.Web/HelloExtension/EndpointDataSourceBuilderExtensions.cs rename to samples/RoutingSample.Web/HelloExtension/EndpointRouteBuilderExtensions.cs index 3d1f53673a..fc26b50bf8 100644 --- a/samples/RoutingSample.Web/HelloExtension/EndpointDataSourceBuilderExtensions.cs +++ b/samples/RoutingSample.Web/HelloExtension/EndpointRouteBuilderExtensions.cs @@ -11,9 +11,9 @@ using Microsoft.AspNetCore.Routing.Patterns; namespace Microsoft.AspNetCore.Builder { - public static class EndpointDataSourceBuilderExtensions + public static class EndpointRouteBuilderExtensions { - public static EndpointBuilder MapHello(this EndpointDataSourceBuilder builder, string template, string greeter) + public static IEndpointConventionBuilder MapHello(this IEndpointRouteBuilder builder, string template, string greeter) { if (builder == null) { @@ -24,10 +24,10 @@ namespace Microsoft.AspNetCore.Builder .UseHello(greeter) .Build(); - return builder.MapEndpoint( - pipeline, + return builder.Map( template, - "Hello"); + "Hello " + greeter, + pipeline); } } } diff --git a/samples/RoutingSample.Web/RoutingSample.Web.csproj b/samples/RoutingSample.Web/RoutingSample.Web.csproj index 5f01b1bb1e..c798b5f4e5 100644 --- a/samples/RoutingSample.Web/RoutingSample.Web.csproj +++ b/samples/RoutingSample.Web/RoutingSample.Web.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.2 diff --git a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs index 5422484210..613bd12462 100644 --- a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs +++ b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; @@ -36,19 +37,29 @@ namespace RoutingSample.Web { builder.MapHello("/helloworld", "World"); - builder.MapEndpoint( + builder.MapHello("/helloworld-secret", "Secret World") + .RequireAuthorization("swordfish"); + + builder.MapGet( + "/", (httpContext) => { + var dataSource = httpContext.RequestServices.GetRequiredService(); + + var sb = new StringBuilder(); + sb.AppendLine("Endpoints:"); + foreach (var endpoint in dataSource.Endpoints.OfType().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase)) + { + sb.AppendLine($"- {endpoint.RoutePattern.RawText}"); + } + 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( + return response.WriteAsync(sb.ToString()); + }); + builder.MapGet( + "/plaintext", (httpContext) => { var response = httpContext.Response; @@ -57,30 +68,28 @@ namespace RoutingSample.Web response.ContentType = "text/plain"; response.ContentLength = payloadLength; return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength); - }, - "/plaintext", - "Plaintext"); - builder.MapEndpoint( + }); + builder.MapGet( + "/withconstraints/{id:endsWith(_001)}", (httpContext) => { var response = httpContext.Response; response.StatusCode = 200; response.ContentType = "text/plain"; return response.WriteAsync("WithConstraints"); - }, - "/withconstraints/{id:endsWith(_001)}", - "withconstraints"); - builder.MapEndpoint( + }); + builder.MapGet( + "/withoptionalconstraints/{id:endsWith(_001)?}", (httpContext) => { var response = httpContext.Response; response.StatusCode = 200; response.ContentType = "text/plain"; return response.WriteAsync("withoptionalconstraints"); - }, - "/withoptionalconstraints/{id:endsWith(_001)?}", - "withoptionalconstraints"); - builder.MapEndpoint( + }); + builder.MapGet( + "/graph", + "DFA Graph", (httpContext) => { using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true)) @@ -91,11 +100,9 @@ namespace RoutingSample.Web } return Task.CompletedTask; - }, - "/graph", - "DFA Graph", - new HttpMethodMetadata(new[] { "GET", })); - builder.MapEndpoint( + }); + builder.MapGet( + "/WithSingleAsteriskCatchAll/{*path}", (httpContext) => { var linkGenerator = httpContext.RequestServices.GetRequiredService(); @@ -106,10 +113,9 @@ namespace RoutingSample.Web return response.WriteAsync( "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { })); }, - "/WithSingleAsteriskCatchAll/{*path}", - "WithSingleAsteriskCatchAll", new RouteValuesAddressMetadata(routeName: "WithSingleAsteriskCatchAll", requiredValues: new RouteValueDictionary())); - builder.MapEndpoint( + builder.MapGet( + "/WithDoubleAsteriskCatchAll/{**path}", (httpContext) => { var linkGenerator = httpContext.RequestServices.GetRequiredService(); @@ -120,12 +126,12 @@ namespace RoutingSample.Web return response.WriteAsync( "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { })); }, - "/WithDoubleAsteriskCatchAll/{**path}", - "WithDoubleAsteriskCatchAll", new RouteValuesAddressMetadata(routeName: "WithDoubleAsteriskCatchAll", requiredValues: new RouteValueDictionary())); }); app.UseStaticFiles(); + + app.UseAuthorization(); app.UseEndpoint(); } diff --git a/src/Microsoft.AspNetCore.Routing/Builder/EndpointRouteBuilderExtensions.cs b/src/Microsoft.AspNetCore.Routing/Builder/EndpointRouteBuilderExtensions.cs new file mode 100644 index 0000000000..0babbfc0e6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/Builder/EndpointRouteBuilderExtensions.cs @@ -0,0 +1,210 @@ +// 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.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Builder +{ + public static class EndpointRouteBuilderExtensions + { + // Avoid creating a new array every call + private static readonly string[] GetVerb = new[] { "GET" }; + private static readonly string[] PostVerb = new[] { "POST" }; + private static readonly string[] PutVerb = new[] { "PUT" }; + private static readonly string[] DeleteVerb = new[] { "DELETE" }; + + #region MapVerbs + public static IEndpointConventionBuilder MapGet( + this IEndpointRouteBuilder builder, + string pattern, + RequestDelegate requestDelegate, + params object[] metadata) + { + return MapVerbs(builder, pattern, displayName: null, requestDelegate, GetVerb, metadata); + } + + public static IEndpointConventionBuilder MapGet( + this IEndpointRouteBuilder builder, + string pattern, + string displayName, + RequestDelegate requestDelegate, + params object[] metadata) + { + return MapVerbs(builder, pattern, displayName, requestDelegate, GetVerb, metadata); + } + + public static IEndpointConventionBuilder MapPost( + this IEndpointRouteBuilder builder, + string pattern, + RequestDelegate requestDelegate, + params object[] metadata) + { + return MapVerbs(builder, pattern, displayName: null, requestDelegate, PostVerb, metadata); + } + + public static IEndpointConventionBuilder MapPost( + this IEndpointRouteBuilder builder, + string pattern, + string displayName, + RequestDelegate requestDelegate, + params object[] metadata) + { + return MapVerbs(builder, pattern, displayName, requestDelegate, PostVerb, metadata); + } + + public static IEndpointConventionBuilder MapPut( + this IEndpointRouteBuilder builder, + string pattern, + RequestDelegate requestDelegate, + params object[] metadata) + { + return MapVerbs(builder, pattern, displayName: null, requestDelegate, PutVerb, metadata); + } + + public static IEndpointConventionBuilder MapPut( + this IEndpointRouteBuilder builder, + string pattern, + string displayName, + RequestDelegate requestDelegate, + params object[] metadata) + { + return MapVerbs(builder, pattern, displayName, requestDelegate, PutVerb, metadata); + } + + public static IEndpointConventionBuilder MapDelete( + this IEndpointRouteBuilder builder, + string pattern, + RequestDelegate requestDelegate, + params object[] metadata) + { + return MapVerbs(builder, pattern, displayName: null, requestDelegate, DeleteVerb, metadata); + } + + public static IEndpointConventionBuilder MapDelete( + this IEndpointRouteBuilder builder, + string pattern, + string displayName, + RequestDelegate requestDelegate, + params object[] metadata) + { + return MapVerbs(builder, pattern, displayName, requestDelegate, DeleteVerb, metadata); + } + + public static IEndpointConventionBuilder MapVerbs( + this IEndpointRouteBuilder builder, + string pattern, + RequestDelegate requestDelegate, + IList httpMethods, + params object[] metadata) + { + return MapVerbs(builder, pattern, displayName: null, requestDelegate, httpMethods, metadata); + } + + public static IEndpointConventionBuilder MapVerbs( + this IEndpointRouteBuilder builder, + string pattern, + string displayName, + RequestDelegate requestDelegate, + IList httpMethods, + params object[] metadata) + { + if (httpMethods == null) + { + throw new ArgumentNullException(nameof(httpMethods)); + } + + var resolvedMetadata = new List(); + resolvedMetadata.Add(new HttpMethodMetadata(httpMethods)); + if (metadata != null) + { + resolvedMetadata.AddRange(metadata); + } + + return Map(builder, pattern, displayName ?? $"{pattern} HTTP: {string.Join(", ", httpMethods)}", requestDelegate, metadata: resolvedMetadata.ToArray()); + } + #endregion + + #region Map + public static IEndpointConventionBuilder Map( + this IEndpointRouteBuilder builder, + string pattern, + RequestDelegate requestDelegate, + params object[] metadata) + { + return Map(builder, RoutePatternFactory.Parse(pattern), pattern, requestDelegate, metadata); + } + + public static IEndpointConventionBuilder Map( + this IEndpointRouteBuilder builder, + string pattern, + string displayName, + RequestDelegate requestDelegate, + params object[] metadata) + { + return Map(builder, RoutePatternFactory.Parse(pattern), displayName, requestDelegate, metadata); + } + + public static IEndpointConventionBuilder Map( + this IEndpointRouteBuilder builder, + RoutePattern pattern, + RequestDelegate requestDelegate, + params object[] metadata) + { + return Map(builder, pattern, pattern.RawText ?? pattern.DebuggerToString(), requestDelegate, metadata); + } + + public static IEndpointConventionBuilder Map( + this IEndpointRouteBuilder builder, + RoutePattern pattern, + string displayName, + RequestDelegate requestDelegate, + params object[] metadata) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (pattern == null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + if (requestDelegate == null) + { + throw new ArgumentNullException(nameof(requestDelegate)); + } + + const int defaultOrder = 0; + + var routeEndpointModel = new RouteEndpointModel( + requestDelegate, + pattern, + defaultOrder); + routeEndpointModel.DisplayName = displayName; + if (metadata != null) + { + foreach (var item in metadata) + { + routeEndpointModel.Metadata.Add(item); + } + } + + var modelEndpointDataSource = builder.DataSources.OfType().FirstOrDefault(); + + if (modelEndpointDataSource == null) + { + modelEndpointDataSource = new ModelEndpointDataSource(); + builder.DataSources.Add(modelEndpointDataSource); + } + + return modelEndpointDataSource.AddEndpointModel(routeEndpointModel); + } + #endregion + } +} diff --git a/src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs index de05c25efe..acdad5b2af 100644 --- a/src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { @@ -14,25 +14,36 @@ namespace Microsoft.AspNetCore.Builder // Property key is used by MVC package to check that routing is registered private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered"; - public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder) + public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder, Action configure) { - return builder.UseEndpointRouting(null); - } + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } - public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder, Action configure) - { VerifyRoutingIsRegistered(builder); - if (configure != null) + var routeOptions = builder.ApplicationServices.GetRequiredService>(); + EndpointDataSource middlewareEndpointDataSource; + + var endpointRouteBuilder = builder.ApplicationServices.GetRequiredService(); + if (endpointRouteBuilder is DefaultEndpointRouteBuilder defaultEndpointRouteBuilder) { - var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)builder.ApplicationServices.GetRequiredService(); - dataSourceBuilder.ApplicationBuilder = builder; - configure(dataSourceBuilder); + defaultEndpointRouteBuilder.ApplicationBuilder = builder; } + configure(endpointRouteBuilder); + + foreach (var dataSource in endpointRouteBuilder.DataSources) + { + routeOptions.Value.EndpointDataSources.Add(dataSource); + } + + // Create endpoint data source for data sources registered in configure + middlewareEndpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources); builder.Properties[EndpointRoutingRegisteredKey] = true; - return builder.UseMiddleware(); + return builder.UseMiddleware(middlewareEndpointDataSource); } public static IApplicationBuilder UseEndpoint(this IApplicationBuilder builder) diff --git a/src/Microsoft.AspNetCore.Routing/Builder/MapEndpointEndpointDataSourceBuilderExtensions.cs b/src/Microsoft.AspNetCore.Routing/Builder/MapEndpointEndpointDataSourceBuilderExtensions.cs deleted file mode 100644 index fca430da9a..0000000000 --- a/src/Microsoft.AspNetCore.Routing/Builder/MapEndpointEndpointDataSourceBuilderExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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 RouteEndpointBuilder MapEndpoint( - this EndpointDataSourceBuilder builder, - RequestDelegate requestDelegate, - string pattern, - string displayName) - { - return MapEndpoint(builder, requestDelegate, pattern, displayName, metadata: null); - } - - public static RouteEndpointBuilder MapEndpoint( - this EndpointDataSourceBuilder builder, - RequestDelegate requestDelegate, - RoutePattern pattern, - string displayName) - { - return MapEndpoint(builder, requestDelegate, pattern, displayName, metadata: null); - } - - public static RouteEndpointBuilder MapEndpoint( - this EndpointDataSourceBuilder builder, - RequestDelegate requestDelegate, - string pattern, - string displayName, - params object[] metadata) - { - return MapEndpoint(builder, requestDelegate, RoutePatternFactory.Parse(pattern), displayName, metadata); - } - - public static RouteEndpointBuilder MapEndpoint( - this EndpointDataSourceBuilder builder, - RequestDelegate requestDelegate, - RoutePattern pattern, - string displayName, - params object[] metadata) - { - const int defaultOrder = 0; - - var endpointBuilder = new RouteEndpointBuilder( - requestDelegate, - 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 deleted file mode 100644 index d119f80ed2..0000000000 --- a/src/Microsoft.AspNetCore.Routing/BuilderEndpointDataSource.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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.AspNetCore.Http; -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/CompositeEndpointDataSource.cs b/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs index 00a24ccfcd..d9bc0f559a 100644 --- a/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Routing/CompositeEndpointDataSource.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Text; @@ -18,24 +20,49 @@ namespace Microsoft.AspNetCore.Routing [DebuggerDisplay("{DebuggerDisplayString,nq}")] public sealed class CompositeEndpointDataSource : EndpointDataSource { - private readonly EndpointDataSource[] _dataSources; private readonly object _lock; + private readonly ICollection _dataSources; private IReadOnlyList _endpoints; private IChangeToken _consumerChangeToken; private CancellationTokenSource _cts; - internal CompositeEndpointDataSource(IEnumerable dataSources) + private CompositeEndpointDataSource() { - if (dataSources == null) - { - throw new ArgumentNullException(nameof(dataSources)); - } - CreateChangeToken(); - _dataSources = dataSources.ToArray(); _lock = new object(); } + internal CompositeEndpointDataSource(ObservableCollection dataSources) : this() + { + dataSources.CollectionChanged += OnDataSourcesChanged; + + _dataSources = dataSources; + } + + public CompositeEndpointDataSource(IEnumerable endpointDataSources) : this() + { + _dataSources = new List(); + + foreach (var dataSource in endpointDataSources) + { + _dataSources.Add(dataSource); + } + } + + private void OnDataSourcesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + lock (_lock) + { + // Only trigger changes if composite data source has already initialized endpoints + if (_endpoints != null) + { + HandleChange(); + } + } + } + + public IEnumerable DataSources => _dataSources; + /// /// Gets a used to signal invalidation of cached /// instances. diff --git a/src/Microsoft.AspNetCore.Routing/DependencyInjection/ConfigureEndpointOptions.cs b/src/Microsoft.AspNetCore.Routing/ConfigureRouteOptions.cs similarity index 62% rename from src/Microsoft.AspNetCore.Routing/DependencyInjection/ConfigureEndpointOptions.cs rename to src/Microsoft.AspNetCore.Routing/ConfigureRouteOptions.cs index ce6c069a22..f4ba549fe5 100644 --- a/src/Microsoft.AspNetCore.Routing/DependencyInjection/ConfigureEndpointOptions.cs +++ b/src/Microsoft.AspNetCore.Routing/ConfigureRouteOptions.cs @@ -3,16 +3,17 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection { - internal class ConfigureEndpointOptions : IConfigureOptions + internal class ConfigureRouteOptions : IConfigureOptions { - private readonly IEnumerable _dataSources; + private readonly ICollection _dataSources; - public ConfigureEndpointOptions(IEnumerable dataSources) + public ConfigureRouteOptions(ICollection dataSources) { if (dataSources == null) { @@ -22,17 +23,14 @@ namespace Microsoft.Extensions.DependencyInjection _dataSources = dataSources; } - public void Configure(EndpointOptions options) + public void Configure(RouteOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } - foreach (var dataSource in _dataSources) - { - options.DataSources.Add(dataSource); - } + options.EndpointDataSources = _dataSources; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSourceBuilder.cs b/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSourceBuilder.cs deleted file mode 100644 index 922a744cb7..0000000000 --- a/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSourceBuilder.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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/DefaultEndpointRouteBuilder.cs b/src/Microsoft.AspNetCore.Routing/DefaultEndpointRouteBuilder.cs new file mode 100644 index 0000000000..d5b09e20eb --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/DefaultEndpointRouteBuilder.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 System.Collections.Generic; +using Microsoft.AspNetCore.Builder; + +namespace Microsoft.AspNetCore.Routing +{ + internal class DefaultEndpointRouteBuilder : IEndpointRouteBuilder + { + public DefaultEndpointRouteBuilder() + { + DataSources = new List(); + } + + public IApplicationBuilder ApplicationBuilder { get; set; } + + public IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New(); + + public ICollection DataSources { get; } + + public IServiceProvider ServiceProvider => ApplicationBuilder.ApplicationServices; + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs index 086d9bf026..2e04322698 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultLinkGenerator.cs @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Routing public DefaultLinkGenerator( ParameterPolicyFactory parameterPolicyFactory, - CompositeEndpointDataSource dataSource, + EndpointDataSource dataSource, ObjectPool uriBuildingContextPool, IOptions routeOptions, ILogger logger, diff --git a/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs index f79893f85a..00a127e861 100644 --- a/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs @@ -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.ObjectModel; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Matching; @@ -49,21 +50,22 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(typeof(RoutingMarkerService)); - // Collect all data sources from DI. - services.TryAddEnumerable(ServiceDescriptor.Transient, ConfigureEndpointOptions>()); + // Setup global collection of endpoint data sources + var dataSources = new ObservableCollection(); + services.TryAddEnumerable(ServiceDescriptor.Transient, ConfigureRouteOptions>( + serviceProvider => new ConfigureRouteOptions(dataSources))); // Allow global access to the list of endpoints. - services.TryAddSingleton(s => + services.TryAddSingleton(s => { - var options = s.GetRequiredService>(); - return new CompositeEndpointDataSource(options.Value.DataSources); + // Call internal ctor and pass global collection + return new CompositeEndpointDataSource(dataSources); }); // // Endpoint Infrastructure // - services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddTransient(); // // Default matcher implementation diff --git a/src/Microsoft.AspNetCore.Routing/EndpointBuilder.cs b/src/Microsoft.AspNetCore.Routing/EndpointModel.cs similarity index 92% rename from src/Microsoft.AspNetCore.Routing/EndpointBuilder.cs rename to src/Microsoft.AspNetCore.Routing/EndpointModel.cs index 27d64f1625..6d9e085645 100644 --- a/src/Microsoft.AspNetCore.Routing/EndpointBuilder.cs +++ b/src/Microsoft.AspNetCore.Routing/EndpointModel.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Routing { - public abstract class EndpointBuilder + public abstract class EndpointModel { public RequestDelegate RequestDelegate { get; set; } diff --git a/src/Microsoft.AspNetCore.Routing/EndpointNameAddressScheme.cs b/src/Microsoft.AspNetCore.Routing/EndpointNameAddressScheme.cs index 2716de36e8..5fd81b40c2 100644 --- a/src/Microsoft.AspNetCore.Routing/EndpointNameAddressScheme.cs +++ b/src/Microsoft.AspNetCore.Routing/EndpointNameAddressScheme.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Routing { private readonly DataSourceDependentCache> _cache; - public EndpointNameAddressScheme(CompositeEndpointDataSource dataSource) + public EndpointNameAddressScheme(EndpointDataSource dataSource) { _cache = new DataSourceDependentCache>(dataSource, Initialize); } diff --git a/src/Microsoft.AspNetCore.Routing/EndpointOptions.cs b/src/Microsoft.AspNetCore.Routing/EndpointOptions.cs deleted file mode 100644 index 8f364d6eaa..0000000000 --- a/src/Microsoft.AspNetCore.Routing/EndpointOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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 -{ - // Internal for 2.2. Public API for configuring endpoints will be added in 3.0 - internal class EndpointOptions - { - public IList DataSources { get; } = new List(); - } -} diff --git a/src/Microsoft.AspNetCore.Routing/EndpointRoutingMiddleware.cs b/src/Microsoft.AspNetCore.Routing/EndpointRoutingMiddleware.cs index 60efa46586..7b18916f29 100644 --- a/src/Microsoft.AspNetCore.Routing/EndpointRoutingMiddleware.cs +++ b/src/Microsoft.AspNetCore.Routing/EndpointRoutingMiddleware.cs @@ -16,14 +16,14 @@ namespace Microsoft.AspNetCore.Routing { private readonly MatcherFactory _matcherFactory; private readonly ILogger _logger; - private readonly CompositeEndpointDataSource _endpointDataSource; + private readonly EndpointDataSource _endpointDataSource; private readonly RequestDelegate _next; private Task _initializationTask; public EndpointRoutingMiddleware( MatcherFactory matcherFactory, - CompositeEndpointDataSource endpointDataSource, + EndpointDataSource endpointDataSource, ILogger logger, RequestDelegate next) { diff --git a/src/Microsoft.AspNetCore.Routing/IEndpointModelConvention.cs b/src/Microsoft.AspNetCore.Routing/IEndpointModelConvention.cs new file mode 100644 index 0000000000..ee77b2cc63 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/IEndpointModelConvention.cs @@ -0,0 +1,12 @@ +// 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; + +namespace Microsoft.AspNetCore.Routing +{ + public interface IEndpointConventionBuilder + { + void Apply(Action convention); + } +} diff --git a/src/Microsoft.AspNetCore.Routing/EndpointDataSourceBuilder.cs b/src/Microsoft.AspNetCore.Routing/IEndpointRouteBuilder.cs similarity index 56% rename from src/Microsoft.AspNetCore.Routing/EndpointDataSourceBuilder.cs rename to src/Microsoft.AspNetCore.Routing/IEndpointRouteBuilder.cs index eea8b9afb2..4d2d1eb69c 100644 --- a/src/Microsoft.AspNetCore.Routing/EndpointDataSourceBuilder.cs +++ b/src/Microsoft.AspNetCore.Routing/IEndpointRouteBuilder.cs @@ -1,15 +1,18 @@ // 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.Builder; namespace Microsoft.AspNetCore.Routing { - public abstract class EndpointDataSourceBuilder + public interface IEndpointRouteBuilder { - public abstract ICollection Endpoints { get; } + IApplicationBuilder CreateApplicationBuilder(); - public abstract IApplicationBuilder CreateApplicationBuilder(); + IServiceProvider ServiceProvider { get; } + + ICollection DataSources { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/ModelEndpointDataSource.cs b/src/Microsoft.AspNetCore.Routing/ModelEndpointDataSource.cs new file mode 100644 index 0000000000..5614310342 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/ModelEndpointDataSource.cs @@ -0,0 +1,68 @@ +// 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.AspNetCore.Http; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Routing +{ + internal class ModelEndpointDataSource : EndpointDataSource + { + private List _endpointConventionBuilders; + + public ModelEndpointDataSource() + { + _endpointConventionBuilders = new List(); + } + + public IEndpointConventionBuilder AddEndpointModel(EndpointModel endpointModel) + { + var builder = new EndpointConventionBuilder(endpointModel); + _endpointConventionBuilders.Add(builder); + + return builder; + } + + public override IChangeToken GetChangeToken() + { + return NullChangeToken.Singleton; + } + + public override IReadOnlyList Endpoints => _endpointConventionBuilders.Select(e => e.Build()).ToArray(); + + // for testing + internal IEnumerable EndpointModels => _endpointConventionBuilders.Select(b => b.EndpointModel); + + private class EndpointConventionBuilder : IEndpointConventionBuilder + { + internal EndpointModel EndpointModel { get; } + + private readonly List> _conventions; + + public EndpointConventionBuilder(EndpointModel endpointModel) + { + EndpointModel = endpointModel; + _conventions = new List>(); + } + + public void Apply(Action convention) + { + _conventions.Add(convention); + } + + public Endpoint Build() + { + foreach (var convention in _conventions) + { + convention(EndpointModel); + } + + return EndpointModel.Build(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/Patterns/RoutePattern.cs b/src/Microsoft.AspNetCore.Routing/Patterns/RoutePattern.cs index 1f1bf7ee0a..6b277f98cd 100644 --- a/src/Microsoft.AspNetCore.Routing/Patterns/RoutePattern.cs +++ b/src/Microsoft.AspNetCore.Routing/Patterns/RoutePattern.cs @@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns return null; } - private string DebuggerToString() + internal string DebuggerToString() { return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString())); } diff --git a/src/Microsoft.AspNetCore.Routing/RouteEndpointBuilder.cs b/src/Microsoft.AspNetCore.Routing/RouteEndpointModel.cs similarity index 82% rename from src/Microsoft.AspNetCore.Routing/RouteEndpointBuilder.cs rename to src/Microsoft.AspNetCore.Routing/RouteEndpointModel.cs index a45d71d391..8021c86b8f 100644 --- a/src/Microsoft.AspNetCore.Routing/RouteEndpointBuilder.cs +++ b/src/Microsoft.AspNetCore.Routing/RouteEndpointModel.cs @@ -6,13 +6,13 @@ using Microsoft.AspNetCore.Routing.Patterns; namespace Microsoft.AspNetCore.Routing { - public sealed class RouteEndpointBuilder : EndpointBuilder + public sealed class RouteEndpointModel : EndpointModel { public RoutePattern RoutePattern { get; set; } public int Order { get; set; } - public RouteEndpointBuilder( + public RouteEndpointModel( RequestDelegate requestDelegate, RoutePattern routePattern, int order) @@ -24,14 +24,14 @@ namespace Microsoft.AspNetCore.Routing public override Endpoint Build() { - var matcherEndpoint = new RouteEndpoint( + var routeEndpoint = new RouteEndpoint( RequestDelegate, RoutePattern, Order, new EndpointMetadataCollection(Metadata), DisplayName); - return matcherEndpoint; + return routeEndpoint; } } } diff --git a/src/Microsoft.AspNetCore.Routing/RouteOptions.cs b/src/Microsoft.AspNetCore.Routing/RouteOptions.cs index efabde03a1..910ec514b7 100644 --- a/src/Microsoft.AspNetCore.Routing/RouteOptions.cs +++ b/src/Microsoft.AspNetCore.Routing/RouteOptions.cs @@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Routing { public class RouteOptions { + public ICollection EndpointDataSources { get; internal set; } + /// /// Gets or sets a value indicating whether all generated paths URLs are lower-case. /// Use to configure the behavior for query strings. diff --git a/src/Microsoft.AspNetCore.Routing/RouteValuesAddressScheme.cs b/src/Microsoft.AspNetCore.Routing/RouteValuesAddressScheme.cs index ae7bd66705..af138df723 100644 --- a/src/Microsoft.AspNetCore.Routing/RouteValuesAddressScheme.cs +++ b/src/Microsoft.AspNetCore.Routing/RouteValuesAddressScheme.cs @@ -14,11 +14,11 @@ namespace Microsoft.AspNetCore.Routing { internal class RouteValuesAddressScheme : IEndpointAddressScheme { - private readonly CompositeEndpointDataSource _dataSource; + private readonly EndpointDataSource _dataSource; private LinkGenerationDecisionTree _allMatchesLinkGenerationTree; private Dictionary> _namedMatchResults; - public RouteValuesAddressScheme(CompositeEndpointDataSource dataSource) + public RouteValuesAddressScheme(EndpointDataSource dataSource) { _dataSource = dataSource; diff --git a/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs b/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs index db27235e70..e399c2bad5 100644 --- a/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs +++ b/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs @@ -29,7 +29,6 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests { // Arrange var expectedContentType = "text/plain"; - var expectedContent = "Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext"; // Act var response = await _client.GetAsync("/"); @@ -39,8 +38,6 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests 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] diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs index 5e7f1964b5..7d0cb9a191 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs @@ -2,13 +2,17 @@ // 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.Threading.Tasks; using Microsoft.AspNetCore.Builder.Internal; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Patterns; +using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; using Xunit; @@ -23,7 +27,7 @@ namespace Microsoft.AspNetCore.Builder var app = new ApplicationBuilder(Mock.Of()); // Act - var ex = Assert.Throws(() => app.UseEndpointRouting()); + var ex = Assert.Throws(() => app.UseEndpointRouting(builder => { })); // Assert Assert.Equal( @@ -58,7 +62,7 @@ namespace Microsoft.AspNetCore.Builder var app = new ApplicationBuilder(services); - app.UseEndpointRouting(); + app.UseEndpointRouting(builder => { }); var appFunc = app.Build(); var httpContext = new DefaultHttpContext(); @@ -75,17 +79,20 @@ namespace Microsoft.AspNetCore.Builder { // Arrange var endpoint = new RouteEndpoint( - TestConstants.EmptyRequestDelegate, - RoutePatternFactory.Parse("{*p}"), - 0, - EndpointMetadataCollection.Empty, - "Test"); + TestConstants.EmptyRequestDelegate, + RoutePatternFactory.Parse("{*p}"), + 0, + EndpointMetadataCollection.Empty, + "Test"); - var services = CreateServices(endpoint); + var services = CreateServices(); var app = new ApplicationBuilder(services); - app.UseEndpointRouting(); + app.UseEndpointRouting(builder => + { + builder.DataSources.Add(new DefaultEndpointDataSource(endpoint)); + }); var appFunc = app.Build(); var httpContext = new DefaultHttpContext(); @@ -126,7 +133,7 @@ namespace Microsoft.AspNetCore.Builder var app = new ApplicationBuilder(services); - app.UseEndpointRouting(); + app.UseEndpointRouting(builder => { }); app.UseEndpoint(); var appFunc = app.Build(); @@ -140,36 +147,83 @@ namespace Microsoft.AspNetCore.Builder } [Fact] - public void UseEndpointRouting_CallWithBuilder_SetsEndpointBuilder() + public void UseEndpointRouting_CallWithBuilder_SetsEndpointDataSource() { // Arrange - var services = CreateServices(); + var matcherEndpointDataSources = new List(); + var matcherFactoryMock = new Mock(); + matcherFactoryMock + .Setup(m => m.CreateMatcher(It.IsAny())) + .Callback((EndpointDataSource arg) => + { + matcherEndpointDataSources.Add(arg); + }) + .Returns(new TestMatcher(false)); + + var services = CreateServices(matcherFactoryMock.Object); var app = new ApplicationBuilder(services); // Act app.UseEndpointRouting(builder => { - builder.MapEndpoint(d => null, "/", "Test endpoint"); + builder.Map("/1", "Test endpoint 1", d => null); + builder.Map("/2", "Test endpoint 2", d => null); }); + app.UseEndpointRouting(builder => + { + builder.Map("/3", "Test endpoint 3", d => null); + builder.Map("/4", "Test endpoint 4", d => null); + }); + + // This triggers the middleware to be created and the matcher factory to be called + // with the datasource we want to test + var requestDelegate = app.Build(); + requestDelegate(new DefaultHttpContext()); + // Assert - var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)services.GetRequiredService(); - var endpointBuilder = Assert.Single(dataSourceBuilder.Endpoints); - Assert.Equal("Test endpoint", endpointBuilder.DisplayName); + Assert.Equal(2, matcherEndpointDataSources.Count); + + // Each middleware has its own endpoints + Assert.Collection(matcherEndpointDataSources[0].Endpoints, + e => Assert.Equal("Test endpoint 1", e.DisplayName), + e => Assert.Equal("Test endpoint 2", e.DisplayName)); + Assert.Collection(matcherEndpointDataSources[1].Endpoints, + e => Assert.Equal("Test endpoint 3", e.DisplayName), + e => Assert.Equal("Test endpoint 4", e.DisplayName)); + + var compositeEndpointBuilder = services.GetRequiredService(); + + // Global middleware has all endpoints + Assert.Collection(compositeEndpointBuilder.Endpoints, + e => Assert.Equal("Test endpoint 1", e.DisplayName), + e => Assert.Equal("Test endpoint 2", e.DisplayName), + e => Assert.Equal("Test endpoint 3", e.DisplayName), + e => Assert.Equal("Test endpoint 4", e.DisplayName)); } - private IServiceProvider CreateServices(params Endpoint[] endpoints) + private IServiceProvider CreateServices() + { + return CreateServices(matcherFactory: null); + } + + private IServiceProvider CreateServices(MatcherFactory matcherFactory) { var services = new ServiceCollection(); + if (matcherFactory != null) + { + services.AddSingleton(matcherFactory); + } + services.AddLogging(); services.AddOptions(); services.AddRouting(); - services.AddSingleton(new DefaultEndpointDataSource(endpoints)); + var serviceProvder = services.BuildServiceProvider(); - return services.BuildServiceProvider(); + return serviceProvder; } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs index 5a425c3075..ad714df63c 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs @@ -14,38 +14,50 @@ namespace Microsoft.AspNetCore.Builder { public class MapEndpointEndpointDataSourceBuilderExtensionsTest { + private ModelEndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder) + { + return Assert.IsType(Assert.Single(endpointRouteBuilder.DataSources)); + } + + private RouteEndpointModel GetRouteEndpointBuilder(IEndpointRouteBuilder endpointRouteBuilder) + { + return Assert.IsType(Assert.Single(GetBuilderEndpointDataSource(endpointRouteBuilder).EndpointModels)); + } + [Fact] public void MapEndpoint_StringPattern_BuildsEndpoint() { // Arrange - var builder = new DefaultEndpointDataSourceBuilder(); + var builder = new DefaultEndpointRouteBuilder(); RequestDelegate requestDelegate = (d) => null; // Act - var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!"); + var endpointBuilder = builder.Map("/", "Display name!", requestDelegate); // Assert - Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); - Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); - Assert.Equal("Display name!", endpointBuilder.DisplayName); - Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + var endpointBuilder1 = GetRouteEndpointBuilder(builder); + + Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate); + Assert.Equal("Display name!", endpointBuilder1.DisplayName); + Assert.Equal("/", endpointBuilder1.RoutePattern.RawText); } [Fact] public void MapEndpoint_TypedPattern_BuildsEndpoint() { // Arrange - var builder = new DefaultEndpointDataSourceBuilder(); + var builder = new DefaultEndpointRouteBuilder(); RequestDelegate requestDelegate = (d) => null; // Act - var endpointBuilder = builder.MapEndpoint(requestDelegate, RoutePatternFactory.Parse("/"), "Display name!"); + var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", requestDelegate); // Assert - Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); - Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); - Assert.Equal("Display name!", endpointBuilder.DisplayName); - Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + var endpointBuilder1 = GetRouteEndpointBuilder(builder); + + Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate); + Assert.Equal("Display name!", endpointBuilder1.DisplayName); + Assert.Equal("/", endpointBuilder1.RoutePattern.RawText); } [Fact] @@ -53,18 +65,18 @@ namespace Microsoft.AspNetCore.Builder { // Arrange var metadata = new object(); - var builder = new DefaultEndpointDataSourceBuilder(); + var builder = new DefaultEndpointRouteBuilder(); RequestDelegate requestDelegate = (d) => null; // Act - var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!", new[] { metadata }); + var endpointBuilder = builder.Map("/", "Display name!", requestDelegate, new[] { metadata }); // Assert - Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); - Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); - Assert.Equal("Display name!", endpointBuilder.DisplayName); - Assert.Equal("/", endpointBuilder.RoutePattern.RawText); - Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata)); + var endpointBuilder1 = GetRouteEndpointBuilder(builder); + Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate); + Assert.Equal("Display name!", endpointBuilder1.DisplayName); + Assert.Equal("/", endpointBuilder1.RoutePattern.RawText); + Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata)); } [Fact] @@ -72,18 +84,18 @@ namespace Microsoft.AspNetCore.Builder { // Arrange var metadata = new object(); - var builder = new DefaultEndpointDataSourceBuilder(); + var builder = new DefaultEndpointRouteBuilder(); RequestDelegate requestDelegate = (d) => null; // Act - var endpointBuilder = builder.MapEndpoint(requestDelegate, RoutePatternFactory.Parse("/"), "Display name!", new[] { metadata }); + var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", requestDelegate, new[] { metadata }); // Assert - Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); - Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); - Assert.Equal("Display name!", endpointBuilder.DisplayName); - Assert.Equal("/", endpointBuilder.RoutePattern.RawText); - Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata)); + var endpointBuilder1 = GetRouteEndpointBuilder(builder); + Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate); + Assert.Equal("Display name!", endpointBuilder1.DisplayName); + Assert.Equal("/", endpointBuilder1.RoutePattern.RawText); + Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata)); } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs index 4f6792b47f..de47be3873 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs @@ -1,11 +1,13 @@ // 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.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.TestObjects; +using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; @@ -259,7 +261,12 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, }); + Action configure = (s) => + { + s.Configure(o => o.LowercaseUrls = true); + }; + + var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act @@ -283,7 +290,12 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}"); - var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, }); + Action configure = (s) => + { + s.Configure(o => o.LowercaseUrls = true); + }; + + var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, }); var httpContext = CreateHttpContext(); // Act @@ -334,7 +346,12 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); - var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, }); + Action configure = (s) => + { + s.Configure(o => o.LowercaseUrls = true); + }; + + var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); // Act @@ -362,8 +379,17 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); + Action configure = (s) => + { + s.Configure(o => + { + o.LowercaseUrls = true; + o.LowercaseQueryStrings = true; + }); + }; + var linkGenerator = CreateLinkGenerator( - new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true }, + configure, endpoints: new[] { endpoint, }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); @@ -387,8 +413,13 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); + Action configure = (s) => + { + s.Configure(o => o.AppendTrailingSlash = true); + }; + var linkGenerator = CreateLinkGenerator( - new RouteOptions() { AppendTrailingSlash = true }, + configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); @@ -412,8 +443,18 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); + Action configure = (s) => + { + s.Configure(o => + { + o.LowercaseUrls = true; + o.LowercaseQueryStrings = true; + o.AppendTrailingSlash = true; + }); + }; + var linkGenerator = CreateLinkGenerator( - new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, AppendTrailingSlash = true }, + configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); @@ -437,8 +478,13 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); + Action configure = (s) => + { + s.Configure(o => o.LowercaseUrls = true); + }; + var linkGenerator = CreateLinkGenerator( - new RouteOptions() { LowercaseUrls = true }, + configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" }); @@ -466,8 +512,13 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); + Action configure = (s) => + { + s.Configure(o => o.LowercaseUrls = false); + }; + var linkGenerator = CreateLinkGenerator( - new RouteOptions() { LowercaseUrls = false }, + configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" }); @@ -494,8 +545,17 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); + Action configure = (s) => + { + s.Configure(o => + { + o.LowercaseUrls = true; + o.LowercaseQueryStrings = true; + }); + }; + var linkGenerator = CreateLinkGenerator( - new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true }, + configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); @@ -523,8 +583,17 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); + Action configure = (s) => + { + s.Configure(o => + { + o.LowercaseUrls = false; + o.LowercaseQueryStrings = false; + }); + }; + var linkGenerator = CreateLinkGenerator( - new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = false }, + configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); @@ -552,8 +621,13 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); + Action configure = (s) => + { + s.Configure(o => o.AppendTrailingSlash = false); + }; + var linkGenerator = CreateLinkGenerator( - new RouteOptions() { AppendTrailingSlash = false }, + configure, endpoints: new[] { endpoint }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs index 3c20c704a0..153c189ae6 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs @@ -177,10 +177,15 @@ namespace Microsoft.AspNetCore.Routing var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), }); var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), }); - var routeOptions = new RouteOptions(); - routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); + Action configureServices = s => + { + s.Configure(o => + { + o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); + }); + }; - var linkGenerator = CreateLinkGenerator(routeOptions: routeOptions, configureServices: null, endpoint1, endpoint2); + var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2); // Act var path = linkGenerator.GetPathByAddress( @@ -198,10 +203,15 @@ namespace Microsoft.AspNetCore.Routing var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), }); var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), }); - var routeOptions = new RouteOptions(); - routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); + Action configureServices = s => + { + s.Configure(o => + { + o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); + }); + }; - var linkGenerator = CreateLinkGenerator(routeOptions: routeOptions, configureServices: null, endpoint1, endpoint2); + var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2); // Act var path = linkGenerator.GetPathByAddress( @@ -313,15 +323,15 @@ namespace Microsoft.AspNetCore.Routing // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}"); - var routeOptions = new RouteOptions(); - routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform); - Action configure = (s) => { - s.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform()); + s.Configure(o => + { + o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform); + }); }; - var linkGenerator = CreateLinkGenerator(routeOptions, configure, endpoint); + var linkGenerator = CreateLinkGenerator(configure, endpoint); // Act var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test" }); @@ -336,15 +346,15 @@ namespace Microsoft.AspNetCore.Routing // Arrange var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", policies: new { c = new UpperCaseParameterTransform(), }); - var routeOptions = new RouteOptions(); - routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform); - Action configure = (s) => { - s.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform()); + s.Configure(o => + { + o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform); + }); }; - var linkGenerator = CreateLinkGenerator(routeOptions, configure, endpoint); + var linkGenerator = CreateLinkGenerator(configure, endpoint); // Act var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test", c = "hithere", }); @@ -707,9 +717,9 @@ namespace Microsoft.AspNetCore.Routing private class IntAddressScheme : IEndpointAddressScheme { - private readonly CompositeEndpointDataSource _dataSource; + private readonly EndpointDataSource _dataSource; - public IntAddressScheme(CompositeEndpointDataSource dataSource) + public IntAddressScheme(EndpointDataSource dataSource) { _dataSource = dataSource; } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/EndpointRoutingMiddlewareTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/EndpointRoutingMiddlewareTest.cs index 48861da09b..df5c703fb2 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/EndpointRoutingMiddlewareTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/EndpointRoutingMiddlewareTest.cs @@ -151,10 +151,9 @@ namespace Microsoft.AspNetCore.Routing logger = logger ?? new Logger(NullLoggerFactory.Instance); matcherFactory = matcherFactory ?? new TestMatcherFactory(true); - var options = Options.Create(new EndpointOptions()); var middleware = new EndpointRoutingMiddleware( matcherFactory, - new CompositeEndpointDataSource(Array.Empty()), + new DefaultEndpointDataSource(), logger, next); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorTestBase.cs b/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorTestBase.cs index a2a9c22db9..e9c3880262 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorTestBase.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/LinkGeneratorTestBase.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.Options; using System; +using System.Collections.Generic; namespace Microsoft.AspNetCore.Routing { @@ -44,29 +45,22 @@ namespace Microsoft.AspNetCore.Routing private protected DefaultLinkGenerator CreateLinkGenerator(params Endpoint[] endpoints) { - return CreateLinkGenerator(routeOptions: null, endpoints); - } - - private protected DefaultLinkGenerator CreateLinkGenerator(RouteOptions routeOptions, params Endpoint[] endpoints) - { - return CreateLinkGenerator(routeOptions, configureServices: null, endpoints); + return CreateLinkGenerator(configureServices: null, endpoints); } private protected DefaultLinkGenerator CreateLinkGenerator( - RouteOptions routeOptions, Action configureServices, params Endpoint[] endpoints) { - return CreateLinkGenerator(routeOptions, configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty()) }); + return CreateLinkGenerator(configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty()) }); } private protected DefaultLinkGenerator CreateLinkGenerator(EndpointDataSource[] dataSources) { - return CreateLinkGenerator(routeOptions: null, configureServices: null, dataSources); + return CreateLinkGenerator(configureServices: null, dataSources); } private protected DefaultLinkGenerator CreateLinkGenerator( - RouteOptions routeOptions, Action configureServices, EndpointDataSource[] dataSources) { @@ -74,25 +68,25 @@ namespace Microsoft.AspNetCore.Routing AddAdditionalServices(services); configureServices?.Invoke(services); - routeOptions = routeOptions ?? new RouteOptions(); - dataSources = dataSources ?? Array.Empty(); - - services.Configure((o) => + services.Configure(o => { - for (var i = 0; i < dataSources.Length; i++) + if (dataSources != null) { - o.DataSources.Add(dataSources[i]); + foreach (var dataSource in dataSources) + { + o.EndpointDataSources.Add(dataSource); + } } }); - var options = Options.Create(routeOptions); var serviceProvider = services.BuildServiceProvider(); + var routeOptions = serviceProvider.GetRequiredService>(); return new DefaultLinkGenerator( - new DefaultParameterPolicyFactory(options, serviceProvider), - serviceProvider.GetRequiredService(), + new DefaultParameterPolicyFactory(routeOptions, serviceProvider), + new CompositeEndpointDataSource(routeOptions.Value.EndpointDataSources), new DefaultObjectPool(new UriBuilderContextPooledObjectPolicy()), - options, + routeOptions, NullLogger.Instance, serviceProvider); } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointBuilderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/RouteEndpointModelTest.cs similarity index 80% rename from test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointBuilderTest.cs rename to test/Microsoft.AspNetCore.Routing.Tests/RouteEndpointModelTest.cs index 3e93c1d3db..cf75832ddd 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/RouteEndpointModelTest.cs @@ -8,18 +8,18 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Patterns; using Xunit; -namespace Microsoft.AspNetCore.Routing.Matching +namespace Microsoft.AspNetCore.Routing { - public class MatcherEndpointBuilderTest + public class RouteEndpointModelTest { [Fact] public void Build_AllValuesSet_EndpointCreated() { const int defaultOrder = 0; - object metadata = new object(); + var metadata = new object(); RequestDelegate requestDelegate = (d) => null; - var builder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder) + var builder = new RouteEndpointModel(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder) { DisplayName = "Display name!", Metadata = { metadata }