Endpoint routing startup experience (#889)

This commit is contained in:
James Newton-King 2018-10-23 13:54:36 +13:00 committed by GitHub
parent be0e602d2f
commit 0ef4b4173c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 826 additions and 329 deletions

View File

@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Benchmarks namespace Benchmarks
{ {
@ -18,8 +17,13 @@ namespace Benchmarks
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddRouting(); services.AddRouting();
}
var endpointDataSource = new DefaultEndpointDataSource(new[] public void Configure(IApplicationBuilder app)
{
app.UseEndpointRouting(builder =>
{
var endpointDataSource = new DefaultEndpointDataSource(new[]
{ {
new RouteEndpoint( new RouteEndpoint(
requestDelegate: (httpContext) => requestDelegate: (httpContext) =>
@ -37,12 +41,8 @@ namespace Benchmarks
displayName: "Plaintext"), displayName: "Plaintext"),
}); });
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource)); builder.DataSources.Add(endpointDataSource);
} });
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
{
app.UseEndpointRouting();
app.UseEndpoint(); app.UseEndpoint();
} }

View File

@ -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<AuthorizationMiddleware>();
}
}
}

View File

@ -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<string> roles)
{
if (roles == null)
{
throw new ArgumentNullException(nameof(roles));
}
Roles = roles.ToArray();
}
public IReadOnlyList<string> Roles { get; }
}
}

View File

@ -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<AuthorizationMiddleware> 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<IEndpointFeature>()?.Endpoint;
if (endpoint != null)
{
var metadata = endpoint.Metadata.GetMetadata<AuthorizationMetadata>();
// 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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -11,9 +11,9 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Builder 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) if (builder == null)
{ {
@ -24,10 +24,10 @@ namespace Microsoft.AspNetCore.Builder
.UseHello(greeter) .UseHello(greeter)
.Build(); .Build();
return builder.MapEndpoint( return builder.Map(
pipeline,
template, template,
"Hello"); "Hello " + greeter,
pipeline);
} }
} }
} }

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp2.2</TargetFrameworks> <TargetFrameworks>netcoreapp2.2</TargetFrameworks>

View File

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -36,19 +37,29 @@ namespace RoutingSample.Web
{ {
builder.MapHello("/helloworld", "World"); builder.MapHello("/helloworld", "World");
builder.MapEndpoint( builder.MapHello("/helloworld-secret", "Secret World")
.RequireAuthorization("swordfish");
builder.MapGet(
"/",
(httpContext) => (httpContext) =>
{ {
var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
var sb = new StringBuilder();
sb.AppendLine("Endpoints:");
foreach (var endpoint in dataSource.Endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
{
sb.AppendLine($"- {endpoint.RoutePattern.RawText}");
}
var response = httpContext.Response; var response = httpContext.Response;
var payloadLength = _homePayload.Length;
response.StatusCode = 200; response.StatusCode = 200;
response.ContentType = "text/plain"; response.ContentType = "text/plain";
response.ContentLength = payloadLength; return response.WriteAsync(sb.ToString());
return response.Body.WriteAsync(_homePayload, 0, payloadLength); });
}, builder.MapGet(
"/", "/plaintext",
"Home");
builder.MapEndpoint(
(httpContext) => (httpContext) =>
{ {
var response = httpContext.Response; var response = httpContext.Response;
@ -57,30 +68,28 @@ namespace RoutingSample.Web
response.ContentType = "text/plain"; response.ContentType = "text/plain";
response.ContentLength = payloadLength; response.ContentLength = payloadLength;
return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength); return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength);
}, });
"/plaintext", builder.MapGet(
"Plaintext"); "/withconstraints/{id:endsWith(_001)}",
builder.MapEndpoint(
(httpContext) => (httpContext) =>
{ {
var response = httpContext.Response; var response = httpContext.Response;
response.StatusCode = 200; response.StatusCode = 200;
response.ContentType = "text/plain"; response.ContentType = "text/plain";
return response.WriteAsync("WithConstraints"); return response.WriteAsync("WithConstraints");
}, });
"/withconstraints/{id:endsWith(_001)}", builder.MapGet(
"withconstraints"); "/withoptionalconstraints/{id:endsWith(_001)?}",
builder.MapEndpoint(
(httpContext) => (httpContext) =>
{ {
var response = httpContext.Response; var response = httpContext.Response;
response.StatusCode = 200; response.StatusCode = 200;
response.ContentType = "text/plain"; response.ContentType = "text/plain";
return response.WriteAsync("withoptionalconstraints"); return response.WriteAsync("withoptionalconstraints");
}, });
"/withoptionalconstraints/{id:endsWith(_001)?}", builder.MapGet(
"withoptionalconstraints"); "/graph",
builder.MapEndpoint( "DFA Graph",
(httpContext) => (httpContext) =>
{ {
using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true)) using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
@ -91,11 +100,9 @@ namespace RoutingSample.Web
} }
return Task.CompletedTask; return Task.CompletedTask;
}, });
"/graph", builder.MapGet(
"DFA Graph", "/WithSingleAsteriskCatchAll/{*path}",
new HttpMethodMetadata(new[] { "GET", }));
builder.MapEndpoint(
(httpContext) => (httpContext) =>
{ {
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>(); var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
@ -106,10 +113,9 @@ namespace RoutingSample.Web
return response.WriteAsync( return response.WriteAsync(
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { })); "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { }));
}, },
"/WithSingleAsteriskCatchAll/{*path}",
"WithSingleAsteriskCatchAll",
new RouteValuesAddressMetadata(routeName: "WithSingleAsteriskCatchAll", requiredValues: new RouteValueDictionary())); new RouteValuesAddressMetadata(routeName: "WithSingleAsteriskCatchAll", requiredValues: new RouteValueDictionary()));
builder.MapEndpoint( builder.MapGet(
"/WithDoubleAsteriskCatchAll/{**path}",
(httpContext) => (httpContext) =>
{ {
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>(); var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
@ -120,13 +126,13 @@ namespace RoutingSample.Web
return response.WriteAsync( return response.WriteAsync(
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { })); "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { }));
}, },
"/WithDoubleAsteriskCatchAll/{**path}",
"WithDoubleAsteriskCatchAll",
new RouteValuesAddressMetadata(routeName: "WithDoubleAsteriskCatchAll", requiredValues: new RouteValueDictionary())); new RouteValuesAddressMetadata(routeName: "WithDoubleAsteriskCatchAll", requiredValues: new RouteValueDictionary()));
}); });
app.UseStaticFiles(); app.UseStaticFiles();
app.UseAuthorization();
app.UseEndpoint(); app.UseEndpoint();
} }
} }

View File

@ -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<string> 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<string> httpMethods,
params object[] metadata)
{
if (httpMethods == null)
{
throw new ArgumentNullException(nameof(httpMethods));
}
var resolvedMetadata = new List<object>();
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<ModelEndpointDataSource>().FirstOrDefault();
if (modelEndpointDataSource == null)
{
modelEndpointDataSource = new ModelEndpointDataSource();
builder.DataSources.Add(modelEndpointDataSource);
}
return modelEndpointDataSource.AddEndpointModel(routeEndpointModel);
}
#endregion
}
}

View File

@ -2,10 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Builder 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 // Property key is used by MVC package to check that routing is registered
private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered"; private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered";
public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder) public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
{ {
return builder.UseEndpointRouting(null); if (configure == null)
} {
throw new ArgumentNullException(nameof(configure));
}
public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder, Action<EndpointDataSourceBuilder> configure)
{
VerifyRoutingIsRegistered(builder); VerifyRoutingIsRegistered(builder);
if (configure != null) var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
EndpointDataSource middlewareEndpointDataSource;
var endpointRouteBuilder = builder.ApplicationServices.GetRequiredService<IEndpointRouteBuilder>();
if (endpointRouteBuilder is DefaultEndpointRouteBuilder defaultEndpointRouteBuilder)
{ {
var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)builder.ApplicationServices.GetRequiredService<EndpointDataSourceBuilder>(); defaultEndpointRouteBuilder.ApplicationBuilder = builder;
dataSourceBuilder.ApplicationBuilder = builder;
configure(dataSourceBuilder);
} }
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; builder.Properties[EndpointRoutingRegisteredKey] = true;
return builder.UseMiddleware<EndpointRoutingMiddleware>(); return builder.UseMiddleware<EndpointRoutingMiddleware>(middlewareEndpointDataSource);
} }
public static IApplicationBuilder UseEndpoint(this IApplicationBuilder builder) public static IApplicationBuilder UseEndpoint(this IApplicationBuilder builder)

View File

@ -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;
}
}
}

View File

@ -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<Endpoint> Endpoints => _builder.Endpoints.Select(b => b.Build()).ToArray();
}
}

View File

@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -18,24 +20,49 @@ namespace Microsoft.AspNetCore.Routing
[DebuggerDisplay("{DebuggerDisplayString,nq}")] [DebuggerDisplay("{DebuggerDisplayString,nq}")]
public sealed class CompositeEndpointDataSource : EndpointDataSource public sealed class CompositeEndpointDataSource : EndpointDataSource
{ {
private readonly EndpointDataSource[] _dataSources;
private readonly object _lock; private readonly object _lock;
private readonly ICollection<EndpointDataSource> _dataSources;
private IReadOnlyList<Endpoint> _endpoints; private IReadOnlyList<Endpoint> _endpoints;
private IChangeToken _consumerChangeToken; private IChangeToken _consumerChangeToken;
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
internal CompositeEndpointDataSource(IEnumerable<EndpointDataSource> dataSources) private CompositeEndpointDataSource()
{ {
if (dataSources == null)
{
throw new ArgumentNullException(nameof(dataSources));
}
CreateChangeToken(); CreateChangeToken();
_dataSources = dataSources.ToArray();
_lock = new object(); _lock = new object();
} }
internal CompositeEndpointDataSource(ObservableCollection<EndpointDataSource> dataSources) : this()
{
dataSources.CollectionChanged += OnDataSourcesChanged;
_dataSources = dataSources;
}
public CompositeEndpointDataSource(IEnumerable<EndpointDataSource> endpointDataSources) : this()
{
_dataSources = new List<EndpointDataSource>();
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<EndpointDataSource> DataSources => _dataSources;
/// <summary> /// <summary>
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/> /// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
/// instances. /// instances.

View File

@ -3,16 +3,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
{ {
internal class ConfigureEndpointOptions : IConfigureOptions<EndpointOptions> internal class ConfigureRouteOptions : IConfigureOptions<RouteOptions>
{ {
private readonly IEnumerable<EndpointDataSource> _dataSources; private readonly ICollection<EndpointDataSource> _dataSources;
public ConfigureEndpointOptions(IEnumerable<EndpointDataSource> dataSources) public ConfigureRouteOptions(ICollection<EndpointDataSource> dataSources)
{ {
if (dataSources == null) if (dataSources == null)
{ {
@ -22,17 +23,14 @@ namespace Microsoft.Extensions.DependencyInjection
_dataSources = dataSources; _dataSources = dataSources;
} }
public void Configure(EndpointOptions options) public void Configure(RouteOptions options)
{ {
if (options == null) if (options == null)
{ {
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
foreach (var dataSource in _dataSources) options.EndpointDataSources = _dataSources;
{
options.DataSources.Add(dataSource);
}
} }
} }
} }

View File

@ -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<EndpointBuilder> Endpoints { get; } = new List<EndpointBuilder>();
public override IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New();
}
}

View File

@ -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<EndpointDataSource>();
}
public IApplicationBuilder ApplicationBuilder { get; set; }
public IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New();
public ICollection<EndpointDataSource> DataSources { get; }
public IServiceProvider ServiceProvider => ApplicationBuilder.ApplicationServices;
}
}

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Routing
public DefaultLinkGenerator( public DefaultLinkGenerator(
ParameterPolicyFactory parameterPolicyFactory, ParameterPolicyFactory parameterPolicyFactory,
CompositeEndpointDataSource dataSource, EndpointDataSource dataSource,
ObjectPool<UriBuildingContext> uriBuildingContextPool, ObjectPool<UriBuildingContext> uriBuildingContextPool,
IOptions<RouteOptions> routeOptions, IOptions<RouteOptions> routeOptions,
ILogger<DefaultLinkGenerator> logger, ILogger<DefaultLinkGenerator> logger,

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.ObjectModel;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Matching;
@ -49,21 +50,22 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton(typeof(RoutingMarkerService)); services.TryAddSingleton(typeof(RoutingMarkerService));
// Collect all data sources from DI. // Setup global collection of endpoint data sources
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<EndpointOptions>, ConfigureEndpointOptions>()); var dataSources = new ObservableCollection<EndpointDataSource>();
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, ConfigureRouteOptions>(
serviceProvider => new ConfigureRouteOptions(dataSources)));
// Allow global access to the list of endpoints. // Allow global access to the list of endpoints.
services.TryAddSingleton<CompositeEndpointDataSource>(s => services.TryAddSingleton<EndpointDataSource>(s =>
{ {
var options = s.GetRequiredService<IOptions<EndpointOptions>>(); // Call internal ctor and pass global collection
return new CompositeEndpointDataSource(options.Value.DataSources); return new CompositeEndpointDataSource(dataSources);
}); });
// //
// Endpoint Infrastructure // Endpoint Infrastructure
// //
services.TryAddSingleton<EndpointDataSource, BuilderEndpointDataSource>(); services.TryAddTransient<IEndpointRouteBuilder, DefaultEndpointRouteBuilder>();
services.TryAddSingleton<EndpointDataSourceBuilder, DefaultEndpointDataSourceBuilder>();
// //
// Default matcher implementation // Default matcher implementation

View File

@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
public abstract class EndpointBuilder public abstract class EndpointModel
{ {
public RequestDelegate RequestDelegate { get; set; } public RequestDelegate RequestDelegate { get; set; }

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Routing
{ {
private readonly DataSourceDependentCache<Dictionary<string, Endpoint[]>> _cache; private readonly DataSourceDependentCache<Dictionary<string, Endpoint[]>> _cache;
public EndpointNameAddressScheme(CompositeEndpointDataSource dataSource) public EndpointNameAddressScheme(EndpointDataSource dataSource)
{ {
_cache = new DataSourceDependentCache<Dictionary<string, Endpoint[]>>(dataSource, Initialize); _cache = new DataSourceDependentCache<Dictionary<string, Endpoint[]>>(dataSource, Initialize);
} }

View File

@ -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<EndpointDataSource> DataSources { get; } = new List<EndpointDataSource>();
}
}

View File

@ -16,14 +16,14 @@ namespace Microsoft.AspNetCore.Routing
{ {
private readonly MatcherFactory _matcherFactory; private readonly MatcherFactory _matcherFactory;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly CompositeEndpointDataSource _endpointDataSource; private readonly EndpointDataSource _endpointDataSource;
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private Task<Matcher> _initializationTask; private Task<Matcher> _initializationTask;
public EndpointRoutingMiddleware( public EndpointRoutingMiddleware(
MatcherFactory matcherFactory, MatcherFactory matcherFactory,
CompositeEndpointDataSource endpointDataSource, EndpointDataSource endpointDataSource,
ILogger<EndpointRoutingMiddleware> logger, ILogger<EndpointRoutingMiddleware> logger,
RequestDelegate next) RequestDelegate next)
{ {

View File

@ -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<EndpointModel> convention);
}
}

View File

@ -1,15 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Collections.Generic;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
public abstract class EndpointDataSourceBuilder public interface IEndpointRouteBuilder
{ {
public abstract ICollection<EndpointBuilder> Endpoints { get; } IApplicationBuilder CreateApplicationBuilder();
public abstract IApplicationBuilder CreateApplicationBuilder(); IServiceProvider ServiceProvider { get; }
ICollection<EndpointDataSource> DataSources { get; }
} }
} }

View File

@ -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<EndpointConventionBuilder> _endpointConventionBuilders;
public ModelEndpointDataSource()
{
_endpointConventionBuilders = new List<EndpointConventionBuilder>();
}
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<Endpoint> Endpoints => _endpointConventionBuilders.Select(e => e.Build()).ToArray();
// for testing
internal IEnumerable<EndpointModel> EndpointModels => _endpointConventionBuilders.Select(b => b.EndpointModel);
private class EndpointConventionBuilder : IEndpointConventionBuilder
{
internal EndpointModel EndpointModel { get; }
private readonly List<Action<EndpointModel>> _conventions;
public EndpointConventionBuilder(EndpointModel endpointModel)
{
EndpointModel = endpointModel;
_conventions = new List<Action<EndpointModel>>();
}
public void Apply(Action<EndpointModel> convention)
{
_conventions.Add(convention);
}
public Endpoint Build()
{
foreach (var convention in _conventions)
{
convention(EndpointModel);
}
return EndpointModel.Build();
}
}
}
}

View File

@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
return null; return null;
} }
private string DebuggerToString() internal string DebuggerToString()
{ {
return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString())); return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
} }

View File

@ -6,13 +6,13 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
public sealed class RouteEndpointBuilder : EndpointBuilder public sealed class RouteEndpointModel : EndpointModel
{ {
public RoutePattern RoutePattern { get; set; } public RoutePattern RoutePattern { get; set; }
public int Order { get; set; } public int Order { get; set; }
public RouteEndpointBuilder( public RouteEndpointModel(
RequestDelegate requestDelegate, RequestDelegate requestDelegate,
RoutePattern routePattern, RoutePattern routePattern,
int order) int order)
@ -24,14 +24,14 @@ namespace Microsoft.AspNetCore.Routing
public override Endpoint Build() public override Endpoint Build()
{ {
var matcherEndpoint = new RouteEndpoint( var routeEndpoint = new RouteEndpoint(
RequestDelegate, RequestDelegate,
RoutePattern, RoutePattern,
Order, Order,
new EndpointMetadataCollection(Metadata), new EndpointMetadataCollection(Metadata),
DisplayName); DisplayName);
return matcherEndpoint; return routeEndpoint;
} }
} }
} }

View File

@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Routing
{ {
public class RouteOptions public class RouteOptions
{ {
public ICollection<EndpointDataSource> EndpointDataSources { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether all generated paths URLs are lower-case. /// Gets or sets a value indicating whether all generated paths URLs are lower-case.
/// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings. /// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.

View File

@ -14,11 +14,11 @@ namespace Microsoft.AspNetCore.Routing
{ {
internal class RouteValuesAddressScheme : IEndpointAddressScheme<RouteValuesAddress> internal class RouteValuesAddressScheme : IEndpointAddressScheme<RouteValuesAddress>
{ {
private readonly CompositeEndpointDataSource _dataSource; private readonly EndpointDataSource _dataSource;
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree; private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
private Dictionary<string, List<OutboundMatchResult>> _namedMatchResults; private Dictionary<string, List<OutboundMatchResult>> _namedMatchResults;
public RouteValuesAddressScheme(CompositeEndpointDataSource dataSource) public RouteValuesAddressScheme(EndpointDataSource dataSource)
{ {
_dataSource = dataSource; _dataSource = dataSource;

View File

@ -29,7 +29,6 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
{ {
// Arrange // Arrange
var expectedContentType = "text/plain"; var expectedContentType = "text/plain";
var expectedContent = "Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext";
// Act // Act
var response = await _client.GetAsync("/"); var response = await _client.GetAsync("/");
@ -39,8 +38,6 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
Assert.NotNull(response.Content); Assert.NotNull(response.Content);
Assert.NotNull(response.Content.Headers.ContentType); Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType); Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
var actualContent = await response.Content.ReadAsStringAsync();
Assert.Equal(expectedContent, actualContent);
} }
[Fact] [Fact]

View File

@ -2,13 +2,17 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder.Internal; using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Moq; using Moq;
using Xunit; using Xunit;
@ -23,7 +27,7 @@ namespace Microsoft.AspNetCore.Builder
var app = new ApplicationBuilder(Mock.Of<IServiceProvider>()); var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
// Act // Act
var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpointRouting()); var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpointRouting(builder => { }));
// Assert // Assert
Assert.Equal( Assert.Equal(
@ -58,7 +62,7 @@ namespace Microsoft.AspNetCore.Builder
var app = new ApplicationBuilder(services); var app = new ApplicationBuilder(services);
app.UseEndpointRouting(); app.UseEndpointRouting(builder => { });
var appFunc = app.Build(); var appFunc = app.Build();
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
@ -75,17 +79,20 @@ namespace Microsoft.AspNetCore.Builder
{ {
// Arrange // Arrange
var endpoint = new RouteEndpoint( var endpoint = new RouteEndpoint(
TestConstants.EmptyRequestDelegate, TestConstants.EmptyRequestDelegate,
RoutePatternFactory.Parse("{*p}"), RoutePatternFactory.Parse("{*p}"),
0, 0,
EndpointMetadataCollection.Empty, EndpointMetadataCollection.Empty,
"Test"); "Test");
var services = CreateServices(endpoint); var services = CreateServices();
var app = new ApplicationBuilder(services); var app = new ApplicationBuilder(services);
app.UseEndpointRouting(); app.UseEndpointRouting(builder =>
{
builder.DataSources.Add(new DefaultEndpointDataSource(endpoint));
});
var appFunc = app.Build(); var appFunc = app.Build();
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
@ -126,7 +133,7 @@ namespace Microsoft.AspNetCore.Builder
var app = new ApplicationBuilder(services); var app = new ApplicationBuilder(services);
app.UseEndpointRouting(); app.UseEndpointRouting(builder => { });
app.UseEndpoint(); app.UseEndpoint();
var appFunc = app.Build(); var appFunc = app.Build();
@ -140,36 +147,83 @@ namespace Microsoft.AspNetCore.Builder
} }
[Fact] [Fact]
public void UseEndpointRouting_CallWithBuilder_SetsEndpointBuilder() public void UseEndpointRouting_CallWithBuilder_SetsEndpointDataSource()
{ {
// Arrange // Arrange
var services = CreateServices(); var matcherEndpointDataSources = new List<EndpointDataSource>();
var matcherFactoryMock = new Mock<MatcherFactory>();
matcherFactoryMock
.Setup(m => m.CreateMatcher(It.IsAny<EndpointDataSource>()))
.Callback((EndpointDataSource arg) =>
{
matcherEndpointDataSources.Add(arg);
})
.Returns(new TestMatcher(false));
var services = CreateServices(matcherFactoryMock.Object);
var app = new ApplicationBuilder(services); var app = new ApplicationBuilder(services);
// Act // Act
app.UseEndpointRouting(builder => 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 // Assert
var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)services.GetRequiredService<EndpointDataSourceBuilder>(); Assert.Equal(2, matcherEndpointDataSources.Count);
var endpointBuilder = Assert.Single(dataSourceBuilder.Endpoints);
Assert.Equal("Test endpoint", endpointBuilder.DisplayName); // 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<EndpointDataSource>();
// 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(); var services = new ServiceCollection();
if (matcherFactory != null)
{
services.AddSingleton<MatcherFactory>(matcherFactory);
}
services.AddLogging(); services.AddLogging();
services.AddOptions(); services.AddOptions();
services.AddRouting(); services.AddRouting();
services.AddSingleton<EndpointDataSource>(new DefaultEndpointDataSource(endpoints)); var serviceProvder = services.BuildServiceProvider();
return services.BuildServiceProvider(); return serviceProvder;
} }
} }
} }

View File

@ -14,38 +14,50 @@ namespace Microsoft.AspNetCore.Builder
{ {
public class MapEndpointEndpointDataSourceBuilderExtensionsTest public class MapEndpointEndpointDataSourceBuilderExtensionsTest
{ {
private ModelEndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder)
{
return Assert.IsType<ModelEndpointDataSource>(Assert.Single(endpointRouteBuilder.DataSources));
}
private RouteEndpointModel GetRouteEndpointBuilder(IEndpointRouteBuilder endpointRouteBuilder)
{
return Assert.IsType<RouteEndpointModel>(Assert.Single(GetBuilderEndpointDataSource(endpointRouteBuilder).EndpointModels));
}
[Fact] [Fact]
public void MapEndpoint_StringPattern_BuildsEndpoint() public void MapEndpoint_StringPattern_BuildsEndpoint()
{ {
// Arrange // Arrange
var builder = new DefaultEndpointDataSourceBuilder(); var builder = new DefaultEndpointRouteBuilder();
RequestDelegate requestDelegate = (d) => null; RequestDelegate requestDelegate = (d) => null;
// Act // Act
var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!"); var endpointBuilder = builder.Map("/", "Display name!", requestDelegate);
// Assert // Assert
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); var endpointBuilder1 = GetRouteEndpointBuilder(builder);
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate);
Assert.Equal("Display name!", endpointBuilder.DisplayName); Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
Assert.Equal("/", endpointBuilder.RoutePattern.RawText); Assert.Equal("Display name!", endpointBuilder1.DisplayName);
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
} }
[Fact] [Fact]
public void MapEndpoint_TypedPattern_BuildsEndpoint() public void MapEndpoint_TypedPattern_BuildsEndpoint()
{ {
// Arrange // Arrange
var builder = new DefaultEndpointDataSourceBuilder(); var builder = new DefaultEndpointRouteBuilder();
RequestDelegate requestDelegate = (d) => null; RequestDelegate requestDelegate = (d) => null;
// Act // Act
var endpointBuilder = builder.MapEndpoint(requestDelegate, RoutePatternFactory.Parse("/"), "Display name!"); var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", requestDelegate);
// Assert // Assert
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); var endpointBuilder1 = GetRouteEndpointBuilder(builder);
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate);
Assert.Equal("Display name!", endpointBuilder.DisplayName); Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
Assert.Equal("/", endpointBuilder.RoutePattern.RawText); Assert.Equal("Display name!", endpointBuilder1.DisplayName);
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
} }
[Fact] [Fact]
@ -53,18 +65,18 @@ namespace Microsoft.AspNetCore.Builder
{ {
// Arrange // Arrange
var metadata = new object(); var metadata = new object();
var builder = new DefaultEndpointDataSourceBuilder(); var builder = new DefaultEndpointRouteBuilder();
RequestDelegate requestDelegate = (d) => null; RequestDelegate requestDelegate = (d) => null;
// Act // Act
var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!", new[] { metadata }); var endpointBuilder = builder.Map("/", "Display name!", requestDelegate, new[] { metadata });
// Assert // Assert
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); var endpointBuilder1 = GetRouteEndpointBuilder(builder);
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
Assert.Equal("Display name!", endpointBuilder.DisplayName); Assert.Equal("Display name!", endpointBuilder1.DisplayName);
Assert.Equal("/", endpointBuilder.RoutePattern.RawText); Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata)); Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata));
} }
[Fact] [Fact]
@ -72,18 +84,18 @@ namespace Microsoft.AspNetCore.Builder
{ {
// Arrange // Arrange
var metadata = new object(); var metadata = new object();
var builder = new DefaultEndpointDataSourceBuilder(); var builder = new DefaultEndpointRouteBuilder();
RequestDelegate requestDelegate = (d) => null; RequestDelegate requestDelegate = (d) => null;
// Act // 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
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); var endpointBuilder1 = GetRouteEndpointBuilder(builder);
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
Assert.Equal("Display name!", endpointBuilder.DisplayName); Assert.Equal("Display name!", endpointBuilder1.DisplayName);
Assert.Equal("/", endpointBuilder.RoutePattern.RawText); Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata)); Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata));
} }
} }
} }

View File

@ -1,11 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;
@ -259,7 +261,12 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, }); Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
};
var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
// Act // Act
@ -283,7 +290,12 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}"); var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, }); Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
};
var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
var httpContext = CreateHttpContext(); var httpContext = CreateHttpContext();
// Act // Act
@ -334,7 +346,12 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, }); Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
};
var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
// Act // Act
@ -362,8 +379,17 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o =>
{
o.LowercaseUrls = true;
o.LowercaseQueryStrings = true;
});
};
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true }, configure,
endpoints: new[] { endpoint, }); endpoints: new[] { endpoint, });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@ -387,8 +413,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o => o.AppendTrailingSlash = true);
};
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { AppendTrailingSlash = true }, configure,
endpoints: new[] { endpoint }); endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@ -412,8 +443,18 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o =>
{
o.LowercaseUrls = true;
o.LowercaseQueryStrings = true;
o.AppendTrailingSlash = true;
});
};
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, AppendTrailingSlash = true }, configure,
endpoints: new[] { endpoint }); endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@ -437,8 +478,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
};
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true }, configure,
endpoints: new[] { endpoint }); endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
@ -466,8 +512,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o => o.LowercaseUrls = false);
};
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = false }, configure,
endpoints: new[] { endpoint }); endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
@ -494,8 +545,17 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o =>
{
o.LowercaseUrls = true;
o.LowercaseQueryStrings = true;
});
};
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true }, configure,
endpoints: new[] { endpoint }); endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@ -523,8 +583,17 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o =>
{
o.LowercaseUrls = false;
o.LowercaseQueryStrings = false;
});
};
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = false }, configure,
endpoints: new[] { endpoint }); endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@ -552,8 +621,13 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
Action<IServiceCollection> configure = (s) =>
{
s.Configure<RouteOptions>(o => o.AppendTrailingSlash = false);
};
var linkGenerator = CreateLinkGenerator( var linkGenerator = CreateLinkGenerator(
new RouteOptions() { AppendTrailingSlash = false }, configure,
endpoints: new[] { endpoint }); endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" }); var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });

View File

@ -177,10 +177,15 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), }); 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 endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
var routeOptions = new RouteOptions(); Action<IServiceCollection> configureServices = s =>
routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); {
s.Configure<RouteOptions>(o =>
{
o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});
};
var linkGenerator = CreateLinkGenerator(routeOptions: routeOptions, configureServices: null, endpoint1, endpoint2); var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
// Act // Act
var path = linkGenerator.GetPathByAddress( 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 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 endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
var routeOptions = new RouteOptions(); Action<IServiceCollection> configureServices = s =>
routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); {
s.Configure<RouteOptions>(o =>
{
o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});
};
var linkGenerator = CreateLinkGenerator(routeOptions: routeOptions, configureServices: null, endpoint1, endpoint2); var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
// Act // Act
var path = linkGenerator.GetPathByAddress( var path = linkGenerator.GetPathByAddress(
@ -313,15 +323,15 @@ namespace Microsoft.AspNetCore.Routing
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}"); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}");
var routeOptions = new RouteOptions();
routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
Action<IServiceCollection> configure = (s) => Action<IServiceCollection> configure = (s) =>
{ {
s.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform()); s.Configure<RouteOptions>(o =>
{
o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
});
}; };
var linkGenerator = CreateLinkGenerator(routeOptions, configure, endpoint); var linkGenerator = CreateLinkGenerator(configure, endpoint);
// Act // Act
var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test" }); var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test" });
@ -336,15 +346,15 @@ namespace Microsoft.AspNetCore.Routing
// Arrange // Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", policies: new { c = new UpperCaseParameterTransform(), }); var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", policies: new { c = new UpperCaseParameterTransform(), });
var routeOptions = new RouteOptions();
routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
Action<IServiceCollection> configure = (s) => Action<IServiceCollection> configure = (s) =>
{ {
s.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform()); s.Configure<RouteOptions>(o =>
{
o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
});
}; };
var linkGenerator = CreateLinkGenerator(routeOptions, configure, endpoint); var linkGenerator = CreateLinkGenerator(configure, endpoint);
// Act // Act
var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test", c = "hithere", }); 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<int> private class IntAddressScheme : IEndpointAddressScheme<int>
{ {
private readonly CompositeEndpointDataSource _dataSource; private readonly EndpointDataSource _dataSource;
public IntAddressScheme(CompositeEndpointDataSource dataSource) public IntAddressScheme(EndpointDataSource dataSource)
{ {
_dataSource = dataSource; _dataSource = dataSource;
} }

View File

@ -151,10 +151,9 @@ namespace Microsoft.AspNetCore.Routing
logger = logger ?? new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance); logger = logger ?? new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
matcherFactory = matcherFactory ?? new TestMatcherFactory(true); matcherFactory = matcherFactory ?? new TestMatcherFactory(true);
var options = Options.Create(new EndpointOptions());
var middleware = new EndpointRoutingMiddleware( var middleware = new EndpointRoutingMiddleware(
matcherFactory, matcherFactory,
new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>()), new DefaultEndpointDataSource(),
logger, logger,
next); next);

View File

@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System; using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
@ -44,29 +45,22 @@ namespace Microsoft.AspNetCore.Routing
private protected DefaultLinkGenerator CreateLinkGenerator(params Endpoint[] endpoints) private protected DefaultLinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
{ {
return CreateLinkGenerator(routeOptions: null, endpoints); return CreateLinkGenerator(configureServices: null, endpoints);
}
private protected DefaultLinkGenerator CreateLinkGenerator(RouteOptions routeOptions, params Endpoint[] endpoints)
{
return CreateLinkGenerator(routeOptions, configureServices: null, endpoints);
} }
private protected DefaultLinkGenerator CreateLinkGenerator( private protected DefaultLinkGenerator CreateLinkGenerator(
RouteOptions routeOptions,
Action<IServiceCollection> configureServices, Action<IServiceCollection> configureServices,
params Endpoint[] endpoints) params Endpoint[] endpoints)
{ {
return CreateLinkGenerator(routeOptions, configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty<Endpoint>()) }); return CreateLinkGenerator(configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty<Endpoint>()) });
} }
private protected DefaultLinkGenerator CreateLinkGenerator(EndpointDataSource[] dataSources) private protected DefaultLinkGenerator CreateLinkGenerator(EndpointDataSource[] dataSources)
{ {
return CreateLinkGenerator(routeOptions: null, configureServices: null, dataSources); return CreateLinkGenerator(configureServices: null, dataSources);
} }
private protected DefaultLinkGenerator CreateLinkGenerator( private protected DefaultLinkGenerator CreateLinkGenerator(
RouteOptions routeOptions,
Action<IServiceCollection> configureServices, Action<IServiceCollection> configureServices,
EndpointDataSource[] dataSources) EndpointDataSource[] dataSources)
{ {
@ -74,25 +68,25 @@ namespace Microsoft.AspNetCore.Routing
AddAdditionalServices(services); AddAdditionalServices(services);
configureServices?.Invoke(services); configureServices?.Invoke(services);
routeOptions = routeOptions ?? new RouteOptions(); services.Configure<RouteOptions>(o =>
dataSources = dataSources ?? Array.Empty<EndpointDataSource>();
services.Configure<EndpointOptions>((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 serviceProvider = services.BuildServiceProvider();
var routeOptions = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
return new DefaultLinkGenerator( return new DefaultLinkGenerator(
new DefaultParameterPolicyFactory(options, serviceProvider), new DefaultParameterPolicyFactory(routeOptions, serviceProvider),
serviceProvider.GetRequiredService<CompositeEndpointDataSource>(), new CompositeEndpointDataSource(routeOptions.Value.EndpointDataSources),
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()), new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
options, routeOptions,
NullLogger<DefaultLinkGenerator>.Instance, NullLogger<DefaultLinkGenerator>.Instance,
serviceProvider); serviceProvider);
} }

View File

@ -8,18 +8,18 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Patterns;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Routing.Matching namespace Microsoft.AspNetCore.Routing
{ {
public class MatcherEndpointBuilderTest public class RouteEndpointModelTest
{ {
[Fact] [Fact]
public void Build_AllValuesSet_EndpointCreated() public void Build_AllValuesSet_EndpointCreated()
{ {
const int defaultOrder = 0; const int defaultOrder = 0;
object metadata = new object(); var metadata = new object();
RequestDelegate requestDelegate = (d) => null; RequestDelegate requestDelegate = (d) => null;
var builder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder) var builder = new RouteEndpointModel(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder)
{ {
DisplayName = "Display name!", DisplayName = "Display name!",
Metadata = { metadata } Metadata = { metadata }