Merge remote-tracking branch 'Routing/rybrande/masterToSrc' into rybrande/Mondo22ToMaster
This commit is contained in:
commit
01b58c9a3d
|
|
@ -1,10 +1,6 @@
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp3.0' ">$(MicrosoftNETCoreAppPackageVersion)</RuntimeFrameworkVersion>
|
||||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
|
|
||||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.2' ">$(MicrosoftNETCoreApp22PackageVersion)</RuntimeFrameworkVersion>
|
|
||||||
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
|
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
|
||||||
<!-- aspnet/BuildTools#662 Don't police what version of NetCoreApp we use -->
|
|
||||||
<NETCoreAppMaximumVersion>99.9</NETCoreAppMaximumVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
ASP.NET Routing
|
ASP.NET Routing [Archived]
|
||||||
===
|
===========================
|
||||||
|
|
||||||
AppVeyor: [](https://ci.appveyor.com/project/aspnetci/Routing/branch/master)
|
**This GitHub project has been archived.** Ongoing development on this project can be found in <https://github.com/aspnet/AspNetCore>.
|
||||||
|
|
||||||
Travis: [](https://travis-ci.org/aspnet/Routing)
|
|
||||||
|
|
||||||
Contains routing middleware for routing requests to application logic.
|
Contains routing middleware for routing requests to application logic.
|
||||||
|
|
||||||
This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
|
This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<TargetFramework Condition="'$(BenchmarksTargetFramework)' != ''">$(BenchmarksTargetFramework)</TargetFramework>
|
<TargetFramework Condition="'$(BenchmarksTargetFramework)' != ''">$(BenchmarksTargetFramework)</TargetFramework>
|
||||||
<UseP2PReferences Condition="'$(UseP2PReferences)'=='' AND '$(BenchmarksTargetFramework)'==''">true</UseP2PReferences>
|
<UseP2PReferences Condition="'$(UseP2PReferences)'=='' AND '$(BenchmarksTargetFramework)'==''">true</UseP2PReferences>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@
|
||||||
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Internal;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
|
|
||||||
namespace Benchmarks
|
namespace Benchmarks
|
||||||
{
|
{
|
||||||
|
|
@ -18,8 +17,13 @@ namespace Benchmarks
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddRouting();
|
services.AddRouting();
|
||||||
|
}
|
||||||
|
|
||||||
var endpointDataSource = new DefaultEndpointDataSource(new[]
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseEndpointRouting(builder =>
|
||||||
|
{
|
||||||
|
var endpointDataSource = new DefaultEndpointDataSource(new[]
|
||||||
{
|
{
|
||||||
new RouteEndpoint(
|
new RouteEndpoint(
|
||||||
requestDelegate: (httpContext) =>
|
requestDelegate: (httpContext) =>
|
||||||
|
|
@ -37,14 +41,10 @@ namespace Benchmarks
|
||||||
displayName: "Plaintext"),
|
displayName: "Plaintext"),
|
||||||
});
|
});
|
||||||
|
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
builder.DataSources.Add(endpointDataSource);
|
||||||
}
|
});
|
||||||
|
|
||||||
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
|
|
||||||
{
|
|
||||||
app.UseEndpointRouting();
|
|
||||||
|
|
||||||
app.UseEndpoint();
|
app.UseEndpoint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
},
|
},
|
||||||
"Source": {
|
"Source": {
|
||||||
"Repository": "https://github.com/aspnet/routing.git",
|
"Repository": "https://github.com/aspnet/routing.git",
|
||||||
"BranchOrCommit": "release/2.2",
|
"BranchOrCommit": "master",
|
||||||
"Project": "benchmarkapps/Benchmarks/Benchmarks.csproj"
|
"Project": "benchmarkapps/Benchmarks/Benchmarks.csproj"
|
||||||
},
|
},
|
||||||
"Port": 8080
|
"Port": 8080
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
|
|
||||||
|
|
@ -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,43 +4,39 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Package Versions">
|
<PropertyGroup Label="Package Versions">
|
||||||
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
|
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
|
||||||
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview2-20181011.10</InternalAspNetCoreSdkPackageVersion>
|
<InternalAspNetCoreSdkPackageVersion>3.0.0-build-20181114.5</InternalAspNetCoreSdkPackageVersion>
|
||||||
<MicrosoftAspNetCoreAppPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreAppPackageVersion>
|
<MicrosoftAspNetCoreAppPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreAppPackageVersion>
|
||||||
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
|
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>3.0.0-preview-181113-11</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
|
||||||
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHostingPackageVersion>
|
<MicrosoftAspNetCoreHostingPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreHostingPackageVersion>
|
||||||
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
|
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreHttpPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHttpPackageVersion>
|
<MicrosoftAspNetCoreHttpPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreHttpPackageVersion>
|
||||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||||
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
<MicrosoftAspNetCoreStaticFilesPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
||||||
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreTestHostPackageVersion>
|
<MicrosoftAspNetCoreTestHostPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||||
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreTestingPackageVersion>
|
<MicrosoftAspNetCoreTestingPackageVersion>3.0.0-preview-181113-11</MicrosoftAspNetCoreTestingPackageVersion>
|
||||||
<MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>
|
<MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>
|
||||||
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
|
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
|
||||||
<MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>
|
<MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>
|
||||||
<MicrosoftExtensionsConfigurationPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsConfigurationPackageVersion>
|
<MicrosoftExtensionsConfigurationPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsConfigurationPackageVersion>
|
||||||
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
|
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
|
||||||
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
<MicrosoftExtensionsDependencyInjectionPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
||||||
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
|
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
|
||||||
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
||||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsLoggingConsolePackageVersion>
|
<MicrosoftExtensionsLoggingConsolePackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||||
<MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsLoggingPackageVersion>
|
<MicrosoftExtensionsLoggingPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsLoggingPackageVersion>
|
||||||
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsLoggingTestingPackageVersion>
|
<MicrosoftExtensionsLoggingTestingPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsLoggingTestingPackageVersion>
|
||||||
<MicrosoftExtensionsObjectPoolPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsObjectPoolPackageVersion>
|
<MicrosoftExtensionsObjectPoolPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsObjectPoolPackageVersion>
|
||||||
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsOptionsPackageVersion>
|
<MicrosoftExtensionsOptionsPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsOptionsPackageVersion>
|
||||||
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
|
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
|
||||||
<MicrosoftExtensionsWebEncodersPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsWebEncodersPackageVersion>
|
<MicrosoftExtensionsWebEncodersPackageVersion>3.0.0-preview-181113-11</MicrosoftExtensionsWebEncodersPackageVersion>
|
||||||
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
|
<MicrosoftNETCoreAppPackageVersion>3.0.0-preview1-26907-05</MicrosoftNETCoreAppPackageVersion>
|
||||||
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
|
|
||||||
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-27008-03</MicrosoftNETCoreApp22PackageVersion>
|
|
||||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||||
<MoqPackageVersion>4.10.0</MoqPackageVersion>
|
<MoqPackageVersion>4.10.0</MoqPackageVersion>
|
||||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||||
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
|
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
|
||||||
<SystemReflectionEmitLightweightPackageVersion>4.3.0</SystemReflectionEmitLightweightPackageVersion>
|
|
||||||
<SystemReflectionEmitPackageVersion>4.3.0</SystemReflectionEmitPackageVersion>
|
|
||||||
<XunitAnalyzersPackageVersion>0.10.0</XunitAnalyzersPackageVersion>
|
<XunitAnalyzersPackageVersion>0.10.0</XunitAnalyzersPackageVersion>
|
||||||
<XunitPackageVersion>2.3.1</XunitPackageVersion>
|
<XunitPackageVersion>2.3.1</XunitPackageVersion>
|
||||||
<XunitRunnerVisualStudioPackageVersion>2.4.0</XunitRunnerVisualStudioPackageVersion>
|
<XunitRunnerVisualStudioPackageVersion>2.4.0</XunitRunnerVisualStudioPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,11 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- These properties are use by the automation that updates dependencies.props -->
|
<!-- These properties are use by the automation that updates dependencies.props -->
|
||||||
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
|
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
|
||||||
<LineupPackageVersion>2.2.0-*</LineupPackageVersion>
|
|
||||||
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
|
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
|
<DotNetCoreRuntime Include="$(MicrosoftNETCoreAppPackageVersion)" />
|
||||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
|
|
||||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp22PackageVersion)" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using RoutingSample.Web.AuthorizationMiddleware;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
public static class AuthorizationAppBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.UseMiddleware<AuthorizationMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace RoutingSample.Web.AuthorizationMiddleware
|
||||||
|
{
|
||||||
|
public class AuthorizationMetadata
|
||||||
|
{
|
||||||
|
public AuthorizationMetadata(IEnumerable<string> roles)
|
||||||
|
{
|
||||||
|
if (roles == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(roles));
|
||||||
|
}
|
||||||
|
|
||||||
|
Roles = roles.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<string> Roles { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace RoutingSample.Web.AuthorizationMiddleware
|
||||||
|
{
|
||||||
|
public class AuthorizationMiddleware
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
public AuthorizationMiddleware(ILogger<AuthorizationMiddleware> logger, RequestDelegate next)
|
||||||
|
{
|
||||||
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(next));
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger = logger;
|
||||||
|
_next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
var endpoint = httpContext.Features.Get<IEndpointFeature>()?.Endpoint;
|
||||||
|
if (endpoint != null)
|
||||||
|
{
|
||||||
|
var metadata = endpoint.Metadata.GetMetadata<AuthorizationMetadata>();
|
||||||
|
// Only run authorization if endpoint has metadata
|
||||||
|
if (metadata != null)
|
||||||
|
{
|
||||||
|
if (!httpContext.Request.Query.TryGetValue("x-role", out var role) ||
|
||||||
|
!metadata.Roles.Contains(role.ToString()))
|
||||||
|
{
|
||||||
|
httpContext.Response.StatusCode = 401;
|
||||||
|
httpContext.Response.ContentType = "text/plain";
|
||||||
|
await httpContext.Response.WriteAsync($"Unauthorized access to '{endpoint.DisplayName}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _next(httpContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using RoutingSample.Web.AuthorizationMiddleware;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
public static class EndpointConventionBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IEndpointConventionBuilder RequireAuthorization(this IEndpointConventionBuilder builder, params string[] roles)
|
||||||
|
{
|
||||||
|
builder.Apply(endpointBuilder => endpointBuilder.Metadata.Add(new AuthorizationMetadata(roles)));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 EndpointRouteBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IEndpointConventionBuilder MapHello(this IEndpointRouteBuilder builder, string template, string greeter)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
var pipeline = builder.CreateApplicationBuilder()
|
||||||
|
.UseHello(greeter)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
return builder.Map(
|
||||||
|
template,
|
||||||
|
"Hello " + greeter,
|
||||||
|
pipeline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,8 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net461</TargetFrameworks>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Internal;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
@ -19,65 +19,70 @@ namespace RoutingSandbox
|
||||||
public class UseEndpointRoutingStartup
|
public class UseEndpointRoutingStartup
|
||||||
{
|
{
|
||||||
private static readonly byte[] _homePayload = Encoding.UTF8.GetBytes("Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext");
|
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)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
var endpointDataSource = new DefaultEndpointDataSource(new[]
|
services.AddRouting();
|
||||||
{
|
|
||||||
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) =>
|
|
||||||
{
|
|
||||||
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"),
|
|
||||||
});
|
|
||||||
|
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app)
|
public void Configure(IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
app.UseEndpointRouting();
|
app.UseEndpointRouting(builder =>
|
||||||
|
{
|
||||||
|
builder.MapHello("/helloworld", "World");
|
||||||
|
|
||||||
|
builder.MapHello("/helloworld-secret", "Secret World")
|
||||||
|
.RequireAuthorization("swordfish");
|
||||||
|
|
||||||
|
builder.MapGet(
|
||||||
|
"/",
|
||||||
|
(httpContext) =>
|
||||||
|
{
|
||||||
|
var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("Endpoints:");
|
||||||
|
foreach (var endpoint in dataSource.Endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
sb.AppendLine($"- {endpoint.RoutePattern.RawText}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = httpContext.Response;
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
return response.WriteAsync(sb.ToString());
|
||||||
|
});
|
||||||
|
builder.MapGet(
|
||||||
|
"/plaintext",
|
||||||
|
(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);
|
||||||
|
});
|
||||||
|
builder.MapGet(
|
||||||
|
"/graph",
|
||||||
|
"DFA Graph",
|
||||||
|
(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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
// Imagine some more stuff here...
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseEndpoint();
|
app.UseEndpoint();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 instance of <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 instance of <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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
Commonly used types:
|
Commonly used types:
|
||||||
Microsoft.AspNetCore.Routing.IRouter
|
Microsoft.AspNetCore.Routing.IRouter
|
||||||
Microsoft.AspNetCore.Routing.RouteData</Description>
|
Microsoft.AspNetCore.Routing.RouteData</Description>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<PackageTags>aspnetcore;routing</PackageTags>
|
<PackageTags>aspnetcore;routing</PackageTags>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,15 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
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("Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
[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,718 +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 instance of <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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure property storage is converted to array storage as we'll be
|
|
||||||
// applying the lookup and removal on the array
|
|
||||||
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 remove and return the value that has the specified key from the <see cref="RouteValueDictionary"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The key of the element to remove and return.</param>
|
|
||||||
/// <param name="value">When this method returns, contains the object removed from the <see cref="RouteValueDictionary"/>, or <c>null</c> if key does not exist.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <c>true</c> if the object was removed successfully; otherwise, <c>false</c>.
|
|
||||||
/// </returns>
|
|
||||||
public bool Remove(string key, out object value)
|
|
||||||
{
|
|
||||||
if (key == null)
|
|
||||||
{
|
|
||||||
ThrowArgumentNullExceptionForKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_count == 0)
|
|
||||||
{
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure property storage is converted to array storage as we'll be
|
|
||||||
// applying the lookup and removal on the array
|
|
||||||
EnsureCapacity(_count);
|
|
||||||
|
|
||||||
var index = FindIndex(key);
|
|
||||||
if (index >= 0)
|
|
||||||
{
|
|
||||||
_count--;
|
|
||||||
var array = _arrayStorage;
|
|
||||||
value = array[index].Value;
|
|
||||||
Array.Copy(array, index + 1, array, index, _count - index);
|
|
||||||
array[_count] = default;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
public static class EndpointRouteBuilderExtensions
|
||||||
|
{
|
||||||
|
// Avoid creating a new array every call
|
||||||
|
private static readonly string[] GetVerb = new[] { "GET" };
|
||||||
|
private static readonly string[] PostVerb = new[] { "POST" };
|
||||||
|
private static readonly string[] PutVerb = new[] { "PUT" };
|
||||||
|
private static readonly string[] DeleteVerb = new[] { "DELETE" };
|
||||||
|
|
||||||
|
#region MapVerbs
|
||||||
|
public static IEndpointConventionBuilder MapGet(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return MapVerbs(builder, pattern, displayName: null, requestDelegate, GetVerb, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder MapGet(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
string displayName,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return MapVerbs(builder, pattern, displayName, requestDelegate, GetVerb, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder MapPost(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return MapVerbs(builder, pattern, displayName: null, requestDelegate, PostVerb, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder MapPost(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
string displayName,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return MapVerbs(builder, pattern, displayName, requestDelegate, PostVerb, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder MapPut(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return MapVerbs(builder, pattern, displayName: null, requestDelegate, PutVerb, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder MapPut(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
string displayName,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return MapVerbs(builder, pattern, displayName, requestDelegate, PutVerb, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder MapDelete(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return MapVerbs(builder, pattern, displayName: null, requestDelegate, DeleteVerb, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder MapDelete(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
string displayName,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return MapVerbs(builder, pattern, displayName, requestDelegate, DeleteVerb, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder MapVerbs(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
IList<string> httpMethods,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return MapVerbs(builder, pattern, displayName: null, requestDelegate, httpMethods, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder MapVerbs(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
string displayName,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
IList<string> httpMethods,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
if (httpMethods == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(httpMethods));
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedMetadata = new List<object>();
|
||||||
|
resolvedMetadata.Add(new HttpMethodMetadata(httpMethods));
|
||||||
|
if (metadata != null)
|
||||||
|
{
|
||||||
|
resolvedMetadata.AddRange(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Map(builder, pattern, displayName ?? $"{pattern} HTTP: {string.Join(", ", httpMethods)}", requestDelegate, metadata: resolvedMetadata.ToArray());
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Map
|
||||||
|
public static IEndpointConventionBuilder Map(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return Map(builder, RoutePatternFactory.Parse(pattern), pattern, requestDelegate, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder Map(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
string pattern,
|
||||||
|
string displayName,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return Map(builder, RoutePatternFactory.Parse(pattern), displayName, requestDelegate, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder Map(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
RoutePattern pattern,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
return Map(builder, pattern, pattern.RawText ?? pattern.DebuggerToString(), requestDelegate, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEndpointConventionBuilder Map(
|
||||||
|
this IEndpointRouteBuilder builder,
|
||||||
|
RoutePattern pattern,
|
||||||
|
string displayName,
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
params object[] metadata)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestDelegate == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(requestDelegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
const int defaultOrder = 0;
|
||||||
|
|
||||||
|
var routeEndpointModel = new RouteEndpointModel(
|
||||||
|
requestDelegate,
|
||||||
|
pattern,
|
||||||
|
defaultOrder);
|
||||||
|
routeEndpointModel.DisplayName = displayName;
|
||||||
|
if (metadata != null)
|
||||||
|
{
|
||||||
|
foreach (var item in metadata)
|
||||||
|
{
|
||||||
|
routeEndpointModel.Metadata.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var modelEndpointDataSource = builder.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (modelEndpointDataSource == null)
|
||||||
|
{
|
||||||
|
modelEndpointDataSource = new ModelEndpointDataSource();
|
||||||
|
builder.DataSources.Add(modelEndpointDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modelEndpointDataSource.AddEndpointModel(routeEndpointModel);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,25 +2,48 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Internal
|
namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
public static class EndpointRoutingApplicationBuilderExtensions
|
public static class EndpointRoutingApplicationBuilderExtensions
|
||||||
{
|
{
|
||||||
// Property key is used by MVC package to check that routing is registered
|
// Property key is used by MVC package to check that routing is registered
|
||||||
private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered";
|
private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered";
|
||||||
|
|
||||||
public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder)
|
public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
|
||||||
{
|
{
|
||||||
|
if (configure == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configure));
|
||||||
|
}
|
||||||
|
|
||||||
VerifyRoutingIsRegistered(builder);
|
VerifyRoutingIsRegistered(builder);
|
||||||
|
|
||||||
|
var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
|
||||||
|
EndpointDataSource middlewareEndpointDataSource;
|
||||||
|
|
||||||
|
var endpointRouteBuilder = builder.ApplicationServices.GetRequiredService<IEndpointRouteBuilder>();
|
||||||
|
if (endpointRouteBuilder is DefaultEndpointRouteBuilder defaultEndpointRouteBuilder)
|
||||||
|
{
|
||||||
|
defaultEndpointRouteBuilder.ApplicationBuilder = builder;
|
||||||
|
}
|
||||||
|
configure(endpointRouteBuilder);
|
||||||
|
|
||||||
|
foreach (var dataSource in endpointRouteBuilder.DataSources)
|
||||||
|
{
|
||||||
|
routeOptions.Value.EndpointDataSources.Add(dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create endpoint data source for data sources registered in configure
|
||||||
|
middlewareEndpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
|
||||||
|
|
||||||
builder.Properties[EndpointRoutingRegisteredKey] = true;
|
builder.Properties[EndpointRoutingRegisteredKey] = true;
|
||||||
|
|
||||||
return builder.UseMiddleware<EndpointRoutingMiddleware>();
|
return builder.UseMiddleware<EndpointRoutingMiddleware>(middlewareEndpointDataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IApplicationBuilder UseEndpoint(this IApplicationBuilder builder)
|
public static IApplicationBuilder UseEndpoint(this IApplicationBuilder builder)
|
||||||
|
|
@ -51,4 +74,4 @@ namespace Microsoft.AspNetCore.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -18,24 +20,49 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
[DebuggerDisplay("{DebuggerDisplayString,nq}")]
|
[DebuggerDisplay("{DebuggerDisplayString,nq}")]
|
||||||
public sealed class CompositeEndpointDataSource : EndpointDataSource
|
public sealed class CompositeEndpointDataSource : EndpointDataSource
|
||||||
{
|
{
|
||||||
private readonly EndpointDataSource[] _dataSources;
|
|
||||||
private readonly object _lock;
|
private readonly object _lock;
|
||||||
|
private readonly ICollection<EndpointDataSource> _dataSources;
|
||||||
private IReadOnlyList<Endpoint> _endpoints;
|
private IReadOnlyList<Endpoint> _endpoints;
|
||||||
private IChangeToken _consumerChangeToken;
|
private IChangeToken _consumerChangeToken;
|
||||||
private CancellationTokenSource _cts;
|
private CancellationTokenSource _cts;
|
||||||
|
|
||||||
internal CompositeEndpointDataSource(IEnumerable<EndpointDataSource> dataSources)
|
private CompositeEndpointDataSource()
|
||||||
{
|
{
|
||||||
if (dataSources == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(dataSources));
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateChangeToken();
|
CreateChangeToken();
|
||||||
_dataSources = dataSources.ToArray();
|
|
||||||
_lock = new object();
|
_lock = new object();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal CompositeEndpointDataSource(ObservableCollection<EndpointDataSource> dataSources) : this()
|
||||||
|
{
|
||||||
|
dataSources.CollectionChanged += OnDataSourcesChanged;
|
||||||
|
|
||||||
|
_dataSources = dataSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompositeEndpointDataSource(IEnumerable<EndpointDataSource> endpointDataSources) : this()
|
||||||
|
{
|
||||||
|
_dataSources = new List<EndpointDataSource>();
|
||||||
|
|
||||||
|
foreach (var dataSource in endpointDataSources)
|
||||||
|
{
|
||||||
|
_dataSources.Add(dataSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDataSourcesChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
// Only trigger changes if composite data source has already initialized endpoints
|
||||||
|
if (_endpoints != null)
|
||||||
|
{
|
||||||
|
HandleChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<EndpointDataSource> DataSources => _dataSources;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
|
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
|
||||||
/// instances.
|
/// instances.
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
internal class ConfigureEndpointOptions : IConfigureOptions<EndpointOptions>
|
internal class ConfigureRouteOptions : IConfigureOptions<RouteOptions>
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<EndpointDataSource> _dataSources;
|
private readonly ICollection<EndpointDataSource> _dataSources;
|
||||||
|
|
||||||
public ConfigureEndpointOptions(IEnumerable<EndpointDataSource> dataSources)
|
public ConfigureRouteOptions(ICollection<EndpointDataSource> dataSources)
|
||||||
{
|
{
|
||||||
if (dataSources == null)
|
if (dataSources == null)
|
||||||
{
|
{
|
||||||
|
|
@ -22,17 +23,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
_dataSources = dataSources;
|
_dataSources = dataSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(EndpointOptions options)
|
public void Configure(RouteOptions options)
|
||||||
{
|
{
|
||||||
if (options == null)
|
if (options == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(options));
|
throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var dataSource in _dataSources)
|
options.EndpointDataSources = _dataSources;
|
||||||
{
|
|
||||||
options.DataSources.Add(dataSource);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
internal class DefaultEndpointRouteBuilder : IEndpointRouteBuilder
|
||||||
|
{
|
||||||
|
public DefaultEndpointRouteBuilder()
|
||||||
|
{
|
||||||
|
DataSources = new List<EndpointDataSource>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IApplicationBuilder ApplicationBuilder { get; set; }
|
||||||
|
|
||||||
|
public IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New();
|
||||||
|
|
||||||
|
public ICollection<EndpointDataSource> DataSources { get; }
|
||||||
|
|
||||||
|
public IServiceProvider ServiceProvider => ApplicationBuilder.ApplicationServices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
public DefaultLinkGenerator(
|
public DefaultLinkGenerator(
|
||||||
ParameterPolicyFactory parameterPolicyFactory,
|
ParameterPolicyFactory parameterPolicyFactory,
|
||||||
CompositeEndpointDataSource dataSource,
|
EndpointDataSource dataSource,
|
||||||
ObjectPool<UriBuildingContext> uriBuildingContextPool,
|
ObjectPool<UriBuildingContext> uriBuildingContextPool,
|
||||||
IOptions<RouteOptions> routeOptions,
|
IOptions<RouteOptions> routeOptions,
|
||||||
ILogger<DefaultLinkGenerator> logger,
|
ILogger<DefaultLinkGenerator> logger,
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.AspNetCore.Routing.Tree;
|
using Microsoft.AspNetCore.Routing.Tree;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -49,16 +51,23 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
|
||||||
services.TryAddSingleton(typeof(RoutingMarkerService));
|
services.TryAddSingleton(typeof(RoutingMarkerService));
|
||||||
|
|
||||||
// Collect all data sources from DI.
|
// Setup global collection of endpoint data sources
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<EndpointOptions>, ConfigureEndpointOptions>());
|
var dataSources = new ObservableCollection<EndpointDataSource>();
|
||||||
|
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, ConfigureRouteOptions>(
|
||||||
|
serviceProvider => new ConfigureRouteOptions(dataSources)));
|
||||||
|
|
||||||
// Allow global access to the list of endpoints.
|
// Allow global access to the list of endpoints.
|
||||||
services.TryAddSingleton<CompositeEndpointDataSource>(s =>
|
services.TryAddSingleton<EndpointDataSource>(s =>
|
||||||
{
|
{
|
||||||
var options = s.GetRequiredService<IOptions<EndpointOptions>>();
|
// Call internal ctor and pass global collection
|
||||||
return new CompositeEndpointDataSource(options.Value.DataSources);
|
return new CompositeEndpointDataSource(dataSources);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Endpoint Infrastructure
|
||||||
|
//
|
||||||
|
services.TryAddTransient<IEndpointRouteBuilder, DefaultEndpointRouteBuilder>();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Default matcher implementation
|
// Default matcher implementation
|
||||||
//
|
//
|
||||||
|
|
@ -78,6 +87,11 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddSingleton<EndpointSelector, DefaultEndpointSelector>();
|
services.TryAddSingleton<EndpointSelector, DefaultEndpointSelector>();
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, HttpMethodMatcherPolicy>());
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, HttpMethodMatcherPolicy>());
|
||||||
|
|
||||||
|
//
|
||||||
|
// Misc infrastructure
|
||||||
|
//
|
||||||
|
services.TryAddSingleton<RoutePatternTransformer, DefaultRoutePatternTransformer>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 EndpointModel
|
||||||
|
{
|
||||||
|
public RequestDelegate RequestDelegate { get; set; }
|
||||||
|
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
public IList<object> Metadata { get; } = new List<object>();
|
||||||
|
|
||||||
|
public abstract Endpoint Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
private readonly DataSourceDependentCache<Dictionary<string, Endpoint[]>> _cache;
|
private readonly DataSourceDependentCache<Dictionary<string, Endpoint[]>> _cache;
|
||||||
|
|
||||||
public EndpointNameAddressScheme(CompositeEndpointDataSource dataSource)
|
public EndpointNameAddressScheme(EndpointDataSource dataSource)
|
||||||
{
|
{
|
||||||
_cache = new DataSourceDependentCache<Dictionary<string, Endpoint[]>>(dataSource, Initialize);
|
_cache = new DataSourceDependentCache<Dictionary<string, Endpoint[]>>(dataSource, Initialize);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
|
||||||
{
|
|
||||||
// Internal for 2.2. Public API for configuring endpoints will be added in 3.0
|
|
||||||
internal class EndpointOptions
|
|
||||||
{
|
|
||||||
public IList<EndpointDataSource> DataSources { get; } = new List<EndpointDataSource>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,14 +16,14 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
private readonly MatcherFactory _matcherFactory;
|
private readonly MatcherFactory _matcherFactory;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly CompositeEndpointDataSource _endpointDataSource;
|
private readonly EndpointDataSource _endpointDataSource;
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
private Task<Matcher> _initializationTask;
|
private Task<Matcher> _initializationTask;
|
||||||
|
|
||||||
public EndpointRoutingMiddleware(
|
public EndpointRoutingMiddleware(
|
||||||
MatcherFactory matcherFactory,
|
MatcherFactory matcherFactory,
|
||||||
CompositeEndpointDataSource endpointDataSource,
|
EndpointDataSource endpointDataSource,
|
||||||
ILogger<EndpointRoutingMiddleware> logger,
|
ILogger<EndpointRoutingMiddleware> logger,
|
||||||
RequestDelegate next)
|
RequestDelegate next)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
public interface IEndpointConventionBuilder
|
||||||
|
{
|
||||||
|
void Apply(Action<EndpointModel> convention);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
public interface IEndpointRouteBuilder
|
||||||
|
{
|
||||||
|
IApplicationBuilder CreateApplicationBuilder();
|
||||||
|
|
||||||
|
IServiceProvider ServiceProvider { get; }
|
||||||
|
|
||||||
|
ICollection<EndpointDataSource> DataSources { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// LICENSING NOTE: This file is from the dotnet corefx repository.
|
||||||
|
//
|
||||||
|
// See https://github.com/dotnet/corefx/blob/143df51926f2ad397fef9c9ca7ede88e2721e801/src/Common/src/System/Collections/Generic/ArrayBuilder.cs
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper type for avoiding allocations while building arrays.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||||
|
internal struct ArrayBuilder<T>
|
||||||
|
{
|
||||||
|
private const int DefaultCapacity = 4;
|
||||||
|
private const int MaxCoreClrArrayLength = 0x7fefffff; // For byte arrays the limit is slightly larger
|
||||||
|
|
||||||
|
private T[] _array; // Starts out null, initialized on first Add.
|
||||||
|
private int _count; // Number of items into _array we're using.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the <see cref="ArrayBuilder{T}"/> with a specified capacity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="capacity">The capacity of the array to allocate.</param>
|
||||||
|
public ArrayBuilder(int capacity) : this()
|
||||||
|
{
|
||||||
|
Debug.Assert(capacity >= 0);
|
||||||
|
if (capacity > 0)
|
||||||
|
{
|
||||||
|
_array = new T[capacity];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of items this instance can store without re-allocating,
|
||||||
|
/// or 0 if the backing array is <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
|
public int Capacity => _array?.Length ?? 0;
|
||||||
|
|
||||||
|
/// <summary>Gets the current underlying array.</summary>
|
||||||
|
public T[] Buffer => _array;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of items in the array currently in use.
|
||||||
|
/// </summary>
|
||||||
|
public int Count => _count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item at a certain index in the array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index into the array.</param>
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Debug.Assert(index >= 0 && index < _count);
|
||||||
|
return _array[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an item to the backing array, resizing it if necessary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to add.</param>
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
if (_count == Capacity)
|
||||||
|
{
|
||||||
|
EnsureCapacity(_count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
UncheckedAdd(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the first item in this builder.
|
||||||
|
/// </summary>
|
||||||
|
public T First()
|
||||||
|
{
|
||||||
|
Debug.Assert(_count > 0);
|
||||||
|
return _array[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last item in this builder.
|
||||||
|
/// </summary>
|
||||||
|
public T Last()
|
||||||
|
{
|
||||||
|
Debug.Assert(_count > 0);
|
||||||
|
return _array[_count - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an array from the contents of this builder.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Do not call this method twice on the same builder.
|
||||||
|
/// </remarks>
|
||||||
|
public T[] ToArray()
|
||||||
|
{
|
||||||
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(_array != null); // Nonzero _count should imply this
|
||||||
|
|
||||||
|
T[] result = _array;
|
||||||
|
if (_count < result.Length)
|
||||||
|
{
|
||||||
|
// Avoid a bit of overhead (method call, some branches, extra codegen)
|
||||||
|
// which would be incurred by using Array.Resize
|
||||||
|
result = new T[_count];
|
||||||
|
Array.Copy(_array, 0, result, 0, _count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
// Try to prevent callers from using the ArrayBuilder after ToArray, if _count != 0.
|
||||||
|
_count = -1;
|
||||||
|
_array = null;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an item to the backing array, without checking if there is room.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to add.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Use this method if you know there is enough space in the <see cref="ArrayBuilder{T}"/>
|
||||||
|
/// for another item, and you are writing performance-sensitive code.
|
||||||
|
/// </remarks>
|
||||||
|
public void UncheckedAdd(T item)
|
||||||
|
{
|
||||||
|
Debug.Assert(_count < Capacity);
|
||||||
|
|
||||||
|
_array[_count++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int minimum)
|
||||||
|
{
|
||||||
|
Debug.Assert(minimum > Capacity);
|
||||||
|
|
||||||
|
int capacity = Capacity;
|
||||||
|
int nextCapacity = capacity == 0 ? DefaultCapacity : 2 * capacity;
|
||||||
|
|
||||||
|
if ((uint)nextCapacity > (uint)MaxCoreClrArrayLength)
|
||||||
|
{
|
||||||
|
nextCapacity = Math.Max(capacity + 1, MaxCoreClrArrayLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCapacity = Math.Max(nextCapacity, minimum);
|
||||||
|
|
||||||
|
T[] next = new T[nextCapacity];
|
||||||
|
if (_count > 0)
|
||||||
|
{
|
||||||
|
Array.Copy(_array, 0, next, 0, _count);
|
||||||
|
}
|
||||||
|
_array = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -119,12 +119,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
||||||
|
|
||||||
private class OutboundMatchClassifier : IClassifier<OutboundMatch>
|
private class OutboundMatchClassifier : IClassifier<OutboundMatch>
|
||||||
{
|
{
|
||||||
public OutboundMatchClassifier()
|
public IEqualityComparer<object> ValueComparer => RouteValueEqualityComparer.Default;
|
||||||
{
|
|
||||||
ValueComparer = new RouteValueEqualityComparer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEqualityComparer<object> ValueComparer { get; private set; }
|
|
||||||
|
|
||||||
public IDictionary<string, DecisionCriterionValue> GetCriteria(OutboundMatch item)
|
public IDictionary<string, DecisionCriterionValue> GetCriteria(OutboundMatch item)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if IL_EMIT
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -596,5 +595,3 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
#if IL_EMIT
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -101,4 +100,3 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -84,11 +84,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
fallback = new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
|
fallback = new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if IL_EMIT
|
|
||||||
return new ILEmitTrieJumpTable(defaultDestination, exitDestination, pathEntries, vectorize: null, fallback);
|
return new ILEmitTrieJumpTable(defaultDestination, exitDestination, pathEntries, vectorize: null, fallback);
|
||||||
#else
|
|
||||||
return fallback;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
Commonly used types:
|
Commonly used types:
|
||||||
Microsoft.AspNetCore.Routing.Route
|
Microsoft.AspNetCore.Routing.Route
|
||||||
Microsoft.AspNetCore.Routing.RouteCollection</Description>
|
Microsoft.AspNetCore.Routing.RouteCollection</Description>
|
||||||
<TargetFrameworks>netstandard2.0;netcoreapp2.2</TargetFrameworks>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<PackageTags>aspnetcore;routing</PackageTags>
|
<PackageTags>aspnetcore;routing</PackageTags>
|
||||||
|
|
@ -12,14 +12,10 @@ Microsoft.AspNetCore.Routing.RouteCollection</Description>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!--
|
<!--
|
||||||
RefEmit is supported in netcoreapp.
|
|
||||||
|
|
||||||
The ability to save compiled assemblies is for testing and debugging, not shipped in the product.
|
The ability to save compiled assemblies is for testing and debugging, not shipped in the product.
|
||||||
-->
|
-->
|
||||||
<ILEmit Condition="'$(TargetFramework)'!='netstandard2.0'">true</ILEmit>
|
|
||||||
<ILEmitSaveAssemblies Condition="'$(ILEmitSaveAssemblies)'==''">false</ILEmitSaveAssemblies>
|
<ILEmitSaveAssemblies Condition="'$(ILEmitSaveAssemblies)'==''">false</ILEmitSaveAssemblies>
|
||||||
<DefineConstants Condition="'$(ILEmit)'=='true'">IL_EMIT;$(DefineConstants)</DefineConstants>
|
|
||||||
<DefineConstants Condition="'$(ILEmitSaveAssemblies)'=='true'">IL_EMIT_SAVE_ASSEMBLIES;$(DefineConstants)</DefineConstants>
|
<DefineConstants Condition="'$(ILEmitSaveAssemblies)'=='true'">IL_EMIT_SAVE_ASSEMBLIES;$(DefineConstants)</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
internal class ModelEndpointDataSource : EndpointDataSource
|
||||||
|
{
|
||||||
|
private List<EndpointConventionBuilder> _endpointConventionBuilders;
|
||||||
|
|
||||||
|
public ModelEndpointDataSource()
|
||||||
|
{
|
||||||
|
_endpointConventionBuilders = new List<EndpointConventionBuilder>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEndpointConventionBuilder AddEndpointModel(EndpointModel endpointModel)
|
||||||
|
{
|
||||||
|
var builder = new EndpointConventionBuilder(endpointModel);
|
||||||
|
_endpointConventionBuilders.Add(builder);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IChangeToken GetChangeToken()
|
||||||
|
{
|
||||||
|
return NullChangeToken.Singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IReadOnlyList<Endpoint> Endpoints => _endpointConventionBuilders.Select(e => e.Build()).ToArray();
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
internal IEnumerable<EndpointModel> EndpointModels => _endpointConventionBuilders.Select(b => b.EndpointModel);
|
||||||
|
|
||||||
|
private class EndpointConventionBuilder : IEndpointConventionBuilder
|
||||||
|
{
|
||||||
|
internal EndpointModel EndpointModel { get; }
|
||||||
|
|
||||||
|
private readonly List<Action<EndpointModel>> _conventions;
|
||||||
|
|
||||||
|
public EndpointConventionBuilder(EndpointModel endpointModel)
|
||||||
|
{
|
||||||
|
EndpointModel = endpointModel;
|
||||||
|
_conventions = new List<Action<EndpointModel>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply(Action<EndpointModel> convention)
|
||||||
|
{
|
||||||
|
_conventions.Add(convention);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Endpoint Build()
|
||||||
|
{
|
||||||
|
foreach (var convention in _conventions)
|
||||||
|
{
|
||||||
|
convention(EndpointModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EndpointModel.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,227 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
|
{
|
||||||
|
internal class DefaultRoutePatternTransformer : RoutePatternTransformer
|
||||||
|
{
|
||||||
|
private readonly ParameterPolicyFactory _policyFactory;
|
||||||
|
|
||||||
|
public DefaultRoutePatternTransformer(ParameterPolicyFactory policyFactory)
|
||||||
|
{
|
||||||
|
if (policyFactory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(policyFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
_policyFactory = policyFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues)
|
||||||
|
{
|
||||||
|
if (original == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(original));
|
||||||
|
}
|
||||||
|
|
||||||
|
return SubstituteRequiredValuesCore(original, new RouteValueDictionary(requiredValues));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RoutePattern SubstituteRequiredValuesCore(RoutePattern original, RouteValueDictionary requiredValues)
|
||||||
|
{
|
||||||
|
// Process each required value in sequence. Bail if we find any rejection criteria. The goal
|
||||||
|
// of rejection is to avoid creating RoutePattern instances that can't *ever* match.
|
||||||
|
//
|
||||||
|
// If we succeed, then we need to create a new RoutePattern with the provided required values.
|
||||||
|
//
|
||||||
|
// Substitution can merge with existing RequiredValues already on the RoutePattern as long
|
||||||
|
// as all of the success criteria are still met at the end.
|
||||||
|
foreach (var kvp in requiredValues)
|
||||||
|
{
|
||||||
|
// There are three possible cases here:
|
||||||
|
// 1. Required value is null-ish
|
||||||
|
// 2. Required value corresponds to a parameter
|
||||||
|
// 3. Required value corresponds to a matching default value
|
||||||
|
//
|
||||||
|
// If none of these are true then we can reject this substitution.
|
||||||
|
RoutePatternParameterPart parameter;
|
||||||
|
if (RouteValueEqualityComparer.Default.Equals(kvp.Value, string.Empty))
|
||||||
|
{
|
||||||
|
// 1. Required value is null-ish - check to make sure that this route doesn't have a
|
||||||
|
// parameter or filter-like default.
|
||||||
|
|
||||||
|
if (original.GetParameter(kvp.Key) != null)
|
||||||
|
{
|
||||||
|
// Fail: we can't 'require' that a parameter be null. In theory this would be possible
|
||||||
|
// for an optional parameter, but that's not really in line with the usage of this feature
|
||||||
|
// so we don't handle it.
|
||||||
|
//
|
||||||
|
// Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = "" }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
|
||||||
|
!RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
|
||||||
|
{
|
||||||
|
// Fail: this route has a non-parameter default that doesn't match.
|
||||||
|
//
|
||||||
|
// Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = "" }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success: (for this parameter at least)
|
||||||
|
//
|
||||||
|
// Ex: {controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if ((parameter = original.GetParameter(kvp.Key)) != null)
|
||||||
|
{
|
||||||
|
// 2. Required value corresponds to a parameter - check to make sure that this value matches
|
||||||
|
// any IRouteConstraint implementations.
|
||||||
|
if (!MatchesConstraints(original, parameter, kvp.Key, requiredValues))
|
||||||
|
{
|
||||||
|
// Fail: this route has a constraint that failed.
|
||||||
|
//
|
||||||
|
// Ex: Admin/{controller:regex(Home|Login)}/{action=Index}/{id?} - with required values: { controller = "Store" }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success: (for this parameter at least)
|
||||||
|
//
|
||||||
|
// Ex: {area}/{controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
|
||||||
|
RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
|
||||||
|
{
|
||||||
|
// 3. Required value corresponds to a matching default value - check to make sure that this value matches
|
||||||
|
// any IRouteConstraint implementations. It's unlikely that this would happen in practice but it doesn't
|
||||||
|
// hurt for us to check.
|
||||||
|
if (!MatchesConstraints(original, parameter: null, kvp.Key, requiredValues))
|
||||||
|
{
|
||||||
|
// Fail: this route has a constraint that failed.
|
||||||
|
//
|
||||||
|
// Ex:
|
||||||
|
// Admin/Home/{action=Index}/{id?}
|
||||||
|
// defaults: { area = "Admin" }
|
||||||
|
// constraints: { area = "Blog" }
|
||||||
|
// with required values: { area = "Admin" }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success: (for this parameter at least)
|
||||||
|
//
|
||||||
|
// Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Admin", ... }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fail: this is a required value for a key that doesn't appear in the templates, or the route
|
||||||
|
// pattern has a different default value for a non-parameter.
|
||||||
|
//
|
||||||
|
// Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Blog", ... }
|
||||||
|
// OR (less likely)
|
||||||
|
// Ex: Admin/{controller=Home}/{action=Index}/{id?} with required values: { page = "/Index", ... }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RoutePatternParameterPart> updatedParameters = null;
|
||||||
|
List<RoutePatternPathSegment> updatedSegments = null;
|
||||||
|
RouteValueDictionary updatedDefaults = null;
|
||||||
|
|
||||||
|
// So if we get here, we're ready to update the route pattern. We need to update two things:
|
||||||
|
// 1. Remove any default values that conflict with the required values.
|
||||||
|
// 2. Merge any existing required values
|
||||||
|
foreach (var kvp in requiredValues)
|
||||||
|
{
|
||||||
|
var parameter = original.GetParameter(kvp.Key);
|
||||||
|
|
||||||
|
// We only need to handle the case where the required value maps to a parameter. That's the only
|
||||||
|
// case where we allow a default and a required value to disagree, and we already validated the
|
||||||
|
// other cases.
|
||||||
|
if (parameter != null &&
|
||||||
|
original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
|
||||||
|
!RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
|
||||||
|
{
|
||||||
|
if (updatedDefaults == null && updatedSegments == null && updatedParameters == null)
|
||||||
|
{
|
||||||
|
updatedDefaults = new RouteValueDictionary(original.Defaults);
|
||||||
|
updatedSegments = new List<RoutePatternPathSegment>(original.PathSegments);
|
||||||
|
updatedParameters = new List<RoutePatternParameterPart>(original.Parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedDefaults.Remove(kvp.Key);
|
||||||
|
RemoveParameterDefault(updatedSegments, updatedParameters, parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kvp in original.RequiredValues)
|
||||||
|
{
|
||||||
|
requiredValues.TryAdd(kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RoutePattern(
|
||||||
|
original.RawText,
|
||||||
|
updatedDefaults ?? original.Defaults,
|
||||||
|
original.ParameterPolicies,
|
||||||
|
requiredValues,
|
||||||
|
updatedParameters ?? original.Parameters,
|
||||||
|
updatedSegments ?? original.PathSegments);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MatchesConstraints(RoutePattern pattern, RoutePatternParameterPart parameter, string key, RouteValueDictionary requiredValues)
|
||||||
|
{
|
||||||
|
if (pattern.ParameterPolicies.TryGetValue(key, out var policies))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < policies.Count; i++)
|
||||||
|
{
|
||||||
|
var policy = _policyFactory.Create(parameter, policies[i]);
|
||||||
|
if (policy is IRouteConstraint constraint)
|
||||||
|
{
|
||||||
|
if (!constraint.Match(httpContext: null, NullRouter.Instance, key, requiredValues, RouteDirection.IncomingRequest))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveParameterDefault(List<RoutePatternPathSegment> segments, List<RoutePatternParameterPart> parameters, RoutePatternParameterPart parameter)
|
||||||
|
{
|
||||||
|
// We know that a parameter can only appear once, so we only need to rewrite one segment and one parameter.
|
||||||
|
for (var i = 0; i < segments.Count; i++)
|
||||||
|
{
|
||||||
|
var segment = segments[i];
|
||||||
|
for (var j = 0; j < segment.Parts.Count; j++)
|
||||||
|
{
|
||||||
|
if (object.ReferenceEquals(parameter, segment.Parts[j]))
|
||||||
|
{
|
||||||
|
// Found it!
|
||||||
|
var updatedParameter = RoutePatternFactory.ParameterPart(parameter.Name, @default: null, parameter.ParameterKind, parameter.ParameterPolicies);
|
||||||
|
|
||||||
|
var updatedParts = new List<RoutePatternPart>(segment.Parts);
|
||||||
|
updatedParts[j] = updatedParameter;
|
||||||
|
segments[i] = RoutePatternFactory.Segment(updatedParts);
|
||||||
|
|
||||||
|
for (var k = 0; k < parameters.Count; k++)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(parameter, parameters[k]))
|
||||||
|
{
|
||||||
|
parameters[k] = updatedParameter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Patterns
|
namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
{
|
{
|
||||||
|
|
@ -77,7 +76,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parseResults = ParseConstraints(parameter, parameterName, currentIndex, endIndex);
|
var parseResults = ParseConstraints(parameter, currentIndex, endIndex);
|
||||||
currentIndex = parseResults.CurrentIndex;
|
currentIndex = parseResults.CurrentIndex;
|
||||||
|
|
||||||
string defaultValue = null;
|
string defaultValue = null;
|
||||||
|
|
@ -91,17 +90,16 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
parameterName,
|
parameterName,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
parameterKind,
|
parameterKind,
|
||||||
parseResults.ParameterPolicies.ToArray(),
|
parseResults.ParameterPolicies,
|
||||||
encodeSlashes);
|
encodeSlashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ParameterPolicyParseResults ParseConstraints(
|
private static ParameterPolicyParseResults ParseConstraints(
|
||||||
string text,
|
string text,
|
||||||
string parameterName,
|
|
||||||
int currentIndex,
|
int currentIndex,
|
||||||
int endIndex)
|
int endIndex)
|
||||||
{
|
{
|
||||||
var constraints = new List<RoutePatternParameterPolicyReference>();
|
var constraints = new ArrayBuilder<RoutePatternParameterPolicyReference>(0);
|
||||||
var state = ParseState.Start;
|
var state = ParseState.Start;
|
||||||
var startIndex = currentIndex;
|
var startIndex = currentIndex;
|
||||||
do
|
do
|
||||||
|
|
@ -234,7 +232,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
|
|
||||||
} while (state != ParseState.End);
|
} while (state != ParseState.End);
|
||||||
|
|
||||||
return new ParameterPolicyParseResults(currentIndex, constraints);
|
return new ParameterPolicyParseResults(currentIndex, constraints.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ParseState
|
private enum ParseState
|
||||||
|
|
@ -249,9 +247,9 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
{
|
{
|
||||||
public readonly int CurrentIndex;
|
public readonly int CurrentIndex;
|
||||||
|
|
||||||
public readonly IReadOnlyList<RoutePatternParameterPolicyReference> ParameterPolicies;
|
public readonly RoutePatternParameterPolicyReference[] ParameterPolicies;
|
||||||
|
|
||||||
public ParameterPolicyParseResults(int currentIndex, IReadOnlyList<RoutePatternParameterPolicyReference> parameterPolicies)
|
public ParameterPolicyParseResults(int currentIndex, RoutePatternParameterPolicyReference[] parameterPolicies)
|
||||||
{
|
{
|
||||||
CurrentIndex = currentIndex;
|
CurrentIndex = currentIndex;
|
||||||
ParameterPolicies = parameterPolicies;
|
ParameterPolicies = parameterPolicies;
|
||||||
|
|
|
||||||
|
|
@ -23,17 +23,20 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
string rawText,
|
string rawText,
|
||||||
IReadOnlyDictionary<string, object> defaults,
|
IReadOnlyDictionary<string, object> defaults,
|
||||||
IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> parameterPolicies,
|
IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> parameterPolicies,
|
||||||
|
IReadOnlyDictionary<string, object> requiredValues,
|
||||||
IReadOnlyList<RoutePatternParameterPart> parameters,
|
IReadOnlyList<RoutePatternParameterPart> parameters,
|
||||||
IReadOnlyList<RoutePatternPathSegment> pathSegments)
|
IReadOnlyList<RoutePatternPathSegment> pathSegments)
|
||||||
{
|
{
|
||||||
Debug.Assert(defaults != null);
|
Debug.Assert(defaults != null);
|
||||||
Debug.Assert(parameterPolicies != null);
|
Debug.Assert(parameterPolicies != null);
|
||||||
Debug.Assert(parameters != null);
|
Debug.Assert(parameters != null);
|
||||||
|
Debug.Assert(requiredValues != null);
|
||||||
Debug.Assert(pathSegments != null);
|
Debug.Assert(pathSegments != null);
|
||||||
|
|
||||||
RawText = rawText;
|
RawText = rawText;
|
||||||
Defaults = defaults;
|
Defaults = defaults;
|
||||||
ParameterPolicies = parameterPolicies;
|
ParameterPolicies = parameterPolicies;
|
||||||
|
RequiredValues = requiredValues;
|
||||||
Parameters = parameters;
|
Parameters = parameters;
|
||||||
PathSegments = pathSegments;
|
PathSegments = pathSegments;
|
||||||
|
|
||||||
|
|
@ -53,6 +56,29 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> ParameterPolicies { get; }
|
public IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> ParameterPolicies { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a collection of route values that must be provided for this route pattern to be considered
|
||||||
|
/// applicable.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="RequiredValues"/> allows a framework to substitute route values into a parameterized template
|
||||||
|
/// so that the same route template specification can be used to create multiple route patterns.
|
||||||
|
/// <example>
|
||||||
|
/// This example shows how a route template can be used with required values to substitute known
|
||||||
|
/// route values for parameters.
|
||||||
|
/// <code>
|
||||||
|
/// Route Template: "{controller=Home}/{action=Index}/{id?}"
|
||||||
|
/// Route Values: { controller = "Store", action = "Index" }
|
||||||
|
/// </code>
|
||||||
|
///
|
||||||
|
/// A route pattern produced in this way will match and generate URL paths like: <c>/Store</c>,
|
||||||
|
/// <c>/Store/Index</c>, and <c>/Store/Index/17</c>.
|
||||||
|
/// </example>
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public IReadOnlyDictionary<string, object> RequiredValues { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the precedence value of the route pattern for URL matching.
|
/// Gets the precedence value of the route pattern for URL matching.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -110,7 +136,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DebuggerToString()
|
internal string DebuggerToString()
|
||||||
{
|
{
|
||||||
return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
|
return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class RoutePatternFactory
|
public static class RoutePatternFactory
|
||||||
{
|
{
|
||||||
private static readonly IReadOnlyDictionary<string, object> EmptyDefaultsDictionary =
|
private static readonly IReadOnlyDictionary<string, object> EmptyDictionary =
|
||||||
new ReadOnlyDictionary<string, object>(new Dictionary<string, object>());
|
new ReadOnlyDictionary<string, object>(new Dictionary<string, object>());
|
||||||
|
|
||||||
private static readonly IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> EmptyPoliciesDictionary =
|
private static readonly IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> EmptyPoliciesDictionary =
|
||||||
|
|
@ -61,7 +61,37 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
}
|
}
|
||||||
|
|
||||||
var original = RoutePatternParser.Parse(pattern);
|
var original = RoutePatternParser.Parse(pattern);
|
||||||
return Pattern(original.RawText, defaults, parameterPolicies, original.PathSegments);
|
return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), requiredValues: null, original.PathSegments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="RoutePattern"/> from its string representation along
|
||||||
|
/// with provided default values and parameter policies.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pattern">The route pattern string to parse.</param>
|
||||||
|
/// <param name="defaults">
|
||||||
|
/// Additional default values to associated with the route pattern. May be null.
|
||||||
|
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
|
||||||
|
/// and then merged into the parsed route pattern.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="parameterPolicies">
|
||||||
|
/// Additional parameter policies to associated with the route pattern. May be null.
|
||||||
|
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
|
||||||
|
/// and then merged into the parsed route pattern.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="requiredValues">
|
||||||
|
/// Route values that can be substituted for parameters in the route pattern. See remarks on <see cref="RoutePattern.RequiredValues"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The <see cref="RoutePattern"/>.</returns>
|
||||||
|
public static RoutePattern Parse(string pattern, object defaults, object parameterPolicies, object requiredValues)
|
||||||
|
{
|
||||||
|
if (pattern == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
var original = RoutePatternParser.Parse(pattern);
|
||||||
|
return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), Wrap(requiredValues), original.PathSegments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -76,7 +106,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
throw new ArgumentNullException(nameof(segments));
|
throw new ArgumentNullException(nameof(segments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PatternCore(null, null, null, segments);
|
return PatternCore(null, null, null, null, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -92,7 +122,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
throw new ArgumentNullException(nameof(segments));
|
throw new ArgumentNullException(nameof(segments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PatternCore(rawText, null, null, segments);
|
return PatternCore(rawText, null, null, null, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -121,14 +151,14 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
throw new ArgumentNullException(nameof(segments));
|
throw new ArgumentNullException(nameof(segments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), segments);
|
return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="RoutePattern"/> from a collection of segments along
|
/// Creates a <see cref="RoutePattern"/> from a collection of segments along
|
||||||
/// with provided default values and parameter policies.
|
/// with provided default values and parameter policies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rawText">The raw text to associate with the route pattern.</param>
|
/// <param name="rawText">The raw text to associate with the route pattern. May be null.</param>
|
||||||
/// <param name="defaults">
|
/// <param name="defaults">
|
||||||
/// Additional default values to associated with the route pattern. May be null.
|
/// Additional default values to associated with the route pattern. May be null.
|
||||||
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
|
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
|
||||||
|
|
@ -152,7 +182,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
throw new ArgumentNullException(nameof(segments));
|
throw new ArgumentNullException(nameof(segments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), segments);
|
return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -167,7 +197,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
throw new ArgumentNullException(nameof(segments));
|
throw new ArgumentNullException(nameof(segments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PatternCore(null, null, null, segments);
|
return PatternCore(null, null, null, requiredValues: null, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -183,7 +213,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
throw new ArgumentNullException(nameof(segments));
|
throw new ArgumentNullException(nameof(segments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PatternCore(rawText, null, null, segments);
|
return PatternCore(rawText, null, null, requiredValues: null, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -212,7 +242,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
throw new ArgumentNullException(nameof(segments));
|
throw new ArgumentNullException(nameof(segments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), segments);
|
return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -243,13 +273,14 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
throw new ArgumentNullException(nameof(segments));
|
throw new ArgumentNullException(nameof(segments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), segments);
|
return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RoutePattern PatternCore(
|
private static RoutePattern PatternCore(
|
||||||
string rawText,
|
string rawText,
|
||||||
RouteValueDictionary defaults,
|
RouteValueDictionary defaults,
|
||||||
RouteValueDictionary parameterPolicies,
|
RouteValueDictionary parameterPolicies,
|
||||||
|
RouteValueDictionary requiredValues,
|
||||||
IEnumerable<RoutePatternPathSegment> segments)
|
IEnumerable<RoutePatternPathSegment> segments)
|
||||||
{
|
{
|
||||||
// We want to merge the segment data with the 'out of line' defaults and parameter policies.
|
// We want to merge the segment data with the 'out of line' defaults and parameter policies.
|
||||||
|
|
@ -311,12 +342,56 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each Required Value either needs to either:
|
||||||
|
// 1. be null-ish
|
||||||
|
// 2. have a corresponding parameter
|
||||||
|
// 3. have a corrsponding default that matches both key and value
|
||||||
|
if (requiredValues != null)
|
||||||
|
{
|
||||||
|
foreach (var kvp in requiredValues)
|
||||||
|
{
|
||||||
|
// 1.be null-ish
|
||||||
|
var found = RouteValueEqualityComparer.Default.Equals(string.Empty, kvp.Value);
|
||||||
|
|
||||||
|
// 2. have a corresponding parameter
|
||||||
|
if (!found && parameters != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < parameters.Count; i++)
|
||||||
|
{
|
||||||
|
if (string.Equals(kvp.Key, parameters[i].Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. have a corrsponding default that matches both key and value
|
||||||
|
if (!found &&
|
||||||
|
updatedDefaults != null &&
|
||||||
|
updatedDefaults.TryGetValue(kvp.Key, out var defaultValue) &&
|
||||||
|
RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"No corresponding parameter or default value could be found for the required value " +
|
||||||
|
$"'{kvp.Key}={kvp.Value}'. A non-null required value must correspond to a route parameter or the " +
|
||||||
|
$"route pattern must have a matching default value.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new RoutePattern(
|
return new RoutePattern(
|
||||||
rawText,
|
rawText,
|
||||||
updatedDefaults ?? EmptyDefaultsDictionary,
|
updatedDefaults ?? EmptyDictionary,
|
||||||
updatedParameterPolicies != null
|
updatedParameterPolicies != null
|
||||||
? updatedParameterPolicies.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<RoutePatternParameterPolicyReference>)kvp.Value.ToArray())
|
? updatedParameterPolicies.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<RoutePatternParameterPolicyReference>)kvp.Value.ToArray())
|
||||||
: EmptyPoliciesDictionary,
|
: EmptyPoliciesDictionary,
|
||||||
|
requiredValues ?? EmptyDictionary,
|
||||||
(IReadOnlyList<RoutePatternParameterPart>)parameters ?? Array.Empty<RoutePatternParameterPart>(),
|
(IReadOnlyList<RoutePatternParameterPart>)parameters ?? Array.Empty<RoutePatternParameterPart>(),
|
||||||
updatedSegments);
|
updatedSegments);
|
||||||
|
|
||||||
|
|
@ -449,7 +524,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
throw new ArgumentNullException(nameof(parts));
|
throw new ArgumentNullException(nameof(parts));
|
||||||
}
|
}
|
||||||
|
|
||||||
return SegmentCore((RoutePatternPart[]) parts.Clone());
|
return SegmentCore((RoutePatternPart[])parts.Clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RoutePatternPathSegment SegmentCore(RoutePatternPart[] parts)
|
private static RoutePatternPathSegment SegmentCore(RoutePatternPart[] parts)
|
||||||
|
|
@ -670,7 +745,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
parameterName: parameterName,
|
parameterName: parameterName,
|
||||||
@default: @default,
|
@default: @default,
|
||||||
parameterKind: parameterKind,
|
parameterKind: parameterKind,
|
||||||
parameterPolicies: (RoutePatternParameterPolicyReference[]) parameterPolicies.Clone());
|
parameterPolicies: (RoutePatternParameterPolicyReference[])parameterPolicies.Clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RoutePatternParameterPart ParameterPartCore(
|
private static RoutePatternParameterPart ParameterPartCore(
|
||||||
|
|
@ -802,5 +877,10 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
{
|
{
|
||||||
return new RoutePatternParameterPolicyReference(parameterPolicy);
|
return new RoutePatternParameterPolicyReference(parameterPolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static RouteValueDictionary Wrap(object values)
|
||||||
|
{
|
||||||
|
return values == null ? null : new RouteValueDictionary(values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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.Routing.Patterns
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A singleton service that provides transformations on <see cref="RoutePattern"/>.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class RoutePatternTransformer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to substitute the provided <paramref name="requiredValues"/> into the provided
|
||||||
|
/// <paramref name="original"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="original">The original <see cref="RoutePattern"/>.</param>
|
||||||
|
/// <param name="requiredValues">The required values to substitute.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A new <see cref="RoutePattern"/> if substitution succeeds, otherwise <c>null</c>.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Substituting required values into a route pattern is intended for us with a general-purpose
|
||||||
|
/// parameterize route specification that can match many logical endpoints. Calling
|
||||||
|
/// <see cref="SubstituteRequiredValues(RoutePattern, object)"/> can produce a derived route pattern
|
||||||
|
/// for each set of route values that corresponds to an endpoint.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The substitution process considers default values and <see cref="IRouteConstraint"/> implementations
|
||||||
|
/// when examining a required value. <see cref="SubstituteRequiredValues(RoutePattern, object)"/> will
|
||||||
|
/// return <c>null</c> if any required value cannot be substituted.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public abstract RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 RouteEndpointModel : EndpointModel
|
||||||
|
{
|
||||||
|
public RoutePattern RoutePattern { get; set; }
|
||||||
|
|
||||||
|
public int Order { get; set; }
|
||||||
|
|
||||||
|
public RouteEndpointModel(
|
||||||
|
RequestDelegate requestDelegate,
|
||||||
|
RoutePattern routePattern,
|
||||||
|
int order)
|
||||||
|
{
|
||||||
|
RequestDelegate = requestDelegate;
|
||||||
|
RoutePattern = routePattern;
|
||||||
|
Order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Endpoint Build()
|
||||||
|
{
|
||||||
|
var routeEndpoint = new RouteEndpoint(
|
||||||
|
RequestDelegate,
|
||||||
|
RoutePattern,
|
||||||
|
Order,
|
||||||
|
new EndpointMetadataCollection(Metadata),
|
||||||
|
DisplayName);
|
||||||
|
|
||||||
|
return routeEndpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
public class RouteOptions
|
public class RouteOptions
|
||||||
{
|
{
|
||||||
|
public ICollection<EndpointDataSource> EndpointDataSources { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether all generated paths URLs are lower-case.
|
/// Gets or sets a value indicating whether all generated paths URLs are lower-case.
|
||||||
/// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
|
/// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class RouteValueEqualityComparer : IEqualityComparer<object>
|
public class RouteValueEqualityComparer : IEqualityComparer<object>
|
||||||
{
|
{
|
||||||
|
public static readonly RouteValueEqualityComparer Default = new RouteValueEqualityComparer();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public new bool Equals(object x, object y)
|
public new bool Equals(object x, object y)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
internal class RouteValuesAddressScheme : IEndpointAddressScheme<RouteValuesAddress>
|
internal class RouteValuesAddressScheme : IEndpointAddressScheme<RouteValuesAddress>
|
||||||
{
|
{
|
||||||
private readonly CompositeEndpointDataSource _dataSource;
|
private readonly EndpointDataSource _dataSource;
|
||||||
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
|
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
|
||||||
private Dictionary<string, List<OutboundMatchResult>> _namedMatchResults;
|
private Dictionary<string, List<OutboundMatchResult>> _namedMatchResults;
|
||||||
|
|
||||||
public RouteValuesAddressScheme(CompositeEndpointDataSource dataSource)
|
public RouteValuesAddressScheme(EndpointDataSource dataSource)
|
||||||
{
|
{
|
||||||
_dataSource = dataSource;
|
_dataSource = dataSource;
|
||||||
|
|
||||||
|
|
@ -125,12 +125,21 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var metadata = endpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||||
|
if (metadata == null && routeEndpoint.RoutePattern.RequiredValues.Count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (endpoint.Metadata.GetMetadata<ISuppressLinkGenerationMetadata>()?.SuppressLinkGeneration == true)
|
if (endpoint.Metadata.GetMetadata<ISuppressLinkGenerationMetadata>()?.SuppressLinkGeneration == true)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entry = CreateOutboundRouteEntry(routeEndpoint);
|
var entry = CreateOutboundRouteEntry(
|
||||||
|
routeEndpoint,
|
||||||
|
metadata?.RequiredValues ?? routeEndpoint.RoutePattern.RequiredValues,
|
||||||
|
metadata?.RouteName);
|
||||||
|
|
||||||
var outboundMatch = new OutboundMatch() { Entry = entry };
|
var outboundMatch = new OutboundMatch() { Entry = entry };
|
||||||
allOutboundMatches.Add(outboundMatch);
|
allOutboundMatches.Add(outboundMatch);
|
||||||
|
|
@ -151,18 +160,20 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
return (allOutboundMatches, namedOutboundMatchResults);
|
return (allOutboundMatches, namedOutboundMatchResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OutboundRouteEntry CreateOutboundRouteEntry(RouteEndpoint endpoint)
|
private OutboundRouteEntry CreateOutboundRouteEntry(
|
||||||
|
RouteEndpoint endpoint,
|
||||||
|
IReadOnlyDictionary<string, object> requiredValues,
|
||||||
|
string routeName)
|
||||||
{
|
{
|
||||||
var routeValuesAddressMetadata = endpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
|
||||||
var entry = new OutboundRouteEntry()
|
var entry = new OutboundRouteEntry()
|
||||||
{
|
{
|
||||||
Handler = NullRouter.Instance,
|
Handler = NullRouter.Instance,
|
||||||
Order = endpoint.Order,
|
Order = endpoint.Order,
|
||||||
Precedence = RoutePrecedence.ComputeOutbound(endpoint.RoutePattern),
|
Precedence = RoutePrecedence.ComputeOutbound(endpoint.RoutePattern),
|
||||||
RequiredLinkValues = new RouteValueDictionary(routeValuesAddressMetadata?.RequiredValues),
|
RequiredLinkValues = new RouteValueDictionary(requiredValues),
|
||||||
RouteTemplate = new RouteTemplate(endpoint.RoutePattern),
|
RouteTemplate = new RouteTemplate(endpoint.RoutePattern),
|
||||||
Data = endpoint,
|
Data = endpoint,
|
||||||
RouteName = routeValuesAddressMetadata?.RouteName,
|
RouteName = routeName,
|
||||||
};
|
};
|
||||||
entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
|
entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
|
||||||
return entry;
|
return entry;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
// /api/template/{id:int} == 1.12
|
// /api/template/{id:int} == 1.12
|
||||||
public static decimal ComputeInbound(RouteTemplate template)
|
public static decimal ComputeInbound(RouteTemplate template)
|
||||||
{
|
{
|
||||||
|
ValidateSegementLength(template.Segments.Count);
|
||||||
|
|
||||||
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
|
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
|
||||||
// and 4 results in a combined precedence of 2.14 (decimal).
|
// and 4 results in a combined precedence of 2.14 (decimal).
|
||||||
var precedence = 0m;
|
var precedence = 0m;
|
||||||
|
|
@ -40,6 +42,8 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
// See description on ComputeInbound(RouteTemplate)
|
// See description on ComputeInbound(RouteTemplate)
|
||||||
internal static decimal ComputeInbound(RoutePattern routePattern)
|
internal static decimal ComputeInbound(RoutePattern routePattern)
|
||||||
{
|
{
|
||||||
|
ValidateSegementLength(routePattern.PathSegments.Count);
|
||||||
|
|
||||||
var precedence = 0m;
|
var precedence = 0m;
|
||||||
|
|
||||||
for (var i = 0; i < routePattern.PathSegments.Count; i++)
|
for (var i = 0; i < routePattern.PathSegments.Count; i++)
|
||||||
|
|
@ -62,6 +66,8 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
// /api/template/{id:int} == 5.54
|
// /api/template/{id:int} == 5.54
|
||||||
public static decimal ComputeOutbound(RouteTemplate template)
|
public static decimal ComputeOutbound(RouteTemplate template)
|
||||||
{
|
{
|
||||||
|
ValidateSegementLength(template.Segments.Count);
|
||||||
|
|
||||||
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
|
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
|
||||||
// and 4 results in a combined precedence of 2.14 (decimal).
|
// and 4 results in a combined precedence of 2.14 (decimal).
|
||||||
var precedence = 0m;
|
var precedence = 0m;
|
||||||
|
|
@ -82,6 +88,8 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
// see description on ComputeOutbound(RouteTemplate)
|
// see description on ComputeOutbound(RouteTemplate)
|
||||||
internal static decimal ComputeOutbound(RoutePattern routePattern)
|
internal static decimal ComputeOutbound(RoutePattern routePattern)
|
||||||
{
|
{
|
||||||
|
ValidateSegementLength(routePattern.PathSegments.Count);
|
||||||
|
|
||||||
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
|
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
|
||||||
// and 4 results in a combined precedence of 2.14 (decimal).
|
// and 4 results in a combined precedence of 2.14 (decimal).
|
||||||
var precedence = 0m;
|
var precedence = 0m;
|
||||||
|
|
@ -99,6 +107,15 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
return precedence;
|
return precedence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ValidateSegementLength(int length)
|
||||||
|
{
|
||||||
|
if (length > 28)
|
||||||
|
{
|
||||||
|
// An OverflowException will be thrown by Math.Pow when greater than 28
|
||||||
|
throw new InvalidOperationException("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Segments have the following order:
|
// Segments have the following order:
|
||||||
// 5 - Literal segments
|
// 5 - Literal segments
|
||||||
// 4 - Multi-part segments && Constrained parameter segments
|
// 4 - Multi-part segments && Constrained parameter segments
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,13 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
|
|
||||||
public RouteTemplate(RoutePattern other)
|
public RouteTemplate(RoutePattern other)
|
||||||
{
|
{
|
||||||
|
if (other == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(other));
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequiredValues will be ignored. RouteTemplate doesn't support them.
|
||||||
|
|
||||||
TemplateText = other.RawText;
|
TemplateText = other.RawText;
|
||||||
Segments = new List<TemplateSegment>(other.PathSegments.Select(p => new TemplateSegment(p)));
|
Segments = new List<TemplateSegment>(other.PathSegments.Select(p => new TemplateSegment(p)));
|
||||||
Parameters = new List<TemplatePart>();
|
Parameters = new List<TemplatePart>();
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
<Project>
|
<Project>
|
||||||
<Import Project="..\Directory.Build.props" />
|
<Import Project="..\Directory.Build.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<DeveloperBuildTestTfms>netcoreapp2.2</DeveloperBuildTestTfms>
|
|
||||||
<StandardTestTfms>$(DeveloperBuildTestTfms)</StandardTestTfms>
|
|
||||||
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' ">$(StandardTestTfms)</StandardTestTfms>
|
|
||||||
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(StandardTestTfms);net461</StandardTestTfms>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -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 { }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -207,13 +207,7 @@ namespace Microsoft.AspNetCore.Routing.DecisionTree
|
||||||
|
|
||||||
private class ItemClassifier : IClassifier<Item>
|
private class ItemClassifier : IClassifier<Item>
|
||||||
{
|
{
|
||||||
public IEqualityComparer<object> ValueComparer
|
public IEqualityComparer<object> ValueComparer => RouteValueEqualityComparer.Default;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RouteValueEqualityComparer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDictionary<string, DecisionCriterionValue> GetCriteria(Item item)
|
public IDictionary<string, DecisionCriterionValue> GetCriteria(Item item)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if NETCOREAPP2_2
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
@ -57,4 +56,3 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if NETCOREAPP2_2
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
@ -56,5 +55,4 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
|
||||||
_client.Dispose();
|
_client.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
@ -24,16 +24,48 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
|
||||||
_client.BaseAddress = new Uri("http://localhost");
|
_client.BaseAddress = new Uri("http://localhost");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Branch1")]
|
||||||
|
[InlineData("Branch2")]
|
||||||
|
public async Task Routing_CanRouteRequest_ToBranchRouter(string branch)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var message = new HttpRequestMessage(HttpMethod.Get, $"{branch}/api/get/5");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.SendAsync(message);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal($"{branch} - API Get 5", await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task MatchesRootPath_AndReturnsPlaintext()
|
public async Task MatchesRootPath_AndReturnsPlaintext()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedContentType = "text/plain";
|
var expectedContentType = "text/plain";
|
||||||
var expectedContent = "Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext";
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await _client.GetAsync("/");
|
var response = await _client.GetAsync("/");
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MatchesStaticRouteTemplate_AndReturnsPlaintext()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContentType = "text/plain";
|
||||||
|
var expectedContent = "Plain text!";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.GetAsync("/plaintext");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.NotNull(response.Content);
|
Assert.NotNull(response.Content);
|
||||||
|
|
@ -44,14 +76,14 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task MatchesStaticRouteTemplate_AndReturnsPlaintext()
|
public async Task MatchesHelloMiddleware_AndReturnsPlaintext()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedContentType = "text/plain";
|
var expectedContentType = "text/plain";
|
||||||
var expectedContent = "Hello, World!";
|
var expectedContent = "Hello World";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await _client.GetAsync("/plaintext");
|
var response = await _client.GetAsync("/helloworld");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\benchmarkapps\Benchmarks\Benchmarks.csproj" Condition="'$(TargetFramework)'=='netcoreapp2.2'" />
|
<ProjectReference Include="..\..\benchmarkapps\Benchmarks\Benchmarks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,22 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
|
||||||
_client.BaseAddress = new Uri("http://localhost");
|
_client.BaseAddress = new Uri("http://localhost");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Branch1")]
|
||||||
|
[InlineData("Branch2")]
|
||||||
|
public async Task Routing_CanRouteRequest_ToBranchRouter(string branch)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var message = new HttpRequestMessage(HttpMethod.Get, $"{branch}/api/get/5");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.SendAsync(message);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal($"{branch} - API Get 5", await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Routing_CanRouteRequestDelegate_ToSpecificHttpVerb()
|
public async Task Routing_CanRouteRequestDelegate_ToSpecificHttpVerb()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,23 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder.Internal;
|
using Microsoft.AspNetCore.Builder.Internal;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Internal;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Builder
|
namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
public class EndpointRoutingBuilderExtensionsTest
|
public class EndpointRoutingApplicationBuilderExtensionsTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void UseEndpointRouting_ServicesNotRegistered_Throws()
|
public void UseEndpointRouting_ServicesNotRegistered_Throws()
|
||||||
|
|
@ -24,7 +27,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
|
var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpointRouting());
|
var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpointRouting(builder => { }));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
|
|
@ -59,7 +62,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
|
|
||||||
var app = new ApplicationBuilder(services);
|
var app = new ApplicationBuilder(services);
|
||||||
|
|
||||||
app.UseEndpointRouting();
|
app.UseEndpointRouting(builder => { });
|
||||||
|
|
||||||
var appFunc = app.Build();
|
var appFunc = app.Build();
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
|
|
@ -76,17 +79,20 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = new RouteEndpoint(
|
var endpoint = new RouteEndpoint(
|
||||||
TestConstants.EmptyRequestDelegate,
|
TestConstants.EmptyRequestDelegate,
|
||||||
RoutePatternFactory.Parse("{*p}"),
|
RoutePatternFactory.Parse("{*p}"),
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
"Test");
|
"Test");
|
||||||
|
|
||||||
var services = CreateServices(endpoint);
|
var services = CreateServices();
|
||||||
|
|
||||||
var app = new ApplicationBuilder(services);
|
var app = new ApplicationBuilder(services);
|
||||||
|
|
||||||
app.UseEndpointRouting();
|
app.UseEndpointRouting(builder =>
|
||||||
|
{
|
||||||
|
builder.DataSources.Add(new DefaultEndpointDataSource(endpoint));
|
||||||
|
});
|
||||||
|
|
||||||
var appFunc = app.Build();
|
var appFunc = app.Build();
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
|
|
@ -127,7 +133,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
|
|
||||||
var app = new ApplicationBuilder(services);
|
var app = new ApplicationBuilder(services);
|
||||||
|
|
||||||
app.UseEndpointRouting();
|
app.UseEndpointRouting(builder => { });
|
||||||
app.UseEndpoint();
|
app.UseEndpoint();
|
||||||
|
|
||||||
var appFunc = app.Build();
|
var appFunc = app.Build();
|
||||||
|
|
@ -140,17 +146,84 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
Assert.Null(httpContext.Features.Get<IEndpointFeature>());
|
Assert.Null(httpContext.Features.Get<IEndpointFeature>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceProvider CreateServices(params Endpoint[] endpoints)
|
[Fact]
|
||||||
|
public void UseEndpointRouting_CallWithBuilder_SetsEndpointDataSource()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var matcherEndpointDataSources = new List<EndpointDataSource>();
|
||||||
|
var matcherFactoryMock = new Mock<MatcherFactory>();
|
||||||
|
matcherFactoryMock
|
||||||
|
.Setup(m => m.CreateMatcher(It.IsAny<EndpointDataSource>()))
|
||||||
|
.Callback((EndpointDataSource arg) =>
|
||||||
|
{
|
||||||
|
matcherEndpointDataSources.Add(arg);
|
||||||
|
})
|
||||||
|
.Returns(new TestMatcher(false));
|
||||||
|
|
||||||
|
var services = CreateServices(matcherFactoryMock.Object);
|
||||||
|
|
||||||
|
var app = new ApplicationBuilder(services);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
app.UseEndpointRouting(builder =>
|
||||||
|
{
|
||||||
|
builder.Map("/1", "Test endpoint 1", d => null);
|
||||||
|
builder.Map("/2", "Test endpoint 2", d => null);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseEndpointRouting(builder =>
|
||||||
|
{
|
||||||
|
builder.Map("/3", "Test endpoint 3", d => null);
|
||||||
|
builder.Map("/4", "Test endpoint 4", d => null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// This triggers the middleware to be created and the matcher factory to be called
|
||||||
|
// with the datasource we want to test
|
||||||
|
var requestDelegate = app.Build();
|
||||||
|
requestDelegate(new DefaultHttpContext());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, matcherEndpointDataSources.Count);
|
||||||
|
|
||||||
|
// Each middleware has its own endpoints
|
||||||
|
Assert.Collection(matcherEndpointDataSources[0].Endpoints,
|
||||||
|
e => Assert.Equal("Test endpoint 1", e.DisplayName),
|
||||||
|
e => Assert.Equal("Test endpoint 2", e.DisplayName));
|
||||||
|
Assert.Collection(matcherEndpointDataSources[1].Endpoints,
|
||||||
|
e => Assert.Equal("Test endpoint 3", e.DisplayName),
|
||||||
|
e => Assert.Equal("Test endpoint 4", e.DisplayName));
|
||||||
|
|
||||||
|
var compositeEndpointBuilder = services.GetRequiredService<EndpointDataSource>();
|
||||||
|
|
||||||
|
// Global middleware has all endpoints
|
||||||
|
Assert.Collection(compositeEndpointBuilder.Endpoints,
|
||||||
|
e => Assert.Equal("Test endpoint 1", e.DisplayName),
|
||||||
|
e => Assert.Equal("Test endpoint 2", e.DisplayName),
|
||||||
|
e => Assert.Equal("Test endpoint 3", e.DisplayName),
|
||||||
|
e => Assert.Equal("Test endpoint 4", e.DisplayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IServiceProvider CreateServices()
|
||||||
|
{
|
||||||
|
return CreateServices(matcherFactory: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IServiceProvider CreateServices(MatcherFactory matcherFactory)
|
||||||
{
|
{
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
if (matcherFactory != null)
|
||||||
|
{
|
||||||
|
services.AddSingleton<MatcherFactory>(matcherFactory);
|
||||||
|
}
|
||||||
|
|
||||||
services.AddLogging();
|
services.AddLogging();
|
||||||
services.AddOptions();
|
services.AddOptions();
|
||||||
services.AddRouting();
|
services.AddRouting();
|
||||||
|
|
||||||
services.AddSingleton<EndpointDataSource>(new DefaultEndpointDataSource(endpoints));
|
var serviceProvder = services.BuildServiceProvider();
|
||||||
|
|
||||||
return services.BuildServiceProvider();
|
return serviceProvder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
private ModelEndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder)
|
||||||
|
{
|
||||||
|
return Assert.IsType<ModelEndpointDataSource>(Assert.Single(endpointRouteBuilder.DataSources));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RouteEndpointModel GetRouteEndpointBuilder(IEndpointRouteBuilder endpointRouteBuilder)
|
||||||
|
{
|
||||||
|
return Assert.IsType<RouteEndpointModel>(Assert.Single(GetBuilderEndpointDataSource(endpointRouteBuilder).EndpointModels));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MapEndpoint_StringPattern_BuildsEndpoint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new DefaultEndpointRouteBuilder();
|
||||||
|
RequestDelegate requestDelegate = (d) => null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpointBuilder = builder.Map("/", "Display name!", requestDelegate);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
|
||||||
|
|
||||||
|
Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
|
||||||
|
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
|
||||||
|
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MapEndpoint_TypedPattern_BuildsEndpoint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new DefaultEndpointRouteBuilder();
|
||||||
|
RequestDelegate requestDelegate = (d) => null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", requestDelegate);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
|
||||||
|
|
||||||
|
Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
|
||||||
|
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
|
||||||
|
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MapEndpoint_StringPatternAndMetadata_BuildsEndpoint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var metadata = new object();
|
||||||
|
var builder = new DefaultEndpointRouteBuilder();
|
||||||
|
RequestDelegate requestDelegate = (d) => null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpointBuilder = builder.Map("/", "Display name!", requestDelegate, new[] { metadata });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
|
||||||
|
Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
|
||||||
|
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
|
||||||
|
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
|
||||||
|
Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MapEndpoint_TypedPatternAndMetadata_BuildsEndpoint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var metadata = new object();
|
||||||
|
var builder = new DefaultEndpointRouteBuilder();
|
||||||
|
RequestDelegate requestDelegate = (d) => null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", requestDelegate, new[] { metadata });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var endpointBuilder1 = GetRouteEndpointBuilder(builder);
|
||||||
|
Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
|
||||||
|
Assert.Equal("Display name!", endpointBuilder1.DisplayName);
|
||||||
|
Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
|
||||||
|
Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Constraints;
|
using Microsoft.AspNetCore.Routing.Constraints;
|
||||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -259,7 +261,12 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
|
||||||
|
};
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -283,7 +290,12 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}");
|
||||||
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
|
||||||
|
};
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
|
||||||
var httpContext = CreateHttpContext();
|
var httpContext = CreateHttpContext();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -334,7 +346,12 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
|
||||||
|
};
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -362,8 +379,17 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o =>
|
||||||
|
{
|
||||||
|
o.LowercaseUrls = true;
|
||||||
|
o.LowercaseQueryStrings = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(
|
var linkGenerator = CreateLinkGenerator(
|
||||||
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true },
|
configure,
|
||||||
endpoints: new[] { endpoint, });
|
endpoints: new[] { endpoint, });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||||
|
|
||||||
|
|
@ -387,8 +413,13 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o => o.AppendTrailingSlash = true);
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(
|
var linkGenerator = CreateLinkGenerator(
|
||||||
new RouteOptions() { AppendTrailingSlash = true },
|
configure,
|
||||||
endpoints: new[] { endpoint });
|
endpoints: new[] { endpoint });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||||
|
|
||||||
|
|
@ -412,8 +443,18 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o =>
|
||||||
|
{
|
||||||
|
o.LowercaseUrls = true;
|
||||||
|
o.LowercaseQueryStrings = true;
|
||||||
|
o.AppendTrailingSlash = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(
|
var linkGenerator = CreateLinkGenerator(
|
||||||
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, AppendTrailingSlash = true },
|
configure,
|
||||||
endpoints: new[] { endpoint });
|
endpoints: new[] { endpoint });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||||
|
|
||||||
|
|
@ -437,8 +478,13 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(
|
var linkGenerator = CreateLinkGenerator(
|
||||||
new RouteOptions() { LowercaseUrls = true },
|
configure,
|
||||||
endpoints: new[] { endpoint });
|
endpoints: new[] { endpoint });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
|
||||||
|
|
||||||
|
|
@ -466,8 +512,13 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o => o.LowercaseUrls = false);
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(
|
var linkGenerator = CreateLinkGenerator(
|
||||||
new RouteOptions() { LowercaseUrls = false },
|
configure,
|
||||||
endpoints: new[] { endpoint });
|
endpoints: new[] { endpoint });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
|
||||||
|
|
||||||
|
|
@ -494,8 +545,17 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o =>
|
||||||
|
{
|
||||||
|
o.LowercaseUrls = true;
|
||||||
|
o.LowercaseQueryStrings = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(
|
var linkGenerator = CreateLinkGenerator(
|
||||||
new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true },
|
configure,
|
||||||
endpoints: new[] { endpoint });
|
endpoints: new[] { endpoint });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||||
|
|
||||||
|
|
@ -523,8 +583,17 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o =>
|
||||||
|
{
|
||||||
|
o.LowercaseUrls = false;
|
||||||
|
o.LowercaseQueryStrings = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(
|
var linkGenerator = CreateLinkGenerator(
|
||||||
new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = false },
|
configure,
|
||||||
endpoints: new[] { endpoint });
|
endpoints: new[] { endpoint });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||||
|
|
||||||
|
|
@ -552,8 +621,13 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
|
||||||
|
Action<IServiceCollection> configure = (s) =>
|
||||||
|
{
|
||||||
|
s.Configure<RouteOptions>(o => o.AppendTrailingSlash = false);
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(
|
var linkGenerator = CreateLinkGenerator(
|
||||||
new RouteOptions() { AppendTrailingSlash = false },
|
configure,
|
||||||
endpoints: new[] { endpoint });
|
endpoints: new[] { endpoint });
|
||||||
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,10 +177,15 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
|
||||||
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
|
||||||
|
|
||||||
var routeOptions = new RouteOptions();
|
Action<IServiceCollection> configureServices = s =>
|
||||||
routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
|
{
|
||||||
|
s.Configure<RouteOptions>(o =>
|
||||||
|
{
|
||||||
|
o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(routeOptions: routeOptions, configureServices: null, endpoint1, endpoint2);
|
var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var path = linkGenerator.GetPathByAddress(
|
var path = linkGenerator.GetPathByAddress(
|
||||||
|
|
@ -198,10 +203,15 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
|
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
|
||||||
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
|
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
|
||||||
|
|
||||||
var routeOptions = new RouteOptions();
|
Action<IServiceCollection> configureServices = s =>
|
||||||
routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
|
{
|
||||||
|
s.Configure<RouteOptions>(o =>
|
||||||
|
{
|
||||||
|
o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(routeOptions: routeOptions, configureServices: null, endpoint1, endpoint2);
|
var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var path = linkGenerator.GetPathByAddress(
|
var path = linkGenerator.GetPathByAddress(
|
||||||
|
|
@ -311,17 +321,17 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public void GetLink_ParameterTransformer()
|
public void GetLink_ParameterTransformer()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}");
|
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", requiredValues: new { controller = "Home", name = "Test" });
|
||||||
|
|
||||||
var routeOptions = new RouteOptions();
|
|
||||||
routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
|
||||||
|
|
||||||
Action<IServiceCollection> configure = (s) =>
|
Action<IServiceCollection> configure = (s) =>
|
||||||
{
|
{
|
||||||
s.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform());
|
s.Configure<RouteOptions>(o =>
|
||||||
|
{
|
||||||
|
o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(routeOptions, configure, endpoint);
|
var linkGenerator = CreateLinkGenerator(configure, endpoint);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test" });
|
var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test" });
|
||||||
|
|
@ -334,17 +344,20 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public void GetLink_ParameterTransformer_ForQueryString()
|
public void GetLink_ParameterTransformer_ForQueryString()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", policies: new { c = new UpperCaseParameterTransform(), });
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
|
"{controller:upper-case}/{name}",
|
||||||
var routeOptions = new RouteOptions();
|
requiredValues: new { controller = "Home", name = "Test", c = "hithere", },
|
||||||
routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
policies: new { c = new UpperCaseParameterTransform(), });
|
||||||
|
|
||||||
Action<IServiceCollection> configure = (s) =>
|
Action<IServiceCollection> configure = (s) =>
|
||||||
{
|
{
|
||||||
s.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform());
|
s.Configure<RouteOptions>(o =>
|
||||||
|
{
|
||||||
|
o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var linkGenerator = CreateLinkGenerator(routeOptions, configure, endpoint);
|
var linkGenerator = CreateLinkGenerator(configure, endpoint);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test", c = "hithere", });
|
var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test", c = "hithere", });
|
||||||
|
|
@ -707,9 +720,9 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
private class IntAddressScheme : IEndpointAddressScheme<int>
|
private class IntAddressScheme : IEndpointAddressScheme<int>
|
||||||
{
|
{
|
||||||
private readonly CompositeEndpointDataSource _dataSource;
|
private readonly EndpointDataSource _dataSource;
|
||||||
|
|
||||||
public IntAddressScheme(CompositeEndpointDataSource dataSource)
|
public IntAddressScheme(EndpointDataSource dataSource)
|
||||||
{
|
{
|
||||||
_dataSource = dataSource;
|
_dataSource = dataSource;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -151,10 +151,9 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
logger = logger ?? new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
|
logger = logger ?? new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
|
||||||
matcherFactory = matcherFactory ?? new TestMatcherFactory(true);
|
matcherFactory = matcherFactory ?? new TestMatcherFactory(true);
|
||||||
|
|
||||||
var options = Options.Create(new EndpointOptions());
|
|
||||||
var middleware = new EndpointRoutingMiddleware(
|
var middleware = new EndpointRoutingMiddleware(
|
||||||
matcherFactory,
|
matcherFactory,
|
||||||
new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>()),
|
new DefaultEndpointDataSource(),
|
||||||
logger,
|
logger,
|
||||||
next);
|
next);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
using Microsoft.Extensions.ObjectPool;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
|
|
@ -44,29 +45,22 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
private protected DefaultLinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
|
private protected DefaultLinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
|
||||||
{
|
{
|
||||||
return CreateLinkGenerator(routeOptions: null, endpoints);
|
return CreateLinkGenerator(configureServices: null, endpoints);
|
||||||
}
|
|
||||||
|
|
||||||
private protected DefaultLinkGenerator CreateLinkGenerator(RouteOptions routeOptions, params Endpoint[] endpoints)
|
|
||||||
{
|
|
||||||
return CreateLinkGenerator(routeOptions, configureServices: null, endpoints);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected DefaultLinkGenerator CreateLinkGenerator(
|
private protected DefaultLinkGenerator CreateLinkGenerator(
|
||||||
RouteOptions routeOptions,
|
|
||||||
Action<IServiceCollection> configureServices,
|
Action<IServiceCollection> configureServices,
|
||||||
params Endpoint[] endpoints)
|
params Endpoint[] endpoints)
|
||||||
{
|
{
|
||||||
return CreateLinkGenerator(routeOptions, configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty<Endpoint>()) });
|
return CreateLinkGenerator(configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty<Endpoint>()) });
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected DefaultLinkGenerator CreateLinkGenerator(EndpointDataSource[] dataSources)
|
private protected DefaultLinkGenerator CreateLinkGenerator(EndpointDataSource[] dataSources)
|
||||||
{
|
{
|
||||||
return CreateLinkGenerator(routeOptions: null, configureServices: null, dataSources);
|
return CreateLinkGenerator(configureServices: null, dataSources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected DefaultLinkGenerator CreateLinkGenerator(
|
private protected DefaultLinkGenerator CreateLinkGenerator(
|
||||||
RouteOptions routeOptions,
|
|
||||||
Action<IServiceCollection> configureServices,
|
Action<IServiceCollection> configureServices,
|
||||||
EndpointDataSource[] dataSources)
|
EndpointDataSource[] dataSources)
|
||||||
{
|
{
|
||||||
|
|
@ -74,25 +68,25 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
AddAdditionalServices(services);
|
AddAdditionalServices(services);
|
||||||
configureServices?.Invoke(services);
|
configureServices?.Invoke(services);
|
||||||
|
|
||||||
routeOptions = routeOptions ?? new RouteOptions();
|
services.Configure<RouteOptions>(o =>
|
||||||
dataSources = dataSources ?? Array.Empty<EndpointDataSource>();
|
|
||||||
|
|
||||||
services.Configure<EndpointOptions>((o) =>
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < dataSources.Length; i++)
|
if (dataSources != null)
|
||||||
{
|
{
|
||||||
o.DataSources.Add(dataSources[i]);
|
foreach (var dataSource in dataSources)
|
||||||
|
{
|
||||||
|
o.EndpointDataSources.Add(dataSource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var options = Options.Create(routeOptions);
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
|
var routeOptions = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
|
||||||
|
|
||||||
return new DefaultLinkGenerator(
|
return new DefaultLinkGenerator(
|
||||||
new DefaultParameterPolicyFactory(options, serviceProvider),
|
new DefaultParameterPolicyFactory(routeOptions, serviceProvider),
|
||||||
serviceProvider.GetRequiredService<CompositeEndpointDataSource>(),
|
new CompositeEndpointDataSource(routeOptions.Value.EndpointDataSources),
|
||||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||||
options,
|
routeOptions,
|
||||||
NullLogger<DefaultLinkGenerator>.Instance,
|
NullLogger<DefaultLinkGenerator>.Instance,
|
||||||
serviceProvider);
|
serviceProvider);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if IL_EMIT
|
|
||||||
using System;
|
using System;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -50,4 +49,3 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if IL_EMIT
|
|
||||||
using Moq;
|
using Moq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -225,4 +224,3 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
httpContext.Request.Path = path;
|
httpContext.Request.Path = path;
|
||||||
httpContext.RequestServices = CreateServices();
|
httpContext.RequestServices = CreateServices();
|
||||||
|
|
||||||
var context = new EndpointSelectorContext();
|
var context = new EndpointSelectorContext()
|
||||||
|
{
|
||||||
|
RouteValues = new RouteValueDictionary()
|
||||||
|
};
|
||||||
httpContext.Features.Set<IEndpointFeature>(context);
|
httpContext.Features.Set<IEndpointFeature>(context);
|
||||||
httpContext.Features.Set<IRouteValuesFeature>(context);
|
httpContext.Features.Set<IRouteValuesFeature>(context);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if IL_EMIT
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
public class NonVectorizedILEmitTrieJumpTableTest : ILEmitTreeJumpTableTestBase
|
public class NonVectorizedILEmitTrieJumpTableTest : ILEmitTreeJumpTableTestBase
|
||||||
|
|
@ -9,4 +8,3 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
public override bool Vectorize => false;
|
public override bool Vectorize => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if IL_EMIT
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
public class VectorizedILEmitTrieJumpTableTest : ILEmitTreeJumpTableTestBase
|
public class VectorizedILEmitTrieJumpTableTest : ILEmitTreeJumpTableTestBase
|
||||||
|
|
@ -11,4 +10,3 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
public override bool Vectorize => true;
|
public override bool Vectorize => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,11 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<RootNamespace>Microsoft.AspNetCore.Routing</RootNamespace>
|
<RootNamespace>Microsoft.AspNetCore.Routing</RootNamespace>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<!--
|
|
||||||
RefEmit is supported in netcoreapp. We test on .NET Framework, but we don't support RefEmit in the product
|
|
||||||
on .NET Framework.
|
|
||||||
-->
|
|
||||||
<ILEmit Condition="'$(TargetFramework)'=='netcoreapp2.2'">true</ILEmit>
|
|
||||||
<DefineConstants Condition="'$(ILEmit)'=='true'">IL_EMIT;$(DefineConstants)</DefineConstants>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,339 @@
|
||||||
|
// 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.Constraints;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
|
{
|
||||||
|
public class DefaultRoutePatternTransformerTest
|
||||||
|
{
|
||||||
|
public DefaultRoutePatternTransformerTest()
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddRouting();
|
||||||
|
services.AddOptions();
|
||||||
|
Transformer = services.BuildServiceProvider().GetRequiredService<RoutePatternTransformer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoutePatternTransformer Transformer { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanAcceptNullForAnyKey()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller=Home}/{action=Index}/{id?}";
|
||||||
|
var defaults = new { };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { a = (string)null, b = "", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("a", null), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("b", string.Empty), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_RejectsNullForParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller=Home}/{action=Index}/{id?}";
|
||||||
|
var defaults = new { };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = string.Empty, };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_RejectsNullForOutOfLineDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller=Home}/{action=Index}/{id?}";
|
||||||
|
var defaults = new { area = "Admin" };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { area = string.Empty, };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanAcceptValueForParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller}/{action}/{id?}";
|
||||||
|
var defaults = new { };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = "Home", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanAcceptValueForParameter_WithSameDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller=Home}/{action=Index}/{id?}";
|
||||||
|
var defaults = new { };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = "Home", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
|
||||||
|
|
||||||
|
// We should not need to rewrite anything in this case.
|
||||||
|
Assert.Same(actual.Defaults, original.Defaults);
|
||||||
|
Assert.Same(actual.Parameters, original.Parameters);
|
||||||
|
Assert.Same(actual.PathSegments, original.PathSegments);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanAcceptValueForParameter_WithDifferentDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller=Blog}/{action=ReadPost}/{id?}";
|
||||||
|
var defaults = new { area = "Admin", };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { area = "Admin", controller = "Home", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
|
||||||
|
|
||||||
|
// We should not need to rewrite anything in this case.
|
||||||
|
Assert.NotSame(actual.Defaults, original.Defaults);
|
||||||
|
Assert.NotSame(actual.Parameters, original.Parameters);
|
||||||
|
Assert.NotSame(actual.PathSegments, original.PathSegments);
|
||||||
|
|
||||||
|
// other defaults were wiped out
|
||||||
|
Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), Assert.Single(actual.Defaults));
|
||||||
|
Assert.Null(actual.GetParameter("controller").Default);
|
||||||
|
Assert.False(actual.Defaults.ContainsKey("controller"));
|
||||||
|
Assert.Null(actual.GetParameter("action").Default);
|
||||||
|
Assert.False(actual.Defaults.ContainsKey("action"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanAcceptValueForParameter_WithMatchingConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller}/{action}/{id?}";
|
||||||
|
var defaults = new { };
|
||||||
|
var policies = new { controller = "Home", action = new RegexRouteConstraint("Index"), };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = "Home", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanRejectValueForParameter_WithNonMatchingConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller}/{action}/{id?}";
|
||||||
|
var defaults = new { };
|
||||||
|
var policies = new { controller = "Home", action = new RegexRouteConstraint("Index"), };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = "Blog", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "Home/Index/{id?}";
|
||||||
|
var defaults = new { controller = "Home", action = "Index", };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = "Home", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanRejectValueForDefault_WithDifferentValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "Home/Index/{id?}";
|
||||||
|
var defaults = new { controller = "Home", action = "Index", };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = "Blog", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue_Null()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "Home/Index/{id?}";
|
||||||
|
var defaults = new { controller = (string)null, action = "", };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = string.Empty, action = (string)null, };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("action", null), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("controller", ""), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue_WithMatchingConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "Home/Index/{id?}";
|
||||||
|
var defaults = new { controller = "Home", action = "Index", };
|
||||||
|
var policies = new { controller = "Home", };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = "Home", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanRejectValueForDefault_WithSameValue_WithNonMatchingConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "Home/Index/{id?}";
|
||||||
|
var defaults = new { controller = "Home", action = "Index", };
|
||||||
|
var policies = new { controller = "Home", };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies);
|
||||||
|
|
||||||
|
var requiredValues = new { controller = "Home", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubstituteRequiredValues_CanMergeExistingRequiredValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "Home/Index/{id?}";
|
||||||
|
var defaults = new { area = "Admin", controller = "Home", action = "Index", };
|
||||||
|
var policies = new { };
|
||||||
|
|
||||||
|
var original = RoutePatternFactory.Parse(template, defaults, policies, new { area = "Admin", controller = "Home", });
|
||||||
|
|
||||||
|
var requiredValues = new { controller = "Home", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
actual.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), kvp),
|
||||||
|
kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -441,6 +441,89 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
Assert.Null(paramPartD.Default);
|
Assert.Null(paramPartD.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_WithRequiredValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller=Home}/{action=Index}/{id?}";
|
||||||
|
var defaults = new { area = "Admin", };
|
||||||
|
var policies = new { };
|
||||||
|
var requiredValues = new { area = "Admin", controller = "Store", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
action.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => { Assert.Equal("action", kvp.Key); Assert.Equal("Index", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("area", kvp.Key); Assert.Equal("Admin", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("controller", kvp.Key); Assert.Equal("Store", kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_WithRequiredValues_AllowsNullRequiredValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller=Home}/{action=Index}/{id?}";
|
||||||
|
var defaults = new { };
|
||||||
|
var policies = new { };
|
||||||
|
var requiredValues = new { area = (string)null, controller = "Store", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
action.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => { Assert.Equal("action", kvp.Key); Assert.Equal("Index", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("area", kvp.Key); Assert.Null(kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("controller", kvp.Key); Assert.Equal("Store", kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_WithRequiredValues_AllowsEmptyRequiredValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller=Home}/{action=Index}/{id?}";
|
||||||
|
var defaults = new { };
|
||||||
|
var policies = new { };
|
||||||
|
var requiredValues = new { area = "", controller = "Store", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
action.RequiredValues.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp => { Assert.Equal("action", kvp.Key); Assert.Equal("Index", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("area", kvp.Key); Assert.Equal("", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("controller", kvp.Key); Assert.Equal("Store", kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_WithRequiredValues_ThrowsForNonParameterNonDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{controller=Home}/{action=Index}/{id?}";
|
||||||
|
var defaults = new { };
|
||||||
|
var policies = new { };
|
||||||
|
var requiredValues = new { area = "Admin", controller = "Store", action = "Index", };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(
|
||||||
|
"No corresponding parameter or default value could be found for the required value " +
|
||||||
|
"'area=Admin'. A non-null required value must correspond to a route parameter or the " +
|
||||||
|
"route pattern must have a matching default value.",
|
||||||
|
exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ParameterPart_ParameterNameAndDefaultAndParameterKindAndArrayOfParameterPolicies_ShouldMakeCopyOfParameterPolicies()
|
public void ParameterPart_ParameterNameAndDefaultAndParameterKindAndArrayOfParameterPolicies_ShouldMakeCopyOfParameterPolicies()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
public class RouteEndpointModelTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Build_AllValuesSet_EndpointCreated()
|
||||||
|
{
|
||||||
|
const int defaultOrder = 0;
|
||||||
|
var metadata = new object();
|
||||||
|
RequestDelegate requestDelegate = (d) => null;
|
||||||
|
|
||||||
|
var builder = new RouteEndpointModel(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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public void GetOutboundMatches_GetsNamedMatchesFor_EndpointsHaving_IRouteNameMetadata()
|
public void GetOutboundMatches_GetsNamedMatchesFor_EndpointsHaving_IRouteNameMetadata()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint1 = CreateEndpoint("/a");
|
var endpoint1 = CreateEndpoint("/a", routeName: "other");
|
||||||
var endpoint2 = CreateEndpoint("/a", routeName: "named");
|
var endpoint2 = CreateEndpoint("/a", routeName: "named");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName()
|
public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint1 = CreateEndpoint("/a");
|
var endpoint1 = CreateEndpoint("/a", routeName: "other");
|
||||||
var endpoint2 = CreateEndpoint("/a", routeName: "named");
|
var endpoint2 = CreateEndpoint("/a", routeName: "named");
|
||||||
var endpoint3 = CreateEndpoint("/b", routeName: "named");
|
var endpoint3 = CreateEndpoint("/b", routeName: "named");
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName_IgnoringCase()
|
public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName_IgnoringCase()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint1 = CreateEndpoint("/a");
|
var endpoint1 = CreateEndpoint("/a", routeName: "other");
|
||||||
var endpoint2 = CreateEndpoint("/a", routeName: "named");
|
var endpoint2 = CreateEndpoint("/a", routeName: "named");
|
||||||
var endpoint3 = CreateEndpoint("/b", routeName: "NaMed");
|
var endpoint3 = CreateEndpoint("/b", routeName: "NaMed");
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public void EndpointDataSource_ChangeCallback_Refreshes_OutboundMatches()
|
public void EndpointDataSource_ChangeCallback_Refreshes_OutboundMatches()
|
||||||
{
|
{
|
||||||
// Arrange 1
|
// Arrange 1
|
||||||
var endpoint1 = CreateEndpoint("/a");
|
var endpoint1 = CreateEndpoint("/a", metadataRequiredValues: new { });
|
||||||
var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
|
var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
|
|
@ -93,21 +93,21 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
Assert.Same(endpoint1, actual);
|
Assert.Same(endpoint1, actual);
|
||||||
|
|
||||||
// Arrange 2
|
// Arrange 2
|
||||||
var endpoint2 = CreateEndpoint("/b");
|
var endpoint2 = CreateEndpoint("/b", metadataRequiredValues: new { });
|
||||||
|
|
||||||
// Act 2
|
// Act 2
|
||||||
// Trigger change
|
// Trigger change
|
||||||
dynamicDataSource.AddEndpoint(endpoint2);
|
dynamicDataSource.AddEndpoint(endpoint2);
|
||||||
|
|
||||||
// Arrange 2
|
// Arrange 2
|
||||||
var endpoint3 = CreateEndpoint("/c");
|
var endpoint3 = CreateEndpoint("/c", metadataRequiredValues: new { });
|
||||||
|
|
||||||
// Act 2
|
// Act 2
|
||||||
// Trigger change
|
// Trigger change
|
||||||
dynamicDataSource.AddEndpoint(endpoint3);
|
dynamicDataSource.AddEndpoint(endpoint3);
|
||||||
|
|
||||||
// Arrange 3
|
// Arrange 3
|
||||||
var endpoint4 = CreateEndpoint("/d");
|
var endpoint4 = CreateEndpoint("/d", metadataRequiredValues: new { });
|
||||||
|
|
||||||
// Act 3
|
// Act 3
|
||||||
// Trigger change
|
// Trigger change
|
||||||
|
|
@ -146,13 +146,11 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint1 = CreateEndpoint(
|
var endpoint1 = CreateEndpoint(
|
||||||
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
||||||
defaults: new { zipCode = 3510 },
|
defaults: new { zipCode = 3510 },
|
||||||
requiredValues: new { id = 7 },
|
metadataRequiredValues: new { id = 7 });
|
||||||
routeName: "OrdersApi");
|
|
||||||
var endpoint2 = CreateEndpoint(
|
var endpoint2 = CreateEndpoint(
|
||||||
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
||||||
defaults: new { id = 12 },
|
defaults: new { id = 12 },
|
||||||
requiredValues: new { zipCode = 3510 },
|
metadataRequiredValues: new { zipCode = 3510 });
|
||||||
routeName: "OrdersApi");
|
|
||||||
var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
|
var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -174,46 +172,12 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var endpoint1 = CreateEndpoint(
|
var endpoint1 = CreateEndpoint(
|
||||||
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
||||||
defaults: new { zipCode = 3510 },
|
defaults: new { zipCode = 3510 },
|
||||||
requiredValues: new { id = 7 },
|
metadataRequiredValues: new { id = 7 });
|
||||||
routeName: "OrdersApi");
|
|
||||||
var endpoint2 = CreateEndpoint(
|
var endpoint2 = CreateEndpoint(
|
||||||
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
||||||
defaults: new { id = 12 },
|
defaults: new { id = 12 });
|
||||||
routeName: "OrdersApi");
|
|
||||||
var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
|
var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
|
||||||
|
|
||||||
// Act
|
|
||||||
var foundEndpoints = addressScheme.FindEndpoints(
|
|
||||||
new RouteValuesAddress
|
|
||||||
{
|
|
||||||
ExplicitValues = new RouteValueDictionary(new { id = 13 }),
|
|
||||||
AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var actual = Assert.Single(foundEndpoints);
|
|
||||||
Assert.Same(endpoint2, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FindEndpoints_LookedUpByCriteria_MultipleMatches()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var endpoint1 = CreateEndpoint(
|
|
||||||
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
|
||||||
defaults: new { zipCode = 3510 },
|
|
||||||
requiredValues: new { id = 7 },
|
|
||||||
routeName: "OrdersApi");
|
|
||||||
var endpoint2 = CreateEndpoint(
|
|
||||||
"api/orders/{id}/{name?}/{urgent}/{zipCode}",
|
|
||||||
defaults: new { id = 12 },
|
|
||||||
routeName: "OrdersApi");
|
|
||||||
var endpoint3 = CreateEndpoint(
|
|
||||||
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
|
||||||
defaults: new { id = 12 },
|
|
||||||
routeName: "OrdersApi");
|
|
||||||
var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var foundEndpoints = addressScheme.FindEndpoints(
|
var foundEndpoints = addressScheme.FindEndpoints(
|
||||||
new RouteValuesAddress
|
new RouteValuesAddress
|
||||||
|
|
@ -223,7 +187,64 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Contains(endpoint1, foundEndpoints);
|
var actual = Assert.Single(foundEndpoints);
|
||||||
|
Assert.Same(endpoint1, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindEndpoints_LookedUpByCriteria_MultipleMatches()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
||||||
|
defaults: new { zipCode = 3510 },
|
||||||
|
metadataRequiredValues: new { id = 7 });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"api/orders/{id}/{name?}/{urgent}/{zipCode}",
|
||||||
|
defaults: new { id = 12 },
|
||||||
|
metadataRequiredValues: new { id = 12 });
|
||||||
|
var endpoint3 = CreateEndpoint(
|
||||||
|
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
||||||
|
defaults: new { id = 12 },
|
||||||
|
metadataRequiredValues: new { id = 12 });
|
||||||
|
var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var foundEndpoints = addressScheme.FindEndpoints(
|
||||||
|
new RouteValuesAddress
|
||||||
|
{
|
||||||
|
ExplicitValues = new RouteValueDictionary(new { id = 12 }),
|
||||||
|
AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(foundEndpoints,
|
||||||
|
e => Assert.Equal(endpoint3, e),
|
||||||
|
e => Assert.Equal(endpoint2, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindEndpoints_LookedUpByCriteria_ExcludeEndpointWithoutRouteValuesAddressMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
|
||||||
|
defaults: new { zipCode = 3510 },
|
||||||
|
metadataRequiredValues: new { id = 7 });
|
||||||
|
var endpoint2 = CreateEndpoint("test");
|
||||||
|
|
||||||
|
var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var foundEndpoints = addressScheme.FindEndpoints(
|
||||||
|
new RouteValuesAddress
|
||||||
|
{
|
||||||
|
ExplicitValues = new RouteValueDictionary(new { id = 7 }),
|
||||||
|
AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }),
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.DoesNotContain(endpoint2, foundEndpoints);
|
||||||
Assert.Contains(endpoint1, foundEndpoints);
|
Assert.Contains(endpoint1, foundEndpoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,7 +255,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var expected = CreateEndpoint(
|
var expected = CreateEndpoint(
|
||||||
"api/orders/{id}",
|
"api/orders/{id}",
|
||||||
defaults: new { controller = "Orders", action = "GetById" },
|
defaults: new { controller = "Orders", action = "GetById" },
|
||||||
requiredValues: new { controller = "Orders", action = "GetById" },
|
metadataRequiredValues: new { controller = "Orders", action = "GetById" },
|
||||||
routeName: "OrdersApi");
|
routeName: "OrdersApi");
|
||||||
var addressScheme = CreateAddressScheme(expected);
|
var addressScheme = CreateAddressScheme(expected);
|
||||||
|
|
||||||
|
|
@ -252,6 +273,29 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
Assert.Same(expected, actual);
|
Assert.Same(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindEndpoints_ReturnsEndpoint_UsingRoutePatternRequiredValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = CreateEndpoint(
|
||||||
|
"api/orders/{id}",
|
||||||
|
defaults: new { controller = "Orders", action = "GetById" },
|
||||||
|
routePatternRequiredValues: new { controller = "Orders", action = "GetById" });
|
||||||
|
var addressScheme = CreateAddressScheme(expected);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var foundEndpoints = addressScheme.FindEndpoints(
|
||||||
|
new RouteValuesAddress
|
||||||
|
{
|
||||||
|
ExplicitValues = new RouteValueDictionary(new { id = 10 }),
|
||||||
|
AmbientValues = new RouteValueDictionary(new { controller = "Orders", action = "GetById" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actual = Assert.Single(foundEndpoints);
|
||||||
|
Assert.Same(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FindEndpoints_AlwaysReturnsEndpointsByRouteName_IgnoringMissingRequiredParameterValues()
|
public void FindEndpoints_AlwaysReturnsEndpointsByRouteName_IgnoringMissingRequiredParameterValues()
|
||||||
{
|
{
|
||||||
|
|
@ -263,7 +307,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
var expected = CreateEndpoint(
|
var expected = CreateEndpoint(
|
||||||
"api/orders/{id}",
|
"api/orders/{id}",
|
||||||
defaults: new { controller = "Orders", action = "GetById" },
|
defaults: new { controller = "Orders", action = "GetById" },
|
||||||
requiredValues: new { controller = "Orders", action = "GetById" },
|
metadataRequiredValues: new { controller = "Orders", action = "GetById" },
|
||||||
routeName: "OrdersApi");
|
routeName: "OrdersApi");
|
||||||
var addressScheme = CreateAddressScheme(expected);
|
var addressScheme = CreateAddressScheme(expected);
|
||||||
|
|
||||||
|
|
@ -302,7 +346,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
var endpoint = EndpointFactory.CreateRouteEndpoint(
|
||||||
"/a",
|
"/a",
|
||||||
metadata: new object[] { new SuppressLinkGenerationMetadata(), new EncourageLinkGenerationMetadata(), });
|
metadata: new object[] { new SuppressLinkGenerationMetadata(), new EncourageLinkGenerationMetadata(), new RouteValuesAddressMetadata(string.Empty), });
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var addressScheme = CreateAddressScheme(endpoint);
|
var addressScheme = CreateAddressScheme(endpoint);
|
||||||
|
|
@ -324,7 +368,8 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
private RouteEndpoint CreateEndpoint(
|
private RouteEndpoint CreateEndpoint(
|
||||||
string template,
|
string template,
|
||||||
object defaults = null,
|
object defaults = null,
|
||||||
object requiredValues = null,
|
object metadataRequiredValues = null,
|
||||||
|
object routePatternRequiredValues = null,
|
||||||
int order = 0,
|
int order = 0,
|
||||||
string routeName = null,
|
string routeName = null,
|
||||||
EndpointMetadataCollection metadataCollection = null)
|
EndpointMetadataCollection metadataCollection = null)
|
||||||
|
|
@ -332,16 +377,16 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
if (metadataCollection == null)
|
if (metadataCollection == null)
|
||||||
{
|
{
|
||||||
var metadata = new List<object>();
|
var metadata = new List<object>();
|
||||||
if (!string.IsNullOrEmpty(routeName) || requiredValues != null)
|
if (!string.IsNullOrEmpty(routeName) || metadataRequiredValues != null)
|
||||||
{
|
{
|
||||||
metadata.Add(new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues)));
|
metadata.Add(new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(metadataRequiredValues)));
|
||||||
}
|
}
|
||||||
metadataCollection = new EndpointMetadataCollection(metadata);
|
metadataCollection = new EndpointMetadataCollection(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RouteEndpoint(
|
return new RouteEndpoint(
|
||||||
TestConstants.EmptyRequestDelegate,
|
TestConstants.EmptyRequestDelegate,
|
||||||
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null, requiredValues: routePatternRequiredValues),
|
||||||
order,
|
order,
|
||||||
metadataCollection,
|
metadataCollection,
|
||||||
null);
|
null);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
|
{
|
||||||
|
public class RoutePatternPrecedenceTests : RoutePrecedenceTestsBase
|
||||||
|
{
|
||||||
|
protected override decimal ComputeMatched(string template)
|
||||||
|
{
|
||||||
|
return ComputeRoutePattern(template, RoutePrecedence.ComputeInbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override decimal ComputeGenerated(string template)
|
||||||
|
{
|
||||||
|
return ComputeRoutePattern(template, RoutePrecedence.ComputeOutbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal ComputeRoutePattern(string template, Func<RoutePattern, decimal> func)
|
||||||
|
{
|
||||||
|
var parsed = RoutePatternFactory.Parse(template);
|
||||||
|
return func(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,13 +2,11 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Template
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
public class RoutePrecedenceTests
|
public abstract class RoutePrecedenceTestsBase
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("Employees/{id}", "Employees/{employeeId}")]
|
[InlineData("Employees/{id}", "Employees/{employeeId}")]
|
||||||
|
|
@ -100,22 +98,34 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
Assert.True(xPrecedence > yPrecedence);
|
Assert.True(xPrecedence > yPrecedence);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static decimal ComputeMatched(string template)
|
[Fact]
|
||||||
|
public void ComputeGenerated_TooManySegments_ThrowHumaneError()
|
||||||
{
|
{
|
||||||
return Compute(template, RoutePrecedence.ComputeInbound);
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||||
}
|
{
|
||||||
private static decimal ComputeGenerated(string template)
|
// Arrange & Act
|
||||||
{
|
ComputeGenerated("{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}/{r}/{s}/{t}/{u}/{v}/{w}/{x}/{y}/{z}/{a2}/{b2}/{b3}");
|
||||||
return Compute(template, RoutePrecedence.ComputeOutbound);
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static decimal Compute(string template, Func<RouteTemplate, decimal> func)
|
[Fact]
|
||||||
|
public void ComputeMatched_TooManySegments_ThrowHumaneError()
|
||||||
{
|
{
|
||||||
var options = new Mock<IOptions<RouteOptions>>();
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||||
options.SetupGet(o => o.Value).Returns(new RouteOptions());
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
ComputeMatched("{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}/{r}/{s}/{t}/{u}/{v}/{w}/{x}/{y}/{z}/{a2}/{b2}/{b3}");
|
||||||
|
});
|
||||||
|
|
||||||
var parsed = TemplateParser.Parse(template);
|
// Assert
|
||||||
return func(parsed);
|
Assert.Equal("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract decimal ComputeMatched(string template);
|
||||||
|
|
||||||
|
protected abstract decimal ComputeGenerated(string template);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
|
{
|
||||||
|
public class RouteTemplatePrecedenceTests : RoutePrecedenceTestsBase
|
||||||
|
{
|
||||||
|
protected override decimal ComputeMatched(string template)
|
||||||
|
{
|
||||||
|
return ComputeRouteTemplate(template, RoutePrecedence.ComputeInbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override decimal ComputeGenerated(string template)
|
||||||
|
{
|
||||||
|
return ComputeRouteTemplate(template, RoutePrecedence.ComputeOutbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal ComputeRouteTemplate(string template, Func<RouteTemplate, decimal> func)
|
||||||
|
{
|
||||||
|
var parsed = TemplateParser.Parse(template);
|
||||||
|
return func(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,4 @@
|
||||||
<Project>
|
<Project>
|
||||||
<!-- Skip the parent folder to prevent getting test package references. -->
|
<!-- Skip the parent folder to prevent getting test package references. -->
|
||||||
<Import Project="..\..\Directory.Build.props" />
|
<Import Project="..\..\Directory.Build.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<DeveloperBuildTestWebsiteTfms>netcoreapp2.2</DeveloperBuildTestWebsiteTfms>
|
|
||||||
<StandardTestWebsiteTfms>$(DeveloperBuildTestWebsiteTfms)</StandardTestWebsiteTfms>
|
|
||||||
<StandardTestWebsiteTfms Condition=" '$(DeveloperBuild)' != 'true' ">netcoreapp2.2</StandardTestWebsiteTfms>
|
|
||||||
<StandardTestWebsiteTfms Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(StandardTestWebsiteTfms);net461</StandardTestWebsiteTfms>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -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 EndpointRouteBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IEndpointConventionBuilder MapHello(this IEndpointRouteBuilder builder, string template, string greeter)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
var pipeline = builder.CreateApplicationBuilder()
|
||||||
|
.UseHello(greeter)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
return builder.Map(
|
||||||
|
template,
|
||||||
|
"Hello " + greeter,
|
||||||
|
pipeline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 RoutingWebSite.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 RoutingWebSite.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 RoutingWebSite.HelloExtension
|
||||||
|
{
|
||||||
|
public class HelloOptions
|
||||||
|
{
|
||||||
|
public string Greeter { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net461</TargetFrameworks>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
|
|
@ -19,7 +21,7 @@ namespace RoutingWebSite
|
||||||
public class UseEndpointRoutingStartup
|
public class UseEndpointRoutingStartup
|
||||||
{
|
{
|
||||||
private static readonly byte[] _homePayload = Encoding.UTF8.GetBytes("Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext");
|
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)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
|
@ -29,114 +31,91 @@ namespace RoutingWebSite
|
||||||
{
|
{
|
||||||
options.ConstraintMap.Add("endsWith", typeof(EndsWithStringRouteConstraint));
|
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(IApplicationBuilder app)
|
public void Configure(IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
app.UseEndpointRouting();
|
app.UseEndpointRouting(routes =>
|
||||||
|
{
|
||||||
|
routes.MapHello("/helloworld", "World");
|
||||||
|
|
||||||
|
routes.MapGet(
|
||||||
|
"/",
|
||||||
|
(httpContext) =>
|
||||||
|
{
|
||||||
|
var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("Endpoints:");
|
||||||
|
foreach (var endpoint in dataSource.Endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
sb.AppendLine($"- {endpoint.RoutePattern.RawText}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = httpContext.Response;
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
return response.WriteAsync(sb.ToString());
|
||||||
|
});
|
||||||
|
routes.MapGet(
|
||||||
|
"/plaintext",
|
||||||
|
(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);
|
||||||
|
});
|
||||||
|
routes.MapGet(
|
||||||
|
"/withconstraints/{id:endsWith(_001)}",
|
||||||
|
(httpContext) =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
return response.WriteAsync("WithConstraints");
|
||||||
|
});
|
||||||
|
routes.MapGet(
|
||||||
|
"/withoptionalconstraints/{id:endsWith(_001)?}",
|
||||||
|
(httpContext) =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
return response.WriteAsync("withoptionalconstraints");
|
||||||
|
});
|
||||||
|
routes.MapGet(
|
||||||
|
"/WithSingleAsteriskCatchAll/{*path}",
|
||||||
|
(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 { }));
|
||||||
|
},
|
||||||
|
new RouteValuesAddressMetadata(routeName: "WithSingleAsteriskCatchAll", requiredValues: new RouteValueDictionary()));
|
||||||
|
routes.MapGet(
|
||||||
|
"/WithDoubleAsteriskCatchAll/{**path}",
|
||||||
|
(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 { }));
|
||||||
|
},
|
||||||
|
new RouteValuesAddressMetadata(routeName: "WithDoubleAsteriskCatchAll", requiredValues: new RouteValueDictionary()));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Map("/Branch1", branch => SetupBranch(branch, "Branch1"));
|
||||||
|
app.Map("/Branch2", branch => SetupBranch(branch, "Branch2"));
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
|
@ -144,5 +123,15 @@ namespace RoutingWebSite
|
||||||
|
|
||||||
app.UseEndpoint();
|
app.UseEndpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetupBranch(IApplicationBuilder app, string name)
|
||||||
|
{
|
||||||
|
app.UseEndpointRouting(routes =>
|
||||||
|
{
|
||||||
|
routes.MapGet("api/get/{id}", (context) => context.Response.WriteAsync($"{name} - API Get {context.GetRouteData().Values["id"]}"));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseEndpoint();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,17 @@ namespace RoutingWebSite
|
||||||
defaults: new { lastName = "Doe" },
|
defaults: new { lastName = "Doe" },
|
||||||
constraints: new { lastName = new RegexRouteConstraint(new Regex("[a-zA-Z]{3}", RegexOptions.CultureInvariant, RegexMatchTimeout)) });
|
constraints: new { lastName = new RegexRouteConstraint(new Regex("[a-zA-Z]{3}", RegexOptions.CultureInvariant, RegexMatchTimeout)) });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.Map("/Branch1", branch => SetupBranch(branch, "Branch1"));
|
||||||
|
app.Map("/Branch2", branch => SetupBranch(branch, "Branch2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupBranch(IApplicationBuilder app, string name)
|
||||||
|
{
|
||||||
|
app.UseRouter(routes =>
|
||||||
|
{
|
||||||
|
routes.MapGet("api/get/{id}", (request, response, routeData) => response.WriteAsync($"{name} - API Get {routeData.Values["id"]}"));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VersionPrefix>2.2.0</VersionPrefix>
|
<VersionPrefix>3.0.0</VersionPrefix>
|
||||||
<VersionSuffix>rtm</VersionSuffix>
|
<VersionSuffix Condition=" '$(VersionSuffix)' == '' ">dev</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>
|
|
||||||
<FeatureBranchVersionPrefix Condition="'$(FeatureBranchVersionPrefix)' == ''">a-</FeatureBranchVersionPrefix>
|
|
||||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(FeatureBranchVersionSuffix)' != ''">$(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-'))</VersionSuffix>
|
|
||||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue