Merge branch 'master' into merge/release/2.2-to-master
This commit is contained in:
commit
54ef2ef2a8
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
version:2.2.0-preview1-20180911.1
|
||||
commithash:ddfecdfc6e8e4859db5a0daea578070b862aac65
|
||||
version:3.0.0-alpha1-20180911.2
|
||||
commithash:2a2b7dbea1b247930c41da497f4ea0b2bb756818
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 { }
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue