Merge branch 'master' into merge/release/2.2-to-master

This commit is contained in:
Ryan Nowak 2018-09-20 11:15:51 -07:00 committed by GitHub
commit 54ef2ef2a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 643 additions and 3460 deletions

View File

@ -7,7 +7,7 @@ resources:
- repository: buildtools
type: git
name: aspnet-BuildTools
ref: refs/heads/release/2.2
ref: refs/heads/master
phases:
- template: .vsts-pipelines/templates/project-ci.yml@buildtools

View File

@ -9,7 +9,7 @@ resources:
type: github
endpoint: DotNet-Bot GitHub Connection
name: aspnet/BuildTools
ref: refs/heads/release/2.2
ref: refs/heads/master
phases:
- template: .vsts-pipelines/templates/project-ci.yml@buildtools

View File

@ -3,7 +3,7 @@
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
@ -47,4 +47,4 @@ namespace Benchmarks
app.UseEndpoint();
}
}
}
}

View File

@ -8,7 +8,7 @@
},
"Source": {
"Repository": "https://github.com/aspnet/routing.git",
"BranchOrCommit": "release/2.2",
"BranchOrCommit": "master",
"Project": "benchmarkapps/Benchmarks/Benchmarks.csproj"
},
"Port": 8080

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
private TrivialMatcher _baseline;
private DfaMatcher _dfa;
private EndpointFeature _feature;
private IEndpointFeature _feature;
[GlobalSetup]
public void Setup()

View File

@ -1,182 +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 BenchmarkDotNet.Attributes;
namespace Microsoft.AspNetCore.Routing
{
public class RouteValueDictionaryBenchmark
{
private RouteValueDictionary _arrayValues;
private RouteValueDictionary _propertyValues;
// We modify the route value dictionaries in many of these benchmarks.
[IterationSetup]
public void Setup()
{
_arrayValues = new RouteValueDictionary()
{
{ "action", "Index" },
{ "controller", "Home" },
{ "id", "17" },
};
_propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
}
[Benchmark]
public RouteValueDictionary AddSingleItem()
{
var dictionary = new RouteValueDictionary
{
{ "action", "Index" }
};
return dictionary;
}
[Benchmark]
public RouteValueDictionary AddThreeItems()
{
var dictionary = new RouteValueDictionary
{
{ "action", "Index" },
{ "controller", "Home" },
{ "id", "15" }
};
return dictionary;
}
[Benchmark]
public RouteValueDictionary ConditionalAdd_ContainsKeyAdd()
{
var dictionary = _arrayValues;
if (!dictionary.ContainsKey("action"))
{
dictionary.Add("action", "Index");
}
if (!dictionary.ContainsKey("controller"))
{
dictionary.Add("controller", "Home");
}
if (!dictionary.ContainsKey("area"))
{
dictionary.Add("area", "Admin");
}
return dictionary;
}
[Benchmark]
public RouteValueDictionary ConditionalAdd_TryAdd()
{
var dictionary = _arrayValues;
dictionary.TryAdd("action", "Index");
dictionary.TryAdd("controller", "Home");
dictionary.TryAdd("area", "Admin");
return dictionary;
}
[Benchmark]
public RouteValueDictionary ForEachThreeItems_Array()
{
var dictionary = _arrayValues;
foreach (var kvp in dictionary)
{
GC.KeepAlive(kvp.Value);
}
return dictionary;
}
[Benchmark]
public RouteValueDictionary ForEachThreeItems_Properties()
{
var dictionary = _propertyValues;
foreach (var kvp in dictionary)
{
GC.KeepAlive(kvp.Value);
}
return dictionary;
}
[Benchmark]
public RouteValueDictionary GetThreeItems_Array()
{
var dictionary = _arrayValues;
GC.KeepAlive(dictionary["action"]);
GC.KeepAlive(dictionary["controller"]);
GC.KeepAlive(dictionary["id"]);
return dictionary;
}
[Benchmark]
public RouteValueDictionary GetThreeItems_Properties()
{
var dictionary = _propertyValues;
GC.KeepAlive(dictionary["action"]);
GC.KeepAlive(dictionary["controller"]);
GC.KeepAlive(dictionary["id"]);
return dictionary;
}
[Benchmark]
public RouteValueDictionary SetSingleItem()
{
var dictionary = new RouteValueDictionary
{
["action"] = "Index"
};
return dictionary;
}
[Benchmark]
public RouteValueDictionary SetExistingItem()
{
var dictionary = _arrayValues;
dictionary["action"] = "About";
return dictionary;
}
[Benchmark]
public RouteValueDictionary SetThreeItems()
{
var dictionary = new RouteValueDictionary
{
["action"] = "Index",
["controller"] = "Home",
["id"] = "15"
};
return dictionary;
}
[Benchmark]
public RouteValueDictionary TryGetValueThreeItems_Array()
{
var dictionary = _arrayValues;
dictionary.TryGetValue("action", out var action);
dictionary.TryGetValue("controller", out var controller);
dictionary.TryGetValue("id", out var id);
GC.KeepAlive(action);
GC.KeepAlive(controller);
GC.KeepAlive(id);
return dictionary;
}
[Benchmark]
public RouteValueDictionary TryGetValueThreeItems_Properties()
{
var dictionary = _propertyValues;
dictionary.TryGetValue("action", out var action);
dictionary.TryGetValue("controller", out var controller);
dictionary.TryGetValue("id", out var id);
GC.KeepAlive(action);
GC.KeepAlive(controller);
GC.KeepAlive(id);
return dictionary;
}
}
}

View File

@ -4,38 +4,38 @@
</PropertyGroup>
<PropertyGroup Label="Package Versions">
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview1-20180911.1</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreAppPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreAppPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview3-35252</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>
<MicrosoftExtensionsConfigurationPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsConfigurationPackageVersion>
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsObjectPoolPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsObjectPoolPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
<MicrosoftExtensionsWebEncodersPackageVersion>2.2.0-preview3-35252</MicrosoftExtensionsWebEncodersPackageVersion>
<InternalAspNetCoreSdkPackageVersion>3.0.0-alpha1-20180911.2</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreAppPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreAppPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>3.0.0-alpha1-10454</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>
<MicrosoftExtensionsConfigurationPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsConfigurationPackageVersion>
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsObjectPoolPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsObjectPoolPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
<MicrosoftExtensionsWebEncodersPackageVersion>3.0.0-alpha1-10454</MicrosoftExtensionsWebEncodersPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview2-26905-02</MicrosoftNETCoreApp22PackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion>
<MoqPackageVersion>4.9.0</MoqPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
<SystemReflectionEmitLightweightPackageVersion>4.3.0</SystemReflectionEmitLightweightPackageVersion>

View File

@ -4,7 +4,6 @@
<PropertyGroup>
<!-- These properties are use by the automation that updates dependencies.props -->
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
<LineupPackageVersion>2.2.0-*</LineupPackageVersion>
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
</PropertyGroup>

View File

@ -1,2 +1,2 @@
version:2.2.0-preview1-20180911.1
commithash:ddfecdfc6e8e4859db5a0daea578070b862aac65
version:3.0.0-alpha1-20180911.2
commithash:2a2b7dbea1b247930c41da497f4ea0b2bb756818

View File

@ -1,4 +1,4 @@
{
"$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.2/tools/korebuild.schema.json",
"channel": "release/2.2"
"$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json",
"channel": "master"
}

View File

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Builder
{
public static class EndpointDataSourceBuilderExtensions
{
public static EndpointBuilder MapHello(this EndpointDataSourceBuilder builder, string template, string greeter)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
var pipeline = builder.CreateApplicationBuilder()
.UseHello(greeter)
.Build();
return builder.MapEndpoint(
pipeline,
template,
"Hello");
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Options;
using RoutingSample.Web.HelloExtension;
namespace Microsoft.AspNetCore.Builder
{
public static class HelloAppBuilderExtensions
{
public static IApplicationBuilder UseHello(this IApplicationBuilder app, string greeter)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<HelloMiddleware>(Options.Create(new HelloOptions
{
Greeter = greeter
}));
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace RoutingSample.Web.HelloExtension
{
public class HelloMiddleware
{
private readonly RequestDelegate _next;
private readonly HelloOptions _helloOptions;
private readonly byte[] _helloPayload;
public HelloMiddleware(RequestDelegate next, IOptions<HelloOptions> helloOptions)
{
_next = next;
_helloOptions = helloOptions.Value;
var payload = new List<byte>();
payload.AddRange(Encoding.UTF8.GetBytes("Hello"));
if (!string.IsNullOrEmpty(_helloOptions.Greeter))
{
payload.Add((byte)' ');
payload.AddRange(Encoding.UTF8.GetBytes(_helloOptions.Greeter));
}
_helloPayload = payload.ToArray();
}
public Task InvokeAsync(HttpContext context)
{
var response = context.Response;
var payloadLength = _helloPayload.Length;
response.StatusCode = 200;
response.ContentType = "text/plain";
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_helloPayload, 0, payloadLength);
}
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace RoutingSample.Web.HelloExtension
{
public class HelloOptions
{
public string Greeter { get; set; }
}
}

View File

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

View File

@ -5,12 +5,10 @@ using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@ -19,7 +17,7 @@ namespace RoutingSample.Web
public class UseEndpointRoutingStartup
{
private static readonly byte[] _homePayload = Encoding.UTF8.GetBytes("Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext");
private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
private static readonly byte[] _plainTextPayload = Encoding.UTF8.GetBytes("Plain text!");
public void ConfigureServices(IServiceCollection services)
{
@ -29,114 +27,102 @@ namespace RoutingSample.Web
{
options.ConstraintMap.Add("endsWith", typeof(EndsWithStringRouteConstraint));
});
var endpointDataSource = new DefaultEndpointDataSource(new[]
{
new RouteEndpoint((httpContext) =>
{
var response = httpContext.Response;
var payloadLength = _homePayload.Length;
response.StatusCode = 200;
response.ContentType = "text/plain";
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
},
RoutePatternFactory.Parse("/"),
0,
EndpointMetadataCollection.Empty,
"Home"),
new RouteEndpoint((httpContext) =>
{
var response = httpContext.Response;
var payloadLength = _helloWorldPayload.Length;
response.StatusCode = 200;
response.ContentType = "text/plain";
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
},
RoutePatternFactory.Parse("/plaintext"),
0,
EndpointMetadataCollection.Empty,
"Plaintext"),
new RouteEndpoint((httpContext) =>
{
var response = httpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
return response.WriteAsync("WithConstraints");
},
RoutePatternFactory.Parse("/withconstraints/{id:endsWith(_001)}"),
0,
EndpointMetadataCollection.Empty,
"withconstraints"),
new RouteEndpoint((httpContext) =>
{
var response = httpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
return response.WriteAsync("withoptionalconstraints");
},
RoutePatternFactory.Parse("/withoptionalconstraints/{id:endsWith(_001)?}"),
0,
EndpointMetadataCollection.Empty,
"withoptionalconstraints"),
new RouteEndpoint((httpContext) =>
{
using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
{
var graphWriter = httpContext.RequestServices.GetRequiredService<DfaGraphWriter>();
var dataSource = httpContext.RequestServices.GetRequiredService<CompositeEndpointDataSource>();
graphWriter.Write(dataSource, writer);
}
return Task.CompletedTask;
},
RoutePatternFactory.Parse("/graph"),
0,
new EndpointMetadataCollection(new HttpMethodMetadata(new[]{ "GET", })),
"DFA Graph"),
new RouteEndpoint((httpContext) =>
{
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
var response = httpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
return response.WriteAsync(
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { }));
},
RoutePatternFactory.Parse("/WithSingleAsteriskCatchAll/{*path}"),
0,
new EndpointMetadataCollection(
new RouteValuesAddressMetadata(
routeName: "WithSingleAsteriskCatchAll",
requiredValues: new RouteValueDictionary())),
"WithSingleAsteriskCatchAll"),
new RouteEndpoint((httpContext) =>
{
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
var response = httpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
return response.WriteAsync(
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { }));
},
RoutePatternFactory.Parse("/WithDoubleAsteriskCatchAll/{**path}"),
0,
new EndpointMetadataCollection(
new RouteValuesAddressMetadata(
routeName: "WithDoubleAsteriskCatchAll",
requiredValues: new RouteValueDictionary())),
"WithDoubleAsteriskCatchAll"),
});
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
}
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
public void Configure(IApplicationBuilder app)
{
app.UseEndpointRouting();
app.UseEndpointRouting(builder =>
{
builder.MapHello("/helloworld", "World");
builder.MapEndpoint(
(httpContext) =>
{
var response = httpContext.Response;
var payloadLength = _homePayload.Length;
response.StatusCode = 200;
response.ContentType = "text/plain";
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
},
"/",
"Home");
builder.MapEndpoint(
(httpContext) =>
{
var response = httpContext.Response;
var payloadLength = _plainTextPayload.Length;
response.StatusCode = 200;
response.ContentType = "text/plain";
response.ContentLength = payloadLength;
return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength);
},
"/plaintext",
"Plaintext");
builder.MapEndpoint(
(httpContext) =>
{
var response = httpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
return response.WriteAsync("WithConstraints");
},
"/withconstraints/{id:endsWith(_001)}",
"withconstraints");
builder.MapEndpoint(
(httpContext) =>
{
var response = httpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
return response.WriteAsync("withoptionalconstraints");
},
"/withoptionalconstraints/{id:endsWith(_001)?}",
"withoptionalconstraints");
builder.MapEndpoint(
(httpContext) =>
{
using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
{
var graphWriter = httpContext.RequestServices.GetRequiredService<DfaGraphWriter>();
var dataSource = httpContext.RequestServices.GetRequiredService<CompositeEndpointDataSource>();
graphWriter.Write(dataSource, writer);
}
return Task.CompletedTask;
},
"/graph",
"DFA Graph",
new HttpMethodMetadata(new[] { "GET", }));
builder.MapEndpoint(
(httpContext) =>
{
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
var response = httpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
return response.WriteAsync(
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { }));
},
"/WithSingleAsteriskCatchAll/{*path}",
"WithSingleAsteriskCatchAll",
new RouteValuesAddressMetadata(routeName: "WithSingleAsteriskCatchAll", requiredValues: new RouteValueDictionary()));
builder.MapEndpoint(
(httpContext) =>
{
var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
var response = httpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
return response.WriteAsync(
"Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { }));
},
"/WithDoubleAsteriskCatchAll/{**path}",
"WithDoubleAsteriskCatchAll",
new RouteValuesAddressMetadata(routeName: "WithDoubleAsteriskCatchAll", requiredValues: new RouteValueDictionary()));
});
// Imagine some more stuff here...

View File

@ -1,49 +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.
namespace Microsoft.AspNetCore.Http
{
/// <summary>
/// Respresents a logical endpoint in an application.
/// </summary>
public class Endpoint
{
/// <summary>
/// Creates a new instance of <see cref="Endpoint"/>.
/// </summary>
/// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
/// <param name="metadata">
/// The endpoint <see cref="EndpointMetadataCollection"/>. May be null.
/// </param>
/// <param name="displayName">
/// The informational display name of the endpoint. May be null.
/// </param>
public Endpoint(
RequestDelegate requestDelegate,
EndpointMetadataCollection metadata,
string displayName)
{
// All are allowed to be null
RequestDelegate = requestDelegate;
Metadata = metadata ?? EndpointMetadataCollection.Empty;
DisplayName = displayName;
}
/// <summary>
/// Gets the informational display name of this endpoint.
/// </summary>
public string DisplayName { get; }
/// <summary>
/// Gets the collection of metadata associated with this endpoint.
/// </summary>
public EndpointMetadataCollection Metadata { get; }
/// <summary>
/// Gets the delegate used to process requests for the endpoint.
/// </summary>
public RequestDelegate RequestDelegate { get; }
public override string ToString() => DisplayName ?? base.ToString();
}
}

View File

@ -1,201 +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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Http
{
/// <summary>
/// A collection of arbitrary metadata associated with an endpoint.
/// </summary>
/// <remarks>
/// <see cref="EndpointMetadataCollection"/> instances contain a list of metadata items
/// of arbitrary types. The metadata items are stored as an ordered collection with
/// items arranged in ascending order of precedence.
/// </remarks>
public sealed class EndpointMetadataCollection : IReadOnlyList<object>
{
/// <summary>
/// An empty <see cref="EndpointMetadataCollection"/>.
/// </summary>
public static readonly EndpointMetadataCollection Empty = new EndpointMetadataCollection(Array.Empty<object>());
private readonly object[] _items;
private readonly ConcurrentDictionary<Type, object[]> _cache;
/// <summary>
/// Creates a new <see cref="EndpointMetadataCollection"/>.
/// </summary>
/// <param name="items">The metadata items.</param>
public EndpointMetadataCollection(IEnumerable<object> items)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
_items = items.ToArray();
_cache = new ConcurrentDictionary<Type, object[]>();
}
/// <summary>
/// Creates a new <see cref="EndpointMetadataCollection"/>.
/// </summary>
/// <param name="items">The metadata items.</param>
public EndpointMetadataCollection(params object[] items)
: this((IEnumerable<object>)items)
{
}
/// <summary>
/// Gets the item at <paramref name="index"/>.
/// </summary>
/// <param name="index">The index of the item to retrieve.</param>
/// <returns>The item at <paramref name="index"/>.</returns>
public object this[int index] => _items[index];
/// <summary>
/// Gets the count of metadata items.
/// </summary>
public int Count => _items.Length;
/// <summary>
/// Gets the most significant metadata item of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of metadata to retrieve.</typeparam>
/// <returns>
/// The most significant metadata of type <typeparamref name="T"/> or <c>null</c>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetMetadata<T>() where T : class
{
if (_cache.TryGetValue(typeof(T), out var result))
{
var length = result.Length;
return length > 0 ? (T)result[length - 1] : default;
}
return GetMetadataSlow<T>();
}
private T GetMetadataSlow<T>() where T : class
{
var array = GetOrderedMetadataSlow<T>();
var length = array.Length;
return length > 0 ? array[length - 1] : default;
}
/// <summary>
/// Gets the metadata items of type <typeparamref name="T"/> in ascending
/// order of precedence.
/// </summary>
/// <typeparam name="T">The type of metadata.</typeparam>
/// <returns>A sequence of metadata items of <typeparamref name="T"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IEnumerable<T> GetOrderedMetadata<T>() where T : class
{
if (_cache.TryGetValue(typeof(T), out var result))
{
return (T[])result;
}
return GetOrderedMetadataSlow<T>();
}
private T[] GetOrderedMetadataSlow<T>() where T : class
{
var items = new List<T>();
for (var i = 0; i < _items.Length; i++)
{
if (_items[i] is T item)
{
items.Add(item);
}
}
var array = items.ToArray();
_cache.TryAdd(typeof(T), array);
return array;
}
/// <summary>
/// Gets an <see cref="IEnumerator"/> of all metadata items.
/// </summary>
/// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
public Enumerator GetEnumerator() => new Enumerator(this);
/// <summary>
/// Gets an <see cref="IEnumerator{Object}"/> of all metadata items.
/// </summary>
/// <returns>An <see cref="IEnumerator{Object}"/> of all metadata items.</returns>
IEnumerator<object> IEnumerable<object>.GetEnumerator() => GetEnumerator();
/// <summary>
/// Gets an <see cref="IEnumerator"/> of all metadata items.
/// </summary>
/// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Enumerates the elements of an <see cref="EndpointMetadataCollection"/>.
/// </summary>
public struct Enumerator : IEnumerator<object>
{
// Intentionally not readonly to prevent defensive struct copies
private object[] _items;
private int _index;
internal Enumerator(EndpointMetadataCollection collection)
{
_items = collection._items;
_index = 0;
Current = null;
}
/// <summary>
/// Gets the element at the current position of the enumerator
/// </summary>
public object Current { get; private set; }
/// <summary>
/// Releases all resources used by the <see cref="Enumerator"/>.
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Advances the enumerator to the next element of the <see cref="Enumerator"/>.
/// </summary>
/// <returns>
/// <c>true</c> if the enumerator was successfully advanced to the next element;
/// <c>false</c> if the enumerator has passed the end of the collection.
/// </returns>
public bool MoveNext()
{
if (_index < _items.Length)
{
Current = _items[_index++];
return true;
}
Current = null;
return false;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
public void Reset()
{
_index = 0;
Current = null;
}
}
}
}

View File

@ -1,18 +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.
namespace Microsoft.AspNetCore.Http.Features
{
/// <summary>
/// A feature interface for endpoint routing. Use <see cref="HttpContext.Features"/>
/// to access an instance associated with the current request.
/// </summary>
public interface IEndpointFeature
{
/// <summary>
/// Gets or sets the selected <see cref="Http.Endpoint"/> for the current
/// request.
/// </summary>
Endpoint Endpoint { get; set; }
}
}

View File

@ -1,16 +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 Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Http.Features
{
public interface IRouteValuesFeature
{
/// <summary>
/// Gets or sets the <see cref="RouteValueDictionary"/> associated with the currrent
/// request.
/// </summary>
RouteValueDictionary RouteValues { get; set; }
}
}

View File

@ -2,6 +2,15 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
[assembly: TypeForwardedTo(typeof(IEndpointFeature))]
[assembly: TypeForwardedTo(typeof(IRouteValuesFeature))]
[assembly: TypeForwardedTo(typeof(Endpoint))]
[assembly: TypeForwardedTo(typeof(EndpointMetadataCollection))]
[assembly: TypeForwardedTo(typeof(RouteValueDictionary))]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

View File

@ -1,58 +0,0 @@
// <auto-generated />
namespace Microsoft.AspNetCore.Routing.Abstractions
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Routing.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// An element with the key '{0}' already exists in the {1}.
/// </summary>
internal static string RouteValueDictionary_DuplicateKey
{
get => GetString("RouteValueDictionary_DuplicateKey");
}
/// <summary>
/// An element with the key '{0}' already exists in the {1}.
/// </summary>
internal static string FormatRouteValueDictionary_DuplicateKey(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("RouteValueDictionary_DuplicateKey"), p0, p1);
/// <summary>
/// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.
/// </summary>
internal static string RouteValueDictionary_DuplicatePropertyName
{
get => GetString("RouteValueDictionary_DuplicatePropertyName");
}
/// <summary>
/// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.
/// </summary>
internal static string FormatRouteValueDictionary_DuplicatePropertyName(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("RouteValueDictionary_DuplicatePropertyName"), p0, p1, p2, p3);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -1,126 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="RouteValueDictionary_DuplicateKey" xml:space="preserve">
<value>An element with the key '{0}' already exists in the {1}.</value>
</data>
<data name="RouteValueDictionary_DuplicatePropertyName" xml:space="preserve">
<value>The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.</value>
</data>
</root>

View File

@ -1,674 +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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Routing.Abstractions;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// An <see cref="IDictionary{String, Object}"/> type for route values.
/// </summary>
public class RouteValueDictionary : IDictionary<string, object>, IReadOnlyDictionary<string, object>
{
// 4 is a good default capacity here because that leaves enough space for area/controller/action/id
private const int DefaultCapacity = 4;
internal KeyValuePair<string, object>[] _arrayStorage;
internal PropertyStorage _propertyStorage;
private int _count;
/// <summary>
/// Creates a new <see cref="RouteValueDictionary"/> from the provided array.
/// The new instance will take ownership of the array, and may mutate it.
/// </summary>
/// <param name="items">The items array.</param>
/// <returns>A new <see cref="RouteValueDictionary"/>.</returns>
public static RouteValueDictionary FromArray(KeyValuePair<string, object>[] items)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
// We need to compress the array by removing non-contiguous items. We
// typically have a very small number of items to process. We don't need
// to preserve order.
var start = 0;
var end = items.Length - 1;
// We walk forwards from the beginning of the array and fill in 'null' slots.
// We walk backwards from the end of the array end move items in non-null' slots
// into whatever start is pointing to. O(n)
while (start <= end)
{
if (items[start].Key != null)
{
start++;
}
else if (items[end].Key != null)
{
// Swap this item into start and advance
items[start] = items[end];
items[end] = default;
start++;
end--;
}
else
{
// Both null, we need to hold on 'start' since we
// still need to fill it with something.
end--;
}
}
return new RouteValueDictionary()
{
_arrayStorage = items,
_count = start,
};
}
/// <summary>
/// Creates an empty <see cref="RouteValueDictionary"/>.
/// </summary>
public RouteValueDictionary()
{
_arrayStorage = Array.Empty<KeyValuePair<string, object>>();
}
/// <summary>
/// Creates a <see cref="RouteValueDictionary"/> initialized with the specified <paramref name="values"/>.
/// </summary>
/// <param name="values">An object to initialize the dictionary. The value can be of type
/// <see cref="IDictionary{TKey, TValue}"/> or <see cref="IReadOnlyDictionary{TKey, TValue}"/>
/// or an object with public properties as key-value pairs.
/// </param>
/// <remarks>
/// If the value is a dictionary or other <see cref="IEnumerable{T}"/> of <see cref="KeyValuePair{String, Object}"/>,
/// then its entries are copied. Otherwise the object is interpreted as a set of key-value pairs where the
/// property names are keys, and property values are the values, and copied into the dictionary.
/// Only public instance non-index properties are considered.
/// </remarks>
public RouteValueDictionary(object values)
: this()
{
if (values is RouteValueDictionary dictionary)
{
if (dictionary._propertyStorage != null)
{
// PropertyStorage is immutable so we can just copy it.
_propertyStorage = dictionary._propertyStorage;
_count = dictionary._count;
return;
}
var other = dictionary._arrayStorage;
var storage = new KeyValuePair<string, object>[other.Length];
if (dictionary._count != 0)
{
Array.Copy(other, 0, storage, 0, dictionary._count);
}
_arrayStorage = storage;
_count = dictionary._count;
return;
}
if (values is IEnumerable<KeyValuePair<string, object>> keyValueEnumerable)
{
foreach (var kvp in keyValueEnumerable)
{
Add(kvp.Key, kvp.Value);
}
return;
}
if (values is IEnumerable<KeyValuePair<string, string>> stringValueEnumerable)
{
foreach (var kvp in stringValueEnumerable)
{
Add(kvp.Key, kvp.Value);
}
return;
}
if (values != null)
{
var storage = new PropertyStorage(values);
_propertyStorage = storage;
_count = storage.Properties.Length;
return;
}
}
/// <inheritdoc />
public object this[string key]
{
get
{
if (key == null)
{
ThrowArgumentNullExceptionForKey();
}
object value;
TryGetValue(key, out value);
return value;
}
set
{
if (key == null)
{
ThrowArgumentNullExceptionForKey();
}
// We're calling this here for the side-effect of converting from properties
// to array. We need to create the array even if we just set an existing value since
// property storage is immutable.
EnsureCapacity(_count);
var index = FindIndex(key);
if (index < 0)
{
EnsureCapacity(_count + 1);
_arrayStorage[_count++] = new KeyValuePair<string, object>(key, value);
}
else
{
_arrayStorage[index] = new KeyValuePair<string, object>(key, value);
}
}
}
/// <summary>
/// Gets the comparer for this dictionary.
/// </summary>
/// <remarks>
/// This will always be a reference to <see cref="StringComparer.OrdinalIgnoreCase"/>
/// </remarks>
public IEqualityComparer<string> Comparer => StringComparer.OrdinalIgnoreCase;
/// <inheritdoc />
public int Count => _count;
/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
/// <inheritdoc />
public ICollection<string> Keys
{
get
{
EnsureCapacity(_count);
var array = _arrayStorage;
var keys = new string[_count];
for (var i = 0; i < keys.Length; i++)
{
keys[i] = array[i].Key;
}
return keys;
}
}
IEnumerable<string> IReadOnlyDictionary<string, object>.Keys => Keys;
/// <inheritdoc />
public ICollection<object> Values
{
get
{
EnsureCapacity(_count);
var array = _arrayStorage;
var values = new object[_count];
for (var i = 0; i < values.Length; i++)
{
values[i] = array[i].Value;
}
return values;
}
}
IEnumerable<object> IReadOnlyDictionary<string, object>.Values => Values;
/// <inheritdoc />
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
Add(item.Key, item.Value);
}
/// <inheritdoc />
public void Add(string key, object value)
{
if (key == null)
{
ThrowArgumentNullExceptionForKey();
}
EnsureCapacity(_count + 1);
var index = FindIndex(key);
if (index >= 0)
{
var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary));
throw new ArgumentException(message, nameof(key));
}
_arrayStorage[_count] = new KeyValuePair<string, object>(key, value);
_count++;
}
/// <inheritdoc />
public void Clear()
{
if (_count == 0)
{
return;
}
if (_propertyStorage != null)
{
_arrayStorage = Array.Empty<KeyValuePair<string, object>>();
_propertyStorage = null;
_count = 0;
return;
}
Array.Clear(_arrayStorage, 0, _count);
_count = 0;
}
/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
return TryGetValue(item.Key, out var value) && EqualityComparer<object>.Default.Equals(value, item.Value);
}
/// <inheritdoc />
public bool ContainsKey(string key)
{
if (key == null)
{
ThrowArgumentNullExceptionForKey();
}
return TryGetValue(key, out var _);
}
/// <inheritdoc />
void ICollection<KeyValuePair<string, object>>.CopyTo(
KeyValuePair<string, object>[] array,
int arrayIndex)
{
if (array == null)
{
throw new ArgumentNullException(nameof(array));
}
if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
if (Count == 0)
{
return;
}
EnsureCapacity(Count);
var storage = _arrayStorage;
Array.Copy(storage, 0, array, arrayIndex, _count);
}
/// <inheritdoc />
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
/// <inheritdoc />
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
if (Count == 0)
{
return false;
}
EnsureCapacity(Count);
var index = FindIndex(item.Key);
var array = _arrayStorage;
if (index >= 0 && EqualityComparer<object>.Default.Equals(array[index].Value, item.Value))
{
Array.Copy(array, index + 1, array, index, _count - index);
_count--;
array[_count] = default;
return true;
}
return false;
}
/// <inheritdoc />
public bool Remove(string key)
{
if (key == null)
{
ThrowArgumentNullExceptionForKey();
}
if (Count == 0)
{
return false;
}
EnsureCapacity(Count);
var index = FindIndex(key);
if (index >= 0)
{
_count--;
var array = _arrayStorage;
Array.Copy(array, index + 1, array, index, _count - index);
array[_count] = default;
return true;
}
return false;
}
/// <summary>
/// Attempts to the add the provided <paramref name="key"/> and <paramref name="value"/> to the dictionary.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns>Returns <c>true</c> if the value was added. Returns <c>false</c> if the key was already present.</returns>
public bool TryAdd(string key, object value)
{
if (key == null)
{
ThrowArgumentNullExceptionForKey();
}
// Since this is an attempt to write to the dictionary, just make it an array if it isn't. If the code
// path we're on event tries to write to the dictionary, it will likely get 'upgraded' at some point,
// so we do it here to keep the code size and complexity down.
EnsureCapacity(Count);
var index = FindIndex(key);
if (index >= 0)
{
return false;
}
EnsureCapacity(Count + 1);
_arrayStorage[Count] = new KeyValuePair<string, object>(key, value);
_count++;
return true;
}
/// <inheritdoc />
public bool TryGetValue(string key, out object value)
{
if (key == null)
{
ThrowArgumentNullExceptionForKey();
}
if (_propertyStorage == null)
{
return TryFindItem(key, out value);
}
return TryGetValueSlow(key, out value);
}
private bool TryGetValueSlow(string key, out object value)
{
if (_propertyStorage != null)
{
var storage = _propertyStorage;
for (var i = 0; i < storage.Properties.Length; i++)
{
if (string.Equals(storage.Properties[i].Name, key, StringComparison.OrdinalIgnoreCase))
{
value = storage.Properties[i].GetValue(storage.Value);
return true;
}
}
}
value = default;
return false;
}
private static void ThrowArgumentNullExceptionForKey()
{
throw new ArgumentNullException("key");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacity(int capacity)
{
if (_propertyStorage != null || _arrayStorage.Length < capacity)
{
EnsureCapacitySlow(capacity);
}
}
private void EnsureCapacitySlow(int capacity)
{
if (_propertyStorage != null)
{
var storage = _propertyStorage;
// If we're converting from properties, it's likely due to an 'add' to make sure we have at least
// the default amount of space.
capacity = Math.Max(DefaultCapacity, Math.Max(storage.Properties.Length, capacity));
var array = new KeyValuePair<string, object>[capacity];
for (var i = 0; i < storage.Properties.Length; i++)
{
var property = storage.Properties[i];
array[i] = new KeyValuePair<string, object>(property.Name, property.GetValue(storage.Value));
}
_arrayStorage = array;
_propertyStorage = null;
return;
}
if (_arrayStorage.Length < capacity)
{
capacity = _arrayStorage.Length == 0 ? DefaultCapacity : _arrayStorage.Length * 2;
var array = new KeyValuePair<string, object>[capacity];
if (_count > 0)
{
Array.Copy(_arrayStorage, 0, array, 0, _count);
}
_arrayStorage = array;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int FindIndex(string key)
{
// Generally the bounds checking here will be elided by the JIT because this will be called
// on the same code path as EnsureCapacity.
var array = _arrayStorage;
var count = _count;
for (var i = 0; i < count; i++)
{
if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
{
return i;
}
}
return -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryFindItem(string key, out object value)
{
var array = _arrayStorage;
var count = _count;
// Elide bounds check for indexing.
if ((uint)count <= (uint)array.Length)
{
for (var i = 0; i < count; i++)
{
if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
{
value = array[i].Value;
return true;
}
}
}
value = null;
return false;
}
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
{
private readonly RouteValueDictionary _dictionary;
private int _index;
public Enumerator(RouteValueDictionary dictionary)
{
if (dictionary == null)
{
throw new ArgumentNullException();
}
_dictionary = dictionary;
Current = default;
_index = 0;
}
public KeyValuePair<string, object> Current { get; private set; }
object IEnumerator.Current => Current;
public void Dispose()
{
}
// Similar to the design of List<T>.Enumerator - Split into fast path and slow path for inlining friendliness
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
var dictionary = _dictionary;
// The uncommon case is that the propertyStorage is in use
if (dictionary._propertyStorage == null && ((uint)_index < (uint)dictionary._count))
{
Current = dictionary._arrayStorage[_index];
_index++;
return true;
}
return MoveNextRare();
}
private bool MoveNextRare()
{
var dictionary = _dictionary;
if (dictionary._propertyStorage != null && ((uint)_index < (uint)dictionary._count))
{
var storage = dictionary._propertyStorage;
var property = storage.Properties[_index];
Current = new KeyValuePair<string, object>(property.Name, property.GetValue(storage.Value));
_index++;
return true;
}
_index = dictionary._count;
Current = default;
return false;
}
public void Reset()
{
Current = default;
_index = 0;
}
}
internal class PropertyStorage
{
private static readonly ConcurrentDictionary<Type, PropertyHelper[]> _propertyCache = new ConcurrentDictionary<Type, PropertyHelper[]>();
public readonly object Value;
public readonly PropertyHelper[] Properties;
public PropertyStorage(object value)
{
Debug.Assert(value != null);
Value = value;
// Cache the properties so we can know if we've already validated them for duplicates.
var type = Value.GetType();
if (!_propertyCache.TryGetValue(type, out Properties))
{
Properties = PropertyHelper.GetVisibleProperties(type);
ValidatePropertyNames(type, Properties);
_propertyCache.TryAdd(type, Properties);
}
}
private static void ValidatePropertyNames(Type type, PropertyHelper[] properties)
{
var names = new Dictionary<string, PropertyHelper>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < properties.Length; i++)
{
var property = properties[i];
if (names.TryGetValue(property.Name, out var duplicate))
{
var message = Resources.FormatRouteValueDictionary_DuplicatePropertyName(
type.FullName,
property.Name,
duplicate.Name,
nameof(RouteValueDictionary));
throw new InvalidOperationException(message);
}
names.Add(property.Name, property);
}
}
}
}
}

View File

@ -7,16 +7,28 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Internal
namespace Microsoft.AspNetCore.Builder
{
public static class EndpointRoutingApplicationBuilderExtensions
{
private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered";
public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder)
{
return builder.UseEndpointRouting(null);
}
public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder, Action<EndpointDataSourceBuilder> configure)
{
VerifyRoutingIsRegistered(builder);
if (configure != null)
{
var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)builder.ApplicationServices.GetRequiredService<EndpointDataSourceBuilder>();
dataSourceBuilder.ApplicationBuilder = builder;
configure(dataSourceBuilder);
}
builder.Properties[EndpointRoutingRegisteredKey] = true;
return builder.UseMiddleware<EndpointRoutingMiddleware>();
@ -50,4 +62,4 @@ namespace Microsoft.AspNetCore.Internal
}
}
}
}
}

View File

@ -0,0 +1,69 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Builder
{
public static class MapEndpointEndpointDataSourceBuilderExtensions
{
public static RouteEndpointBuilder MapEndpoint(
this EndpointDataSourceBuilder builder,
RequestDelegate requestDelegate,
string pattern,
string displayName)
{
return MapEndpoint(builder, requestDelegate, pattern, displayName, metadata: null);
}
public static RouteEndpointBuilder MapEndpoint(
this EndpointDataSourceBuilder builder,
RequestDelegate requestDelegate,
RoutePattern pattern,
string displayName)
{
return MapEndpoint(builder, requestDelegate, pattern, displayName, metadata: null);
}
public static RouteEndpointBuilder MapEndpoint(
this EndpointDataSourceBuilder builder,
RequestDelegate requestDelegate,
string pattern,
string displayName,
params object[] metadata)
{
return MapEndpoint(builder, requestDelegate, RoutePatternFactory.Parse(pattern), displayName, metadata);
}
public static RouteEndpointBuilder MapEndpoint(
this EndpointDataSourceBuilder builder,
RequestDelegate requestDelegate,
RoutePattern pattern,
string displayName,
params object[] metadata)
{
const int defaultOrder = 0;
var endpointBuilder = new RouteEndpointBuilder(
requestDelegate,
pattern,
defaultOrder);
endpointBuilder.DisplayName = displayName;
if (metadata != null)
{
foreach (var item in metadata)
{
endpointBuilder.Metadata.Add(item);
}
}
builder.Endpoints.Add(endpointBuilder);
return endpointBuilder;
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Routing
{
internal class BuilderEndpointDataSource : EndpointDataSource
{
private readonly EndpointDataSourceBuilder _builder;
public BuilderEndpointDataSource(EndpointDataSourceBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
_builder = builder;
}
public override IChangeToken GetChangeToken()
{
return NullChangeToken.Singleton;
}
public override IReadOnlyList<Endpoint> Endpoints => _builder.Endpoints.Select(b => b.Build()).ToArray();
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
namespace Microsoft.AspNetCore.Routing
{
internal class DefaultEndpointDataSourceBuilder : EndpointDataSourceBuilder
{
public IApplicationBuilder ApplicationBuilder { get; set; }
public override ICollection<EndpointBuilder> Endpoints { get; } = new List<EndpointBuilder>();
public override IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New();
}
}

View File

@ -59,6 +59,12 @@ namespace Microsoft.Extensions.DependencyInjection
return new CompositeEndpointDataSource(options.Value.DataSources);
});
//
// Endpoint Infrastructure
//
services.TryAddSingleton<EndpointDataSource, BuilderEndpointDataSource>();
services.TryAddSingleton<EndpointDataSourceBuilder, DefaultEndpointDataSourceBuilder>();
//
// Default matcher implementation
//

View File

@ -0,0 +1,19 @@
// 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.Http;
namespace Microsoft.AspNetCore.Routing
{
public abstract class EndpointBuilder
{
public RequestDelegate RequestDelegate { get; set; }
public string DisplayName { get; set; }
public IList<object> Metadata { get; } = new List<object>();
public abstract Endpoint Build();
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
namespace Microsoft.AspNetCore.Routing
{
public abstract class EndpointDataSourceBuilder
{
public abstract ICollection<EndpointBuilder> Endpoints { get; }
public abstract IApplicationBuilder CreateApplicationBuilder();
}
}

View File

@ -0,0 +1,37 @@
// 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.Http;
using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing
{
public sealed class RouteEndpointBuilder : EndpointBuilder
{
public RoutePattern RoutePattern { get; set; }
public int Order { get; set; }
public RouteEndpointBuilder(
RequestDelegate requestDelegate,
RoutePattern routePattern,
int order)
{
RequestDelegate = requestDelegate;
RoutePattern = routePattern;
Order = order;
}
public override Endpoint Build()
{
var matcherEndpoint = new RouteEndpoint(
RequestDelegate,
RoutePattern,
Order,
new EndpointMetadataCollection(Metadata),
DisplayName);
return matcherEndpoint;
}
}
}

View File

@ -1,142 +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.Text;
using Microsoft.AspNetCore.Http;
using Xunit;
namespace Microsoft.AspNetCore.Routing
{
public class EndpointMetadataCollectionTests
{
[Fact]
public void Constructor_Enumeration_ContainsValues()
{
// Arrange & Act
var metadata = new EndpointMetadataCollection(new List<object>
{
1,
2,
3,
});
// Assert
Assert.Equal(3, metadata.Count);
Assert.Collection(metadata,
value => Assert.Equal(1, value),
value => Assert.Equal(2, value),
value => Assert.Equal(3, value));
}
[Fact]
public void Constructor_ParamsArray_ContainsValues()
{
// Arrange & Act
var metadata = new EndpointMetadataCollection(1, 2, 3);
// Assert
Assert.Equal(3, metadata.Count);
Assert.Collection(metadata,
value => Assert.Equal(1, value),
value => Assert.Equal(2, value),
value => Assert.Equal(3, value));
}
[Fact]
public void GetMetadata_Match_ReturnsLastMatchingEntry()
{
// Arrange
var items = new object[]
{
new Metadata1(),
new Metadata2(),
new Metadata3(),
};
var metadata = new EndpointMetadataCollection(items);
// Act
var result = metadata.GetMetadata<IMetadata5>();
// Assert
Assert.Same(items[1], result);
}
[Fact]
public void GetMetadata_NoMatch_ReturnsNull()
{
// Arrange
var items = new object[]
{
new Metadata3(),
new Metadata3(),
new Metadata3(),
};
var metadata = new EndpointMetadataCollection(items);
// Act
var result = metadata.GetMetadata<IMetadata5>();
// Assert
Assert.Null(result);
}
[Fact]
public void GetOrderedMetadata_Match_ReturnsItemsInAscendingOrder()
{
// Arrange
var items = new object[]
{
new Metadata1(),
new Metadata2(),
new Metadata3(),
};
var metadata = new EndpointMetadataCollection(items);
// Act
var result = metadata.GetOrderedMetadata<IMetadata5>();
// Assert
Assert.Collection(
result,
i => Assert.Same(items[0], i),
i => Assert.Same(items[1], i));
}
[Fact]
public void GetOrderedMetadata_NoMatch_ReturnsEmpty()
{
// Arrange
var items = new object[]
{
new Metadata3(),
new Metadata3(),
new Metadata3(),
};
var metadata = new EndpointMetadataCollection(items);
// Act
var result = metadata.GetOrderedMetadata<IMetadata5>();
// Assert
Assert.Empty(result);
}
private interface IMetadata1 { }
private interface IMetadata2 { }
private interface IMetadata3 { }
private interface IMetadata4 { }
private interface IMetadata5 { }
private class Metadata1 : IMetadata1, IMetadata4, IMetadata5 { }
private class Metadata2 : IMetadata2, IMetadata5 { }
private class Metadata3 : IMetadata3 { }
}
}

View File

@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
{
// Arrange
var expectedContentType = "text/plain";
var expectedContent = "Hello, World!";
var expectedContent = "Plain text!";
// Act
var response = await _client.GetAsync("/plaintext");
@ -62,6 +62,25 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
Assert.Equal(expectedContent, actualContent);
}
[Fact]
public async Task MatchesHelloMiddleware_AndReturnsPlaintext()
{
// Arrange
var expectedContentType = "text/plain";
var expectedContent = "Hello World";
// Act
var response = await _client.GetAsync("/helloworld");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content);
Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
var actualContent = await response.Content.ReadAsStringAsync();
Assert.Equal(expectedContent, actualContent);
}
[Fact]
public async Task MatchesEndpoint_WithSuccessfulConstraintMatch()
{

View File

@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
@ -15,7 +14,7 @@ using Xunit;
namespace Microsoft.AspNetCore.Builder
{
public class EndpointRoutingBuilderExtensionsTest
public class EndpointRoutingApplicationBuilderExtensionsTest
{
[Fact]
public void UseEndpointRouting_ServicesNotRegistered_Throws()
@ -140,6 +139,26 @@ namespace Microsoft.AspNetCore.Builder
Assert.Null(httpContext.Features.Get<IEndpointFeature>());
}
[Fact]
public void UseEndpointRouting_CallWithBuilder_SetsEndpointBuilder()
{
// Arrange
var services = CreateServices();
var app = new ApplicationBuilder(services);
// Act
app.UseEndpointRouting(builder =>
{
builder.MapEndpoint(d => null, "/", "Test endpoint");
});
// Assert
var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)services.GetRequiredService<EndpointDataSourceBuilder>();
var endpointBuilder = Assert.Single(dataSourceBuilder.Endpoints);
Assert.Equal("Test endpoint", endpointBuilder.DisplayName);
}
private IServiceProvider CreateServices(params Endpoint[] endpoints)
{
var services = new ServiceCollection();

View File

@ -0,0 +1,89 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
namespace Microsoft.AspNetCore.Builder
{
public class MapEndpointEndpointDataSourceBuilderExtensionsTest
{
[Fact]
public void MapEndpoint_StringPattern_BuildsEndpoint()
{
// Arrange
var builder = new DefaultEndpointDataSourceBuilder();
RequestDelegate requestDelegate = (d) => null;
// Act
var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!");
// Assert
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints));
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate);
Assert.Equal("Display name!", endpointBuilder.DisplayName);
Assert.Equal("/", endpointBuilder.RoutePattern.RawText);
}
[Fact]
public void MapEndpoint_TypedPattern_BuildsEndpoint()
{
// Arrange
var builder = new DefaultEndpointDataSourceBuilder();
RequestDelegate requestDelegate = (d) => null;
// Act
var endpointBuilder = builder.MapEndpoint(requestDelegate, RoutePatternFactory.Parse("/"), "Display name!");
// Assert
Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints));
Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate);
Assert.Equal("Display name!", endpointBuilder.DisplayName);
Assert.Equal("/", endpointBuilder.RoutePattern.RawText);
}
[Fact]
public void MapEndpoint_StringPatternAndMetadata_BuildsEndpoint()
{
// Arrange
var metadata = new object();
var builder = new DefaultEndpointDataSourceBuilder();
RequestDelegate requestDelegate = (d) => null;
// Act
var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!", 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));
}
[Fact]
public void MapEndpoint_TypedPatternAndMetadata_BuildsEndpoint()
{
// Arrange
var metadata = new object();
var builder = new DefaultEndpointDataSourceBuilder();
RequestDelegate requestDelegate = (d) => null;
// Act
var endpointBuilder = builder.MapEndpoint(requestDelegate, RoutePatternFactory.Parse("/"), "Display name!", 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));
}
}
}

View File

@ -1,10 +1,11 @@
// 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.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Routing
{

View File

@ -21,7 +21,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
httpContext.Request.Path = path;
httpContext.RequestServices = CreateServices();
var feature = new EndpointFeature();
var feature = new EndpointFeature
{
RouteValues = new RouteValueDictionary()
};
httpContext.Features.Set<IEndpointFeature>(feature);
httpContext.Features.Set<IRouteValuesFeature>(feature);

View File

@ -0,0 +1,36 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
namespace Microsoft.AspNetCore.Routing.Matching
{
public class MatcherEndpointBuilderTest
{
[Fact]
public void Build_AllValuesSet_EndpointCreated()
{
const int defaultOrder = 0;
object metadata = new object();
RequestDelegate requestDelegate = (d) => null;
var builder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder)
{
DisplayName = "Display name!",
Metadata = { metadata }
};
var endpoint = Assert.IsType<RouteEndpoint>(builder.Build());
Assert.Equal("Display name!", endpoint.DisplayName);
Assert.Equal(defaultOrder, endpoint.Order);
Assert.Equal(requestDelegate, endpoint.RequestDelegate);
Assert.Equal("/", endpoint.RoutePattern.RawText);
Assert.Equal(metadata, Assert.Single(endpoint.Metadata));
}
}
}

View File

@ -1,7 +1,7 @@
<Project>
<Project>
<PropertyGroup>
<VersionPrefix>2.2.0</VersionPrefix>
<VersionSuffix>preview3</VersionSuffix>
<VersionPrefix>3.0.0</VersionPrefix>
<VersionSuffix>alpha1</VersionSuffix>
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' == 'rtm' ">$(VersionPrefix)</PackageVersion>
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' != 'rtm' ">$(VersionPrefix)-$(VersionSuffix)-final</PackageVersion>
<BuildNumber Condition="'$(BuildNumber)' == ''">t000</BuildNumber>