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.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Benchmarks
|
||||
{
|
||||
|
|
@ -18,8 +17,13 @@ namespace Benchmarks
|
|||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddRouting();
|
||||
}
|
||||
|
||||
var endpointDataSource = new DefaultEndpointDataSource(new[]
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseEndpointRouting(builder =>
|
||||
{
|
||||
var endpointDataSource = new DefaultEndpointDataSource(new[]
|
||||
{
|
||||
new RouteEndpoint(
|
||||
requestDelegate: (httpContext) =>
|
||||
|
|
@ -37,12 +41,8 @@ namespace Benchmarks
|
|||
displayName: "Plaintext"),
|
||||
});
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
||||
}
|
||||
|
||||
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
|
||||
{
|
||||
app.UseEndpointRouting();
|
||||
builder.DataSources.Add(endpointDataSource);
|
||||
});
|
||||
|
||||
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
|
||||
{
|
||||
public static class EndpointDataSourceBuilderExtensions
|
||||
public static class EndpointRouteBuilderExtensions
|
||||
{
|
||||
public static EndpointBuilder MapHello(this EndpointDataSourceBuilder builder, string template, string greeter)
|
||||
public static IEndpointConventionBuilder MapHello(this IEndpointRouteBuilder builder, string template, string greeter)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
|
|
@ -24,10 +24,10 @@ namespace Microsoft.AspNetCore.Builder
|
|||
.UseHello(greeter)
|
||||
.Build();
|
||||
|
||||
return builder.MapEndpoint(
|
||||
pipeline,
|
||||
return builder.Map(
|
||||
template,
|
||||
"Hello");
|
||||
"Hello " + greeter,
|
||||
pipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
|
@ -36,19 +37,29 @@ namespace RoutingSample.Web
|
|||
{
|
||||
builder.MapHello("/helloworld", "World");
|
||||
|
||||
builder.MapEndpoint(
|
||||
builder.MapHello("/helloworld-secret", "Secret World")
|
||||
.RequireAuthorization("swordfish");
|
||||
|
||||
builder.MapGet(
|
||||
"/",
|
||||
(httpContext) =>
|
||||
{
|
||||
var dataSource = httpContext.RequestServices.GetRequiredService<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 payloadLength = _homePayload.Length;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
response.ContentLength = payloadLength;
|
||||
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
|
||||
},
|
||||
"/",
|
||||
"Home");
|
||||
builder.MapEndpoint(
|
||||
return response.WriteAsync(sb.ToString());
|
||||
});
|
||||
builder.MapGet(
|
||||
"/plaintext",
|
||||
(httpContext) =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
|
|
@ -57,30 +68,28 @@ namespace RoutingSample.Web
|
|||
response.ContentType = "text/plain";
|
||||
response.ContentLength = payloadLength;
|
||||
return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength);
|
||||
},
|
||||
"/plaintext",
|
||||
"Plaintext");
|
||||
builder.MapEndpoint(
|
||||
});
|
||||
builder.MapGet(
|
||||
"/withconstraints/{id:endsWith(_001)}",
|
||||
(httpContext) =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
return response.WriteAsync("WithConstraints");
|
||||
},
|
||||
"/withconstraints/{id:endsWith(_001)}",
|
||||
"withconstraints");
|
||||
builder.MapEndpoint(
|
||||
});
|
||||
builder.MapGet(
|
||||
"/withoptionalconstraints/{id:endsWith(_001)?}",
|
||||
(httpContext) =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
return response.WriteAsync("withoptionalconstraints");
|
||||
},
|
||||
"/withoptionalconstraints/{id:endsWith(_001)?}",
|
||||
"withoptionalconstraints");
|
||||
builder.MapEndpoint(
|
||||
});
|
||||
builder.MapGet(
|
||||
"/graph",
|
||||
"DFA Graph",
|
||||
(httpContext) =>
|
||||
{
|
||||
using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
|
||||
|
|
@ -91,11 +100,9 @@ namespace RoutingSample.Web
|
|||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
"/graph",
|
||||
"DFA Graph",
|
||||
new HttpMethodMetadata(new[] { "GET", }));
|
||||
builder.MapEndpoint(
|
||||
});
|
||||
builder.MapGet(
|
||||
"/WithSingleAsteriskCatchAll/{*path}",
|
||||
(httpContext) =>
|
||||
{
|
||||
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
|
||||
|
|
@ -106,10 +113,9 @@ namespace RoutingSample.Web
|
|||
return response.WriteAsync(
|
||||
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { }));
|
||||
},
|
||||
"/WithSingleAsteriskCatchAll/{*path}",
|
||||
"WithSingleAsteriskCatchAll",
|
||||
new RouteValuesAddressMetadata(routeName: "WithSingleAsteriskCatchAll", requiredValues: new RouteValueDictionary()));
|
||||
builder.MapEndpoint(
|
||||
builder.MapGet(
|
||||
"/WithDoubleAsteriskCatchAll/{**path}",
|
||||
(httpContext) =>
|
||||
{
|
||||
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
|
||||
|
|
@ -120,12 +126,12 @@ namespace RoutingSample.Web
|
|||
return response.WriteAsync(
|
||||
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { }));
|
||||
},
|
||||
"/WithDoubleAsteriskCatchAll/{**path}",
|
||||
"WithDoubleAsteriskCatchAll",
|
||||
new RouteValuesAddressMetadata(routeName: "WithDoubleAsteriskCatchAll", requiredValues: new RouteValueDictionary()));
|
||||
});
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoint();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
|
|
@ -14,25 +14,36 @@ namespace Microsoft.AspNetCore.Builder
|
|||
// Property key is used by MVC package to check that routing is registered
|
||||
private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered";
|
||||
|
||||
public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder)
|
||||
public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder, Action<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);
|
||||
|
||||
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>();
|
||||
dataSourceBuilder.ApplicationBuilder = builder;
|
||||
configure(dataSourceBuilder);
|
||||
defaultEndpointRouteBuilder.ApplicationBuilder = builder;
|
||||
}
|
||||
configure(endpointRouteBuilder);
|
||||
|
||||
foreach (var dataSource in endpointRouteBuilder.DataSources)
|
||||
{
|
||||
routeOptions.Value.EndpointDataSources.Add(dataSource);
|
||||
}
|
||||
|
||||
// Create endpoint data source for data sources registered in configure
|
||||
middlewareEndpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
|
||||
|
||||
builder.Properties[EndpointRoutingRegisteredKey] = true;
|
||||
|
||||
return builder.UseMiddleware<EndpointRoutingMiddleware>();
|
||||
return builder.UseMiddleware<EndpointRoutingMiddleware>(middlewareEndpointDataSource);
|
||||
}
|
||||
|
||||
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.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
|
@ -18,24 +20,49 @@ namespace Microsoft.AspNetCore.Routing
|
|||
[DebuggerDisplay("{DebuggerDisplayString,nq}")]
|
||||
public sealed class CompositeEndpointDataSource : EndpointDataSource
|
||||
{
|
||||
private readonly EndpointDataSource[] _dataSources;
|
||||
private readonly object _lock;
|
||||
private readonly ICollection<EndpointDataSource> _dataSources;
|
||||
private IReadOnlyList<Endpoint> _endpoints;
|
||||
private IChangeToken _consumerChangeToken;
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
internal CompositeEndpointDataSource(IEnumerable<EndpointDataSource> dataSources)
|
||||
private CompositeEndpointDataSource()
|
||||
{
|
||||
if (dataSources == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataSources));
|
||||
}
|
||||
|
||||
CreateChangeToken();
|
||||
_dataSources = dataSources.ToArray();
|
||||
_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>
|
||||
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
|
||||
/// instances.
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
internal class ConfigureEndpointOptions : IConfigureOptions<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)
|
||||
{
|
||||
|
|
@ -22,17 +23,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
_dataSources = dataSources;
|
||||
}
|
||||
|
||||
public void Configure(EndpointOptions options)
|
||||
public void Configure(RouteOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
foreach (var dataSource in _dataSources)
|
||||
{
|
||||
options.DataSources.Add(dataSource);
|
||||
}
|
||||
options.EndpointDataSources = _dataSources;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
ParameterPolicyFactory parameterPolicyFactory,
|
||||
CompositeEndpointDataSource dataSource,
|
||||
EndpointDataSource dataSource,
|
||||
ObjectPool<UriBuildingContext> uriBuildingContextPool,
|
||||
IOptions<RouteOptions> routeOptions,
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
|
|
@ -49,21 +50,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
services.TryAddSingleton(typeof(RoutingMarkerService));
|
||||
|
||||
// Collect all data sources from DI.
|
||||
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<EndpointOptions>, ConfigureEndpointOptions>());
|
||||
// Setup global collection of endpoint data sources
|
||||
var dataSources = new ObservableCollection<EndpointDataSource>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, ConfigureRouteOptions>(
|
||||
serviceProvider => new ConfigureRouteOptions(dataSources)));
|
||||
|
||||
// Allow global access to the list of endpoints.
|
||||
services.TryAddSingleton<CompositeEndpointDataSource>(s =>
|
||||
services.TryAddSingleton<EndpointDataSource>(s =>
|
||||
{
|
||||
var options = s.GetRequiredService<IOptions<EndpointOptions>>();
|
||||
return new CompositeEndpointDataSource(options.Value.DataSources);
|
||||
// Call internal ctor and pass global collection
|
||||
return new CompositeEndpointDataSource(dataSources);
|
||||
});
|
||||
|
||||
//
|
||||
// Endpoint Infrastructure
|
||||
//
|
||||
services.TryAddSingleton<EndpointDataSource, BuilderEndpointDataSource>();
|
||||
services.TryAddSingleton<EndpointDataSourceBuilder, DefaultEndpointDataSourceBuilder>();
|
||||
services.TryAddTransient<IEndpointRouteBuilder, DefaultEndpointRouteBuilder>();
|
||||
|
||||
//
|
||||
// Default matcher implementation
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http;
|
|||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public abstract class EndpointBuilder
|
||||
public abstract class EndpointModel
|
||||
{
|
||||
public RequestDelegate RequestDelegate { get; set; }
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
private readonly DataSourceDependentCache<Dictionary<string, Endpoint[]>> _cache;
|
||||
|
||||
public EndpointNameAddressScheme(CompositeEndpointDataSource dataSource)
|
||||
public EndpointNameAddressScheme(EndpointDataSource dataSource)
|
||||
{
|
||||
_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 ILogger _logger;
|
||||
private readonly CompositeEndpointDataSource _endpointDataSource;
|
||||
private readonly EndpointDataSource _endpointDataSource;
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
private Task<Matcher> _initializationTask;
|
||||
|
||||
public EndpointRoutingMiddleware(
|
||||
MatcherFactory matcherFactory,
|
||||
CompositeEndpointDataSource endpointDataSource,
|
||||
EndpointDataSource endpointDataSource,
|
||||
ILogger<EndpointRoutingMiddleware> logger,
|
||||
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.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public abstract class EndpointDataSourceBuilder
|
||||
public interface IEndpointRouteBuilder
|
||||
{
|
||||
public abstract ICollection<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;
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
internal string DebuggerToString()
|
||||
{
|
||||
return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ using Microsoft.AspNetCore.Routing.Patterns;
|
|||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public sealed class RouteEndpointBuilder : EndpointBuilder
|
||||
public sealed class RouteEndpointModel : EndpointModel
|
||||
{
|
||||
public RoutePattern RoutePattern { get; set; }
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public RouteEndpointBuilder(
|
||||
public RouteEndpointModel(
|
||||
RequestDelegate requestDelegate,
|
||||
RoutePattern routePattern,
|
||||
int order)
|
||||
|
|
@ -24,14 +24,14 @@ namespace Microsoft.AspNetCore.Routing
|
|||
|
||||
public override Endpoint Build()
|
||||
{
|
||||
var matcherEndpoint = new RouteEndpoint(
|
||||
var routeEndpoint = new RouteEndpoint(
|
||||
RequestDelegate,
|
||||
RoutePattern,
|
||||
Order,
|
||||
new EndpointMetadataCollection(Metadata),
|
||||
DisplayName);
|
||||
|
||||
return matcherEndpoint;
|
||||
return routeEndpoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
public class RouteOptions
|
||||
{
|
||||
public ICollection<EndpointDataSource> EndpointDataSources { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
internal class RouteValuesAddressScheme : IEndpointAddressScheme<RouteValuesAddress>
|
||||
{
|
||||
private readonly CompositeEndpointDataSource _dataSource;
|
||||
private readonly EndpointDataSource _dataSource;
|
||||
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
|
||||
private Dictionary<string, List<OutboundMatchResult>> _namedMatchResults;
|
||||
|
||||
public RouteValuesAddressScheme(CompositeEndpointDataSource dataSource)
|
||||
public RouteValuesAddressScheme(EndpointDataSource dataSource)
|
||||
{
|
||||
_dataSource = dataSource;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
|
|||
{
|
||||
// Arrange
|
||||
var expectedContentType = "text/plain";
|
||||
var expectedContent = "Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext";
|
||||
|
||||
// Act
|
||||
var response = await _client.GetAsync("/");
|
||||
|
|
@ -39,8 +38,6 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
|
|||
Assert.NotNull(response.Content);
|
||||
Assert.NotNull(response.Content.Headers.ContentType);
|
||||
Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
|
||||
var actualContent = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(expectedContent, actualContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -2,13 +2,17 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder.Internal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -23,7 +27,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpointRouting());
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpointRouting(builder => { }));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
|
|
@ -58,7 +62,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
var app = new ApplicationBuilder(services);
|
||||
|
||||
app.UseEndpointRouting();
|
||||
app.UseEndpointRouting(builder => { });
|
||||
|
||||
var appFunc = app.Build();
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
|
@ -75,17 +79,20 @@ namespace Microsoft.AspNetCore.Builder
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = new RouteEndpoint(
|
||||
TestConstants.EmptyRequestDelegate,
|
||||
RoutePatternFactory.Parse("{*p}"),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
"Test");
|
||||
TestConstants.EmptyRequestDelegate,
|
||||
RoutePatternFactory.Parse("{*p}"),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
"Test");
|
||||
|
||||
var services = CreateServices(endpoint);
|
||||
var services = CreateServices();
|
||||
|
||||
var app = new ApplicationBuilder(services);
|
||||
|
||||
app.UseEndpointRouting();
|
||||
app.UseEndpointRouting(builder =>
|
||||
{
|
||||
builder.DataSources.Add(new DefaultEndpointDataSource(endpoint));
|
||||
});
|
||||
|
||||
var appFunc = app.Build();
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
|
@ -126,7 +133,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
var app = new ApplicationBuilder(services);
|
||||
|
||||
app.UseEndpointRouting();
|
||||
app.UseEndpointRouting(builder => { });
|
||||
app.UseEndpoint();
|
||||
|
||||
var appFunc = app.Build();
|
||||
|
|
@ -140,36 +147,83 @@ namespace Microsoft.AspNetCore.Builder
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void UseEndpointRouting_CallWithBuilder_SetsEndpointBuilder()
|
||||
public void UseEndpointRouting_CallWithBuilder_SetsEndpointDataSource()
|
||||
{
|
||||
// Arrange
|
||||
var services = CreateServices();
|
||||
var matcherEndpointDataSources = new List<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);
|
||||
|
||||
// Act
|
||||
app.UseEndpointRouting(builder =>
|
||||
{
|
||||
builder.MapEndpoint(d => null, "/", "Test endpoint");
|
||||
builder.Map("/1", "Test endpoint 1", d => null);
|
||||
builder.Map("/2", "Test endpoint 2", d => null);
|
||||
});
|
||||
|
||||
app.UseEndpointRouting(builder =>
|
||||
{
|
||||
builder.Map("/3", "Test endpoint 3", d => null);
|
||||
builder.Map("/4", "Test endpoint 4", d => null);
|
||||
});
|
||||
|
||||
// This triggers the middleware to be created and the matcher factory to be called
|
||||
// with the datasource we want to test
|
||||
var requestDelegate = app.Build();
|
||||
requestDelegate(new DefaultHttpContext());
|
||||
|
||||
// Assert
|
||||
var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)services.GetRequiredService<EndpointDataSourceBuilder>();
|
||||
var endpointBuilder = Assert.Single(dataSourceBuilder.Endpoints);
|
||||
Assert.Equal("Test endpoint", endpointBuilder.DisplayName);
|
||||
Assert.Equal(2, matcherEndpointDataSources.Count);
|
||||
|
||||
// Each middleware has its own endpoints
|
||||
Assert.Collection(matcherEndpointDataSources[0].Endpoints,
|
||||
e => Assert.Equal("Test endpoint 1", e.DisplayName),
|
||||
e => Assert.Equal("Test endpoint 2", e.DisplayName));
|
||||
Assert.Collection(matcherEndpointDataSources[1].Endpoints,
|
||||
e => Assert.Equal("Test endpoint 3", e.DisplayName),
|
||||
e => Assert.Equal("Test endpoint 4", e.DisplayName));
|
||||
|
||||
var compositeEndpointBuilder = services.GetRequiredService<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();
|
||||
|
||||
if (matcherFactory != null)
|
||||
{
|
||||
services.AddSingleton<MatcherFactory>(matcherFactory);
|
||||
}
|
||||
|
||||
services.AddLogging();
|
||||
services.AddOptions();
|
||||
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
|
||||
{
|
||||
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]
|
||||
public void MapEndpoint_StringPattern_BuildsEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultEndpointDataSourceBuilder();
|
||||
var builder = new DefaultEndpointRouteBuilder();
|
||||
RequestDelegate requestDelegate = (d) => null;
|
||||
|
||||
// Act
|
||||
var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!");
|
||||
var endpointBuilder = builder.Map("/", "Display name!", requestDelegate);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints));
|
||||
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate);
|
||||
Assert.Equal("Display name!", endpointBuilder.DisplayName);
|
||||
Assert.Equal("/", endpointBuilder.RoutePattern.RawText);
|
||||
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
|
||||
|
||||
Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
|
||||
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
|
||||
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapEndpoint_TypedPattern_BuildsEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultEndpointDataSourceBuilder();
|
||||
var builder = new DefaultEndpointRouteBuilder();
|
||||
RequestDelegate requestDelegate = (d) => null;
|
||||
|
||||
// Act
|
||||
var endpointBuilder = builder.MapEndpoint(requestDelegate, RoutePatternFactory.Parse("/"), "Display name!");
|
||||
var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", requestDelegate);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints));
|
||||
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate);
|
||||
Assert.Equal("Display name!", endpointBuilder.DisplayName);
|
||||
Assert.Equal("/", endpointBuilder.RoutePattern.RawText);
|
||||
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
|
||||
|
||||
Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
|
||||
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
|
||||
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -53,18 +65,18 @@ namespace Microsoft.AspNetCore.Builder
|
|||
{
|
||||
// Arrange
|
||||
var metadata = new object();
|
||||
var builder = new DefaultEndpointDataSourceBuilder();
|
||||
var builder = new DefaultEndpointRouteBuilder();
|
||||
RequestDelegate requestDelegate = (d) => null;
|
||||
|
||||
// Act
|
||||
var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!", new[] { metadata });
|
||||
var endpointBuilder = builder.Map("/", "Display name!", requestDelegate, new[] { metadata });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints));
|
||||
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate);
|
||||
Assert.Equal("Display name!", endpointBuilder.DisplayName);
|
||||
Assert.Equal("/", endpointBuilder.RoutePattern.RawText);
|
||||
Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata));
|
||||
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
|
||||
Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
|
||||
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
|
||||
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
|
||||
Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -72,18 +84,18 @@ namespace Microsoft.AspNetCore.Builder
|
|||
{
|
||||
// Arrange
|
||||
var metadata = new object();
|
||||
var builder = new DefaultEndpointDataSourceBuilder();
|
||||
var builder = new DefaultEndpointRouteBuilder();
|
||||
RequestDelegate requestDelegate = (d) => null;
|
||||
|
||||
// Act
|
||||
var endpointBuilder = builder.MapEndpoint(requestDelegate, RoutePatternFactory.Parse("/"), "Display name!", new[] { metadata });
|
||||
var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", requestDelegate, new[] { metadata });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints));
|
||||
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate);
|
||||
Assert.Equal("Display name!", endpointBuilder.DisplayName);
|
||||
Assert.Equal("/", endpointBuilder.RoutePattern.RawText);
|
||||
Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata));
|
||||
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
|
||||
Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
|
||||
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
|
||||
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
|
||||
Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -259,7 +261,12 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||
|
||||
// Act
|
||||
|
|
@ -283,7 +290,12 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}");
|
||||
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
|
||||
var httpContext = CreateHttpContext();
|
||||
|
||||
// Act
|
||||
|
|
@ -334,7 +346,12 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||
|
||||
// Act
|
||||
|
|
@ -362,8 +379,17 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o =>
|
||||
{
|
||||
o.LowercaseUrls = true;
|
||||
o.LowercaseQueryStrings = true;
|
||||
});
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(
|
||||
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true },
|
||||
configure,
|
||||
endpoints: new[] { endpoint, });
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||
|
||||
|
|
@ -387,8 +413,13 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o => o.AppendTrailingSlash = true);
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(
|
||||
new RouteOptions() { AppendTrailingSlash = true },
|
||||
configure,
|
||||
endpoints: new[] { endpoint });
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||
|
||||
|
|
@ -412,8 +443,18 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o =>
|
||||
{
|
||||
o.LowercaseUrls = true;
|
||||
o.LowercaseQueryStrings = true;
|
||||
o.AppendTrailingSlash = true;
|
||||
});
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(
|
||||
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, AppendTrailingSlash = true },
|
||||
configure,
|
||||
endpoints: new[] { endpoint });
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||
|
||||
|
|
@ -437,8 +478,13 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(
|
||||
new RouteOptions() { LowercaseUrls = true },
|
||||
configure,
|
||||
endpoints: new[] { endpoint });
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
|
||||
|
||||
|
|
@ -466,8 +512,13 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o => o.LowercaseUrls = false);
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(
|
||||
new RouteOptions() { LowercaseUrls = false },
|
||||
configure,
|
||||
endpoints: new[] { endpoint });
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
|
||||
|
||||
|
|
@ -494,8 +545,17 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o =>
|
||||
{
|
||||
o.LowercaseUrls = true;
|
||||
o.LowercaseQueryStrings = true;
|
||||
});
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(
|
||||
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true },
|
||||
configure,
|
||||
endpoints: new[] { endpoint });
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||
|
||||
|
|
@ -523,8 +583,17 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o =>
|
||||
{
|
||||
o.LowercaseUrls = false;
|
||||
o.LowercaseQueryStrings = false;
|
||||
});
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(
|
||||
new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = false },
|
||||
configure,
|
||||
endpoints: new[] { endpoint });
|
||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||
|
||||
|
|
@ -552,8 +621,13 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||
Action<IServiceCollection> configure = (s) =>
|
||||
{
|
||||
s.Configure<RouteOptions>(o => o.AppendTrailingSlash = false);
|
||||
};
|
||||
|
||||
var linkGenerator = CreateLinkGenerator(
|
||||
new RouteOptions() { AppendTrailingSlash = false },
|
||||
configure,
|
||||
endpoints: new[] { endpoint });
|
||||
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 endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
|
||||
|
||||
var routeOptions = new RouteOptions();
|
||||
routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
|
||||
Action<IServiceCollection> configureServices = s =>
|
||||
{
|
||||
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
|
||||
var path = linkGenerator.GetPathByAddress(
|
||||
|
|
@ -198,10 +203,15 @@ namespace Microsoft.AspNetCore.Routing
|
|||
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
|
||||
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
|
||||
|
||||
var routeOptions = new RouteOptions();
|
||||
routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
|
||||
Action<IServiceCollection> configureServices = s =>
|
||||
{
|
||||
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
|
||||
var path = linkGenerator.GetPathByAddress(
|
||||
|
|
@ -313,15 +323,15 @@ namespace Microsoft.AspNetCore.Routing
|
|||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}");
|
||||
|
||||
var routeOptions = new RouteOptions();
|
||||
routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||
|
||||
Action<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
|
||||
var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test" });
|
||||
|
|
@ -336,15 +346,15 @@ namespace Microsoft.AspNetCore.Routing
|
|||
// Arrange
|
||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", policies: new { c = new UpperCaseParameterTransform(), });
|
||||
|
||||
var routeOptions = new RouteOptions();
|
||||
routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||
|
||||
Action<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
|
||||
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 readonly CompositeEndpointDataSource _dataSource;
|
||||
private readonly EndpointDataSource _dataSource;
|
||||
|
||||
public IntAddressScheme(CompositeEndpointDataSource dataSource)
|
||||
public IntAddressScheme(EndpointDataSource dataSource)
|
||||
{
|
||||
_dataSource = dataSource;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,10 +151,9 @@ namespace Microsoft.AspNetCore.Routing
|
|||
logger = logger ?? new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
|
||||
matcherFactory = matcherFactory ?? new TestMatcherFactory(true);
|
||||
|
||||
var options = Options.Create(new EndpointOptions());
|
||||
var middleware = new EndpointRoutingMiddleware(
|
||||
matcherFactory,
|
||||
new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>()),
|
||||
new DefaultEndpointDataSource(),
|
||||
logger,
|
||||
next);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
|||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
|
|
@ -44,29 +45,22 @@ namespace Microsoft.AspNetCore.Routing
|
|||
|
||||
private protected DefaultLinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
|
||||
{
|
||||
return CreateLinkGenerator(routeOptions: null, endpoints);
|
||||
}
|
||||
|
||||
private protected DefaultLinkGenerator CreateLinkGenerator(RouteOptions routeOptions, params Endpoint[] endpoints)
|
||||
{
|
||||
return CreateLinkGenerator(routeOptions, configureServices: null, endpoints);
|
||||
return CreateLinkGenerator(configureServices: null, endpoints);
|
||||
}
|
||||
|
||||
private protected DefaultLinkGenerator CreateLinkGenerator(
|
||||
RouteOptions routeOptions,
|
||||
Action<IServiceCollection> configureServices,
|
||||
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)
|
||||
{
|
||||
return CreateLinkGenerator(routeOptions: null, configureServices: null, dataSources);
|
||||
return CreateLinkGenerator(configureServices: null, dataSources);
|
||||
}
|
||||
|
||||
private protected DefaultLinkGenerator CreateLinkGenerator(
|
||||
RouteOptions routeOptions,
|
||||
Action<IServiceCollection> configureServices,
|
||||
EndpointDataSource[] dataSources)
|
||||
{
|
||||
|
|
@ -74,25 +68,25 @@ namespace Microsoft.AspNetCore.Routing
|
|||
AddAdditionalServices(services);
|
||||
configureServices?.Invoke(services);
|
||||
|
||||
routeOptions = routeOptions ?? new RouteOptions();
|
||||
dataSources = dataSources ?? Array.Empty<EndpointDataSource>();
|
||||
|
||||
services.Configure<EndpointOptions>((o) =>
|
||||
services.Configure<RouteOptions>(o =>
|
||||
{
|
||||
for (var i = 0; i < dataSources.Length; i++)
|
||||
if (dataSources != null)
|
||||
{
|
||||
o.DataSources.Add(dataSources[i]);
|
||||
foreach (var dataSource in dataSources)
|
||||
{
|
||||
o.EndpointDataSources.Add(dataSource);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var options = Options.Create(routeOptions);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var routeOptions = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
|
||||
|
||||
return new DefaultLinkGenerator(
|
||||
new DefaultParameterPolicyFactory(options, serviceProvider),
|
||||
serviceProvider.GetRequiredService<CompositeEndpointDataSource>(),
|
||||
new DefaultParameterPolicyFactory(routeOptions, serviceProvider),
|
||||
new CompositeEndpointDataSource(routeOptions.Value.EndpointDataSources),
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||
options,
|
||||
routeOptions,
|
||||
NullLogger<DefaultLinkGenerator>.Instance,
|
||||
serviceProvider);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class MatcherEndpointBuilderTest
|
||||
public class RouteEndpointModelTest
|
||||
{
|
||||
[Fact]
|
||||
public void Build_AllValuesSet_EndpointCreated()
|
||||
{
|
||||
const int defaultOrder = 0;
|
||||
object metadata = new object();
|
||||
var metadata = new object();
|
||||
RequestDelegate requestDelegate = (d) => null;
|
||||
|
||||
var builder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder)
|
||||
var builder = new RouteEndpointModel(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder)
|
||||
{
|
||||
DisplayName = "Display name!",
|
||||
Metadata = { metadata }
|
||||
Loading…
Reference in New Issue