Endpoint routing startup experience (#889)
This commit is contained in:
parent
be0e602d2f
commit
0ef4b4173c
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,12 +126,12 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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" });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
Loading…
Reference in New Issue