Merge remote-tracking branch 'Routing/rybrande/release21ToSrc' into rybrande/Mondo2.1
This commit is contained in:
commit
ff3891fa7a
|
|
@ -0,0 +1,41 @@
|
|||
[Oo]bj/
|
||||
[Bb]in/
|
||||
TestResults/
|
||||
.nuget/
|
||||
*.sln.ide/
|
||||
_ReSharper.*/
|
||||
packages/
|
||||
artifacts/
|
||||
PublishProfiles/
|
||||
.vs/
|
||||
.build/
|
||||
.testPublish/
|
||||
bower_components/
|
||||
node_modules/
|
||||
**/wwwroot/lib/
|
||||
debugSettings.json
|
||||
project.lock.json
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
*.docstates
|
||||
_ReSharper.*
|
||||
nuget.exe
|
||||
*net45.csproj
|
||||
*net451.csproj
|
||||
*k10.csproj
|
||||
*.psess
|
||||
*.vsp
|
||||
*.pidb
|
||||
*.userprefs
|
||||
*DS_Store
|
||||
*.ncrunchsolution
|
||||
*.*sdf
|
||||
*.ipch
|
||||
.settings
|
||||
*.sln.ide
|
||||
node_modules
|
||||
**/[Cc]ompiler/[Rr]esources/**/*.js
|
||||
*launchSettings.json
|
||||
global.json
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<Project>
|
||||
<Import
|
||||
Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))\AspNetCoreSettings.props"
|
||||
Condition=" '$(CI)' != 'true' AND '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))' != '' " />
|
||||
|
||||
<Import Project="version.props" />
|
||||
<Import Project="build\dependencies.props" />
|
||||
<Import Project="build\sources.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Product>Microsoft ASP.NET Core</Product>
|
||||
<RepositoryUrl>https://github.com/aspnet/Routing</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)build\Key.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"adx-nonshipping": {
|
||||
"rules": [],
|
||||
"packages": {
|
||||
"Microsoft.AspNetCore.Routing.DecisionTree.Sources": {}
|
||||
}
|
||||
},
|
||||
"Default": {
|
||||
"rules": [
|
||||
"DefaultCompositeRule"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
ASP.NET Routing
|
||||
===
|
||||
|
||||
AppVeyor: [](https://ci.appveyor.com/project/aspnetci/Routing/branch/dev)
|
||||
|
||||
Travis: [](https://travis-ci.org/aspnet/Routing)
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27106.3000
|
||||
MinimumVisualStudioVersion = 15.0.26730.03
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0E966C37-7334-4D96-AAF6-9F49FBD166E3}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\Directory.Build.props = src\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{C3ADD55B-B9C7-4061-8AD4-6A70D1AE3B2E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{95359B4B-4C85-4B44-A75B-0621905C4CF6}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
test\Directory.Build.props = test\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Routing", "src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj", "{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Routing.Tests", "test\Microsoft.AspNetCore.Routing.Tests\Microsoft.AspNetCore.Routing.Tests.csproj", "{636D79ED-7B32-487C-BDA5-D2A1AAA97371}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoutingSample.Web", "samples\RoutingSample.Web\RoutingSample.Web.csproj", "{DB94E647-C73A-4F52-A126-AA7544CCF33B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C430C499-382D-47BD-B351-CF8F89C08CD2}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
global.json = global.json
|
||||
NuGet.config = NuGet.config
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests", "test\Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests\Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests.csproj", "{09C2933C-23AC-41B7-994D-E8A5184A629C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Routing.Abstractions", "src\Microsoft.AspNetCore.Routing.Abstractions\Microsoft.AspNetCore.Routing.Abstractions.csproj", "{ED253B01-24F1-43D1-AA0B-079391E105A9}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests", "test\Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests\Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests.csproj", "{741B0B05-CE96-473B-B962-6B0A347DF79A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Routing.FunctionalTests", "test\Microsoft.AspNetCore.Routing.FunctionalTests\Microsoft.AspNetCore.Routing.FunctionalTests.csproj", "{5C73140B-41F3-466F-A07B-3614E4D80DF9}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6DC6B416-C8C4-4BFA-8C1E-A55A6D7EFD08}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\dependencies.props = build\dependencies.props
|
||||
build\Key.snk = build\Key.snk
|
||||
build\repo.props = build\repo.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Routing.Performance", "benchmarks\Microsoft.AspNetCore.Routing.Performance\Microsoft.AspNetCore.Routing.Performance.csproj", "{F3D86714-4E64-41A6-9B36-A47B3683CF5D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{D5F39F59-5725-4127-82E7-67028D006185}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|Mixed Platforms = Debug|Mixed Platforms
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|Mixed Platforms = Release|Mixed Platforms
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|x86.Build.0 = Release|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9}.Release|x86.Build.0 = Release|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228} = {0E966C37-7334-4D96-AAF6-9F49FBD166E3}
|
||||
{636D79ED-7B32-487C-BDA5-D2A1AAA97371} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
|
||||
{DB94E647-C73A-4F52-A126-AA7544CCF33B} = {C3ADD55B-B9C7-4061-8AD4-6A70D1AE3B2E}
|
||||
{09C2933C-23AC-41B7-994D-E8A5184A629C} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
|
||||
{ED253B01-24F1-43D1-AA0B-079391E105A9} = {0E966C37-7334-4D96-AAF6-9F49FBD166E3}
|
||||
{741B0B05-CE96-473B-B962-6B0A347DF79A} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
|
||||
{5C73140B-41F3-466F-A07B-3614E4D80DF9} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
|
||||
{F3D86714-4E64-41A6-9B36-A47B3683CF5D} = {D5F39F59-5725-4127-82E7-67028D006185}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {36C8D815-B7F1-479D-894B-E606FB8DECDA}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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 BenchmarkDotNet.Columns;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Validators;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Performance
|
||||
{
|
||||
public class CoreConfig : ManualConfig
|
||||
{
|
||||
public CoreConfig()
|
||||
{
|
||||
Add(JitOptimizationsValidator.FailOnError);
|
||||
Add(MemoryDiagnoser.Default);
|
||||
Add(StatisticColumn.OperationsPerSecond);
|
||||
|
||||
Add(Job.Default
|
||||
.With(BenchmarkDotNet.Environments.Runtime.Core)
|
||||
.WithRemoveOutliers(false)
|
||||
.With(new GcMode() { Server = true })
|
||||
.With(RunStrategy.Throughput)
|
||||
.WithLaunchCount(3)
|
||||
.WithWarmupCount(5)
|
||||
.WithTargetCount(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Performance
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
// 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.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Performance
|
||||
{
|
||||
public class RoutingBenchmark
|
||||
{
|
||||
private const int NumberOfRequestTypes = 3;
|
||||
private const int Iterations = 100;
|
||||
|
||||
private readonly IRouter _treeRouter;
|
||||
private readonly RequestEntry[] _requests;
|
||||
|
||||
public RoutingBenchmark()
|
||||
{
|
||||
var handler = new RouteHandler((next) => Task.FromResult<object>(null));
|
||||
|
||||
var treeBuilder = new TreeRouteBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
|
||||
new DefaultInlineConstraintResolver(new OptionsManager<RouteOptions>(new OptionsFactory<RouteOptions>(Enumerable.Empty<IConfigureOptions<RouteOptions>>(), Enumerable.Empty<IPostConfigureOptions<RouteOptions>>()))));
|
||||
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets/{id}"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets/search/{term}"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("admin/users/{id}"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("admin/users/{id}/manage"), "default", 0);
|
||||
|
||||
_treeRouter = treeBuilder.Build();
|
||||
|
||||
_requests = new RequestEntry[NumberOfRequestTypes];
|
||||
|
||||
_requests[0].HttpContext = new DefaultHttpContext();
|
||||
_requests[0].HttpContext.Request.Path = "/api/Widgets/5";
|
||||
_requests[0].IsMatch = true;
|
||||
_requests[0].Values = new RouteValueDictionary(new { id = 5 });
|
||||
|
||||
_requests[1].HttpContext = new DefaultHttpContext();
|
||||
_requests[1].HttpContext.Request.Path = "/admin/users/17/mAnage";
|
||||
_requests[1].IsMatch = true;
|
||||
_requests[1].Values = new RouteValueDictionary(new { id = 17 });
|
||||
|
||||
_requests[2].HttpContext = new DefaultHttpContext();
|
||||
_requests[2].HttpContext.Request.Path = "/api/Widgets/search/dldldldldld/ddld";
|
||||
_requests[2].IsMatch = false;
|
||||
_requests[2].Values = new RouteValueDictionary();
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Attribute Routing", OperationsPerInvoke = Iterations * NumberOfRequestTypes)]
|
||||
public async Task AttributeRouting()
|
||||
{
|
||||
for (var i = 0; i < Iterations; i++)
|
||||
{
|
||||
for (var j = 0; j < _requests.Length; j++)
|
||||
{
|
||||
var context = new RouteContext(_requests[j].HttpContext);
|
||||
|
||||
await _treeRouter.RouteAsync(context);
|
||||
|
||||
Verify(context, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Verify(RouteContext context, int i)
|
||||
{
|
||||
if (_requests[i].IsMatch)
|
||||
{
|
||||
if (context.Handler == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed {i}");
|
||||
}
|
||||
|
||||
var values = _requests[i].Values;
|
||||
if (values.Count != context.RouteData.Values.Count)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed {i}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (context.Handler != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed {i}");
|
||||
}
|
||||
|
||||
if (context.RouteData.Values.Count != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed {i}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct RequestEntry
|
||||
{
|
||||
public HttpContext HttpContext;
|
||||
public bool IsMatch;
|
||||
public RouteValueDictionary Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
Compile the solution in Release mode (so binaries are available in release)
|
||||
|
||||
To run a specific benchmark add it as parameter.
|
||||
```
|
||||
dotnet run -c Release <benchmark_name>
|
||||
```
|
||||
|
||||
If you run without any parameters, you'll be offered the list of all benchmarks and get to choose.
|
||||
```
|
||||
dotnet run -c Release
|
||||
```
|
||||
Binary file not shown.
|
|
@ -0,0 +1,46 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- These package versions may be overridden or updated by automation. -->
|
||||
<PropertyGroup Label="Package Versions: Auto">
|
||||
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
|
||||
<InternalAspNetCoreSdkPackageVersion>2.1.3-rtm-15802</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
||||
<MicrosoftNETCoreApp21PackageVersion>2.1.2</MicrosoftNETCoreApp21PackageVersion>
|
||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||
<MoqPackageVersion>4.7.49</MoqPackageVersion>
|
||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
|
||||
<XunitPackageVersion>2.3.1</XunitPackageVersion>
|
||||
<XunitRunnerVisualStudioPackageVersion>2.4.0-beta.1.build3945</XunitRunnerVisualStudioPackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- This may import a generated file which may override the variables above. -->
|
||||
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
|
||||
|
||||
<!-- These are package versions that should not be overridden or updated by automation. -->
|
||||
<PropertyGroup Label="Package Versions: Pinned">
|
||||
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.1.1</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHostingPackageVersion>2.1.1</MicrosoftAspNetCoreHostingPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.1.1</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.1.1</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpPackageVersion>2.1.1</MicrosoftAspNetCoreHttpPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.1.1</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.1.2</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||
<MicrosoftAspNetCoreTestHostPackageVersion>2.1.1</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0</MicrosoftAspNetCoreTestingPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
||||
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.1.1</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
|
||||
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.1</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||
<MicrosoftExtensionsLoggingPackageVersion>2.1.1</MicrosoftExtensionsLoggingPackageVersion>
|
||||
<MicrosoftExtensionsLoggingTestingPackageVersion>2.1.1</MicrosoftExtensionsLoggingTestingPackageVersion>
|
||||
<MicrosoftExtensionsObjectPoolPackageVersion>2.1.1</MicrosoftExtensionsObjectPoolPackageVersion>
|
||||
<MicrosoftExtensionsOptionsPackageVersion>2.1.1</MicrosoftExtensionsOptionsPackageVersion>
|
||||
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.1.1</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
|
||||
<MicrosoftExtensionsWebEncodersPackageVersion>2.1.1</MicrosoftExtensionsWebEncodersPackageVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<Project>
|
||||
<Import Project="dependencies.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- These properties are use by the automation that updates dependencies.props -->
|
||||
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
|
||||
<LineupPackageVersion>2.1.0-rc1-*</LineupPackageVersion>
|
||||
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<Project>
|
||||
<Import Project="$(DotNetRestoreSourcePropsPath)" Condition="'$(DotNetRestoreSourcePropsPath)' != ''"/>
|
||||
|
||||
<PropertyGroup Label="RestoreSources">
|
||||
<RestoreSources>$(DotNetRestoreSources)</RestoreSources>
|
||||
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true' AND '$(AspNetUniverseBuildOffline)' != 'true' ">
|
||||
$(RestoreSources);
|
||||
https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
|
||||
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
|
||||
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
|
||||
</RestoreSources>
|
||||
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
|
||||
$(RestoreSources);
|
||||
https://api.nuget.org/v3/index.json;
|
||||
</RestoreSources>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace RoutingSample.Web
|
||||
{
|
||||
public class PrefixRoute : IRouter
|
||||
{
|
||||
private readonly IRouteHandler _target;
|
||||
private readonly string _prefix;
|
||||
|
||||
public PrefixRoute(IRouteHandler target, string prefix)
|
||||
{
|
||||
_target = target;
|
||||
|
||||
if (prefix == null)
|
||||
{
|
||||
prefix = "/";
|
||||
}
|
||||
else if (prefix.Length > 0 && prefix[0] != '/')
|
||||
{
|
||||
// owin.RequestPath starts with a /
|
||||
prefix = "/" + prefix;
|
||||
}
|
||||
|
||||
if (prefix.Length > 1 && prefix[prefix.Length - 1] == '/')
|
||||
{
|
||||
prefix = prefix.Substring(0, prefix.Length - 1);
|
||||
}
|
||||
|
||||
_prefix = prefix;
|
||||
}
|
||||
|
||||
public Task RouteAsync(RouteContext context)
|
||||
{
|
||||
var requestPath = context.HttpContext.Request.Path.Value ?? string.Empty;
|
||||
if (requestPath.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (requestPath.Length > _prefix.Length)
|
||||
{
|
||||
var lastCharacter = requestPath[_prefix.Length];
|
||||
if (lastCharacter != '/' && lastCharacter != '#' && lastCharacter != '?')
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
context.Handler = _target.GetRequestHandler(context.HttpContext, context.RouteData);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// 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.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RoutingSample.Web
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var webHost = GetWebHostBuilder().Build();
|
||||
webHost.Run();
|
||||
}
|
||||
|
||||
// For unit testing
|
||||
public static IWebHostBuilder GetWebHostBuilder()
|
||||
{
|
||||
return new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseIISIntegration()
|
||||
.ConfigureServices(services => services.AddRouting())
|
||||
.Configure(app => app.UseRouter(routes =>
|
||||
{
|
||||
routes.DefaultHandler = new RouteHandler((httpContext) =>
|
||||
{
|
||||
var request = httpContext.Request;
|
||||
return httpContext.Response.WriteAsync($"Verb = {request.Method.ToUpperInvariant()} - Path = {request.Path} - Route values - {string.Join(", ", httpContext.GetRouteData().Values)}");
|
||||
});
|
||||
|
||||
routes.MapGet("api/get/{id}", (request, response, routeData) => response.WriteAsync($"API Get {routeData.Values["id"]}"))
|
||||
.MapMiddlewareRoute("api/middleware", (appBuilder) => appBuilder.Use((httpContext, next) => httpContext.Response.WriteAsync("Middleware!")))
|
||||
.MapRoute(
|
||||
name: "AllVerbs",
|
||||
template: "api/all/{name}/{lastName?}",
|
||||
defaults: new { lastName = "Doe" },
|
||||
constraints: new { lastName = new RegexRouteConstraint(new Regex("[a-zA-Z]{3}", RegexOptions.CultureInvariant, RegexMatchTimeout)) });
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// 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 Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RoutingSample.Web
|
||||
{
|
||||
public static class RouteBuilderExtensions
|
||||
{
|
||||
public static IRouteBuilder AddPrefixRoute(
|
||||
this IRouteBuilder routeBuilder,
|
||||
string prefix,
|
||||
IRouteHandler handler)
|
||||
{
|
||||
routeBuilder.Routes.Add(new PrefixRoute(handler, prefix));
|
||||
return routeBuilder;
|
||||
}
|
||||
|
||||
public static IRouteBuilder MapLocaleRoute(
|
||||
this IRouteBuilder routeBuilder,
|
||||
string locale,
|
||||
string routeTemplate,
|
||||
object defaults)
|
||||
{
|
||||
var defaultsDictionary = new RouteValueDictionary(defaults);
|
||||
defaultsDictionary.Add("locale", locale);
|
||||
|
||||
var constraintResolver = routeBuilder.ServiceProvider.GetService<IInlineConstraintResolver>();
|
||||
|
||||
var route = new Route(
|
||||
target: routeBuilder.DefaultHandler,
|
||||
routeTemplate: routeTemplate,
|
||||
defaults: defaultsDictionary,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: constraintResolver);
|
||||
routeBuilder.Routes.Add(route);
|
||||
|
||||
return routeBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;netcoreapp2.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net461</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// 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.DecisionTree
|
||||
{
|
||||
internal class DecisionCriterion<TItem>
|
||||
{
|
||||
public string Key { get; set; }
|
||||
|
||||
public Dictionary<object, DecisionTreeNode<TItem>> Branches { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.DecisionTree
|
||||
{
|
||||
internal struct DecisionCriterionValue
|
||||
{
|
||||
private readonly object _value;
|
||||
|
||||
public DecisionCriterionValue(object value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public object Value
|
||||
{
|
||||
get { return _value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.DecisionTree
|
||||
{
|
||||
internal class DecisionCriterionValueEqualityComparer : IEqualityComparer<DecisionCriterionValue>
|
||||
{
|
||||
public DecisionCriterionValueEqualityComparer(IEqualityComparer<object> innerComparer)
|
||||
{
|
||||
InnerComparer = innerComparer;
|
||||
}
|
||||
|
||||
public IEqualityComparer<object> InnerComparer { get; private set; }
|
||||
|
||||
public bool Equals(DecisionCriterionValue x, DecisionCriterionValue y)
|
||||
{
|
||||
return InnerComparer.Equals(x.Value, y.Value);
|
||||
}
|
||||
|
||||
public int GetHashCode(DecisionCriterionValue obj)
|
||||
{
|
||||
return InnerComparer.GetHashCode(obj.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.DecisionTree
|
||||
{
|
||||
// This code generates a minimal tree of decision criteria that map known categorical data
|
||||
// (key-value-pairs) to a set of inputs. Action Selection is the best example of how this
|
||||
// can be used, so the comments here will describe the process from the point-of-view,
|
||||
// though the decision tree is generally applicable to like-problems.
|
||||
//
|
||||
// Care has been taken here to keep the performance of building the data-structure at a
|
||||
// reasonable level, as this has an impact on startup cost for action selection. Additionally
|
||||
// we want to hold on to the minimal amount of memory needed once we've built the tree.
|
||||
//
|
||||
// Ex:
|
||||
// Given actions like the following, create a decision tree that will help action
|
||||
// selection work efficiently.
|
||||
//
|
||||
// Given any set of route data it should be possible to traverse the tree using the
|
||||
// presence our route data keys (like action), and whether or not they match any of
|
||||
// the known values for that route data key, to find the set of actions that match
|
||||
// the route data.
|
||||
//
|
||||
// Actions:
|
||||
//
|
||||
// { controller = "Home", action = "Index" }
|
||||
// { controller = "Products", action = "Index" }
|
||||
// { controller = "Products", action = "Buy" }
|
||||
// { area = "Admin", controller = "Users", action = "AddUser" }
|
||||
//
|
||||
// The generated tree looks like this (json-like-notation):
|
||||
//
|
||||
// {
|
||||
// action : {
|
||||
// "AddUser" : {
|
||||
// controller : {
|
||||
// "Users" : {
|
||||
// area : {
|
||||
// "Admin" : match { area = "Admin", controller = "Users", action = "AddUser" }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "Buy" : {
|
||||
// controller : {
|
||||
// "Products" : {
|
||||
// area : {
|
||||
// null : match { controller = "Products", action = "Buy" }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "Index" : {
|
||||
// controller : {
|
||||
// "Home" : {
|
||||
// area : {
|
||||
// null : match { controller = "Home", action = "Index" }
|
||||
// }
|
||||
// }
|
||||
// "Products" : {
|
||||
// area : {
|
||||
// "null" : match { controller = "Products", action = "Index" }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
internal static class DecisionTreeBuilder<TItem>
|
||||
{
|
||||
public static DecisionTreeNode<TItem> GenerateTree(IReadOnlyList<TItem> items, IClassifier<TItem> classifier)
|
||||
{
|
||||
var itemDescriptors = new List<ItemDescriptor<TItem>>();
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
itemDescriptors.Add(new ItemDescriptor<TItem>()
|
||||
{
|
||||
Criteria = classifier.GetCriteria(items[i]),
|
||||
Index = i,
|
||||
Item = items[i],
|
||||
});
|
||||
}
|
||||
|
||||
var comparer = new DecisionCriterionValueEqualityComparer(classifier.ValueComparer);
|
||||
return GenerateNode(
|
||||
new TreeBuilderContext(),
|
||||
comparer,
|
||||
itemDescriptors);
|
||||
}
|
||||
|
||||
private static DecisionTreeNode<TItem> GenerateNode(
|
||||
TreeBuilderContext context,
|
||||
DecisionCriterionValueEqualityComparer comparer,
|
||||
IList<ItemDescriptor<TItem>> items)
|
||||
{
|
||||
// The extreme use of generics here is intended to reduce the number of intermediate
|
||||
// allocations of wrapper classes. Performance testing found that building these trees allocates
|
||||
// significant memory that we can avoid and that it has a real impact on startup.
|
||||
var criteria = new Dictionary<string, Criterion>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Matches are items that have no remaining criteria - at this point in the tree
|
||||
// they are considered accepted.
|
||||
var matches = new List<TItem>();
|
||||
|
||||
// For each item in the working set, we want to map it to it's possible criteria-branch
|
||||
// pairings, then reduce that tree to the minimal set.
|
||||
foreach (var item in items)
|
||||
{
|
||||
var unsatisfiedCriteria = 0;
|
||||
|
||||
foreach (var kvp in item.Criteria)
|
||||
{
|
||||
// context.CurrentCriteria is the logical 'stack' of criteria that we've already processed
|
||||
// on this branch of the tree.
|
||||
if (context.CurrentCriteria.Contains(kvp.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
unsatisfiedCriteria++;
|
||||
|
||||
Criterion criterion;
|
||||
if (!criteria.TryGetValue(kvp.Key, out criterion))
|
||||
{
|
||||
criterion = new Criterion(comparer);
|
||||
criteria.Add(kvp.Key, criterion);
|
||||
}
|
||||
|
||||
List<ItemDescriptor<TItem>> branch;
|
||||
if (!criterion.TryGetValue(kvp.Value, out branch))
|
||||
{
|
||||
branch = new List<ItemDescriptor<TItem>>();
|
||||
criterion.Add(kvp.Value, branch);
|
||||
}
|
||||
|
||||
branch.Add(item);
|
||||
}
|
||||
|
||||
// If all of the criteria on item are satisfied by the 'stack' then this item is a match.
|
||||
if (unsatisfiedCriteria == 0)
|
||||
{
|
||||
matches.Add(item.Item);
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate criteria in order of branchiness to determine which one to explore next. If a criterion
|
||||
// has no 'new' matches under it then we can just eliminate that part of the tree.
|
||||
var reducedCriteria = new List<DecisionCriterion<TItem>>();
|
||||
foreach (var criterion in criteria.OrderByDescending(c => c.Value.Count))
|
||||
{
|
||||
var reducedBranches = new Dictionary<object, DecisionTreeNode<TItem>>(comparer.InnerComparer);
|
||||
|
||||
foreach (var branch in criterion.Value)
|
||||
{
|
||||
var reducedItems = new List<ItemDescriptor<TItem>>();
|
||||
foreach (var item in branch.Value)
|
||||
{
|
||||
if (context.MatchedItems.Add(item))
|
||||
{
|
||||
reducedItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (reducedItems.Count > 0)
|
||||
{
|
||||
var childContext = new TreeBuilderContext(context);
|
||||
childContext.CurrentCriteria.Add(criterion.Key);
|
||||
|
||||
var newBranch = GenerateNode(childContext, comparer, branch.Value);
|
||||
reducedBranches.Add(branch.Key.Value, newBranch);
|
||||
}
|
||||
}
|
||||
|
||||
if (reducedBranches.Count > 0)
|
||||
{
|
||||
var newCriterion = new DecisionCriterion<TItem>()
|
||||
{
|
||||
Key = criterion.Key,
|
||||
Branches = reducedBranches,
|
||||
};
|
||||
|
||||
reducedCriteria.Add(newCriterion);
|
||||
}
|
||||
}
|
||||
|
||||
return new DecisionTreeNode<TItem>()
|
||||
{
|
||||
Criteria = reducedCriteria.ToList(),
|
||||
Matches = matches,
|
||||
};
|
||||
}
|
||||
|
||||
private class TreeBuilderContext
|
||||
{
|
||||
public TreeBuilderContext()
|
||||
{
|
||||
CurrentCriteria = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
MatchedItems = new HashSet<ItemDescriptor<TItem>>();
|
||||
}
|
||||
|
||||
public TreeBuilderContext(TreeBuilderContext other)
|
||||
{
|
||||
CurrentCriteria = new HashSet<string>(other.CurrentCriteria, StringComparer.OrdinalIgnoreCase);
|
||||
MatchedItems = new HashSet<ItemDescriptor<TItem>>();
|
||||
}
|
||||
|
||||
public HashSet<string> CurrentCriteria { get; private set; }
|
||||
|
||||
public HashSet<ItemDescriptor<TItem>> MatchedItems { get; private set; }
|
||||
}
|
||||
|
||||
// Subclass just to give a logical name to a mess of generics
|
||||
private class Criterion : Dictionary<DecisionCriterionValue, List<ItemDescriptor<TItem>>>
|
||||
{
|
||||
public Criterion(DecisionCriterionValueEqualityComparer comparer)
|
||||
: base(comparer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.DecisionTree
|
||||
{
|
||||
// Data structure representing a node in a decision tree. These are created in DecisionTreeBuilder
|
||||
// and walked to find a set of items matching some input criteria.
|
||||
internal class DecisionTreeNode<TItem>
|
||||
{
|
||||
// The list of matches for the current node. This represents a set of items that have had all
|
||||
// of their criteria matched if control gets to this point in the tree.
|
||||
public IList<TItem> Matches { get; set; }
|
||||
|
||||
// Additional criteria that further branch out from this node. Walk these to fine more items
|
||||
// matching the input data.
|
||||
public IList<DecisionCriterion<TItem>> Criteria { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// 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.DecisionTree
|
||||
{
|
||||
internal interface IClassifier<TItem>
|
||||
{
|
||||
IDictionary<string, DecisionCriterionValue> GetCriteria(TItem item);
|
||||
|
||||
IEqualityComparer<object> ValueComparer { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// 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.DecisionTree
|
||||
{
|
||||
internal class ItemDescriptor<TItem>
|
||||
{
|
||||
public IDictionary<string, DecisionCriterionValue> Criteria { get; set; }
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public TItem Item { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<Project>
|
||||
<Import Project="..\Directory.Build.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
</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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract that a class must implement in order to check whether a URL parameter
|
||||
/// value is valid for a constraint.
|
||||
/// </summary>
|
||||
public interface IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the URL parameter contains a valid value for this constraint.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
|
||||
/// <param name="route">The router that this constraint belongs to.</param>
|
||||
/// <param name="routeKey">The name of the parameter that is being checked.</param>
|
||||
/// <param name="values">A dictionary that contains the parameters for the URL.</param>
|
||||
/// <param name="routeDirection">
|
||||
/// An object that indicates whether the constraint check is being performed
|
||||
/// when an incoming request is being handled or when a URL is being generated.
|
||||
/// </param>
|
||||
/// <returns><c>true</c> if the URL parameter contains a valid value; otherwise, <c>false</c>.</returns>
|
||||
bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a contract for a handler of a route.
|
||||
/// </summary>
|
||||
public interface IRouteHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a <see cref="RequestDelegate"/> to handle the request, based on the provided
|
||||
/// <paramref name="routeData"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="routeData">The <see cref="RouteData"/> associated with the current routing match.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="RequestDelegate"/>, or <c>null</c> if the handler cannot handle this request.
|
||||
/// </returns>
|
||||
RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// 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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public interface IRouter
|
||||
{
|
||||
Task RouteAsync(RouteContext context);
|
||||
|
||||
VirtualPathData GetVirtualPath(VirtualPathContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// A feature interface for routing functionality.
|
||||
/// </summary>
|
||||
public interface IRoutingFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Routing.RouteData"/> associated with the current request.
|
||||
/// </summary>
|
||||
RouteData RouteData { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core abstractions for routing requests to application logic and for generating links.
|
||||
Commonly used types:
|
||||
Microsoft.AspNetCore.Routing.IRouter
|
||||
Microsoft.AspNetCore.Routing.RouteData</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;routing</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" Version="$(MicrosoftExtensionsPropertyHelperSourcesPackageVersion)" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
58
src/Routing/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/Resources.Designer.cs
generated
Normal file
58
src/Routing/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/Resources.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// A context object for <see cref="IRouter.RouteAsync(RouteContext)"/>.
|
||||
/// </summary>
|
||||
public class RouteContext
|
||||
{
|
||||
private RouteData _routeData;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteContext"/> for the provided <paramref name="httpContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
|
||||
public RouteContext(HttpContext httpContext)
|
||||
{
|
||||
HttpContext = httpContext;
|
||||
|
||||
RouteData = new RouteData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for the request. An <see cref="IRouter"/> should set <see cref="Handler"/>
|
||||
/// when it matches.
|
||||
/// </summary>
|
||||
public RequestDelegate Handler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Http.HttpContext"/> associated with the current request.
|
||||
/// </summary>
|
||||
public HttpContext HttpContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Routing.RouteData"/> associated with the current context.
|
||||
/// </summary>
|
||||
public RouteData RouteData
|
||||
{
|
||||
get
|
||||
{
|
||||
return _routeData;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(RouteData));
|
||||
}
|
||||
|
||||
_routeData = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,296 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about the current routing path.
|
||||
/// </summary>
|
||||
public class RouteData
|
||||
{
|
||||
private RouteValueDictionary _dataTokens;
|
||||
private List<IRouter> _routers;
|
||||
private RouteValueDictionary _values;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteData"/> instance.
|
||||
/// </summary>
|
||||
public RouteData()
|
||||
{
|
||||
// Perf: Avoid allocating collections unless needed.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteData"/> instance with values copied from <paramref name="other"/>.
|
||||
/// </summary>
|
||||
/// <param name="other">The other <see cref="RouteData"/> instance to copy.</param>
|
||||
public RouteData(RouteData other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(other));
|
||||
}
|
||||
|
||||
// Perf: Avoid allocating collections unless we need to make a copy.
|
||||
if (other._routers != null)
|
||||
{
|
||||
_routers = new List<IRouter>(other.Routers);
|
||||
}
|
||||
|
||||
if (other._dataTokens != null)
|
||||
{
|
||||
_dataTokens = new RouteValueDictionary(other._dataTokens);
|
||||
}
|
||||
|
||||
if (other._values != null)
|
||||
{
|
||||
_values = new RouteValueDictionary(other._values);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data tokens produced by routes on the current routing path.
|
||||
/// </summary>
|
||||
public RouteValueDictionary DataTokens
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_dataTokens == null)
|
||||
{
|
||||
_dataTokens = new RouteValueDictionary();
|
||||
}
|
||||
|
||||
return _dataTokens;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of <see cref="IRouter"/> instances on the current routing path.
|
||||
/// </summary>
|
||||
public IList<IRouter> Routers
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_routers == null)
|
||||
{
|
||||
_routers = new List<IRouter>();
|
||||
}
|
||||
|
||||
return _routers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of values produced by routes on the current routing path.
|
||||
/// </summary>
|
||||
public RouteValueDictionary Values
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_values == null)
|
||||
{
|
||||
_values = new RouteValueDictionary();
|
||||
}
|
||||
|
||||
return _values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Creates a snapshot of the current state of the <see cref="RouteData"/> before appending
|
||||
/// <paramref name="router"/> to <see cref="Routers"/>, merging <paramref name="values"/> into
|
||||
/// <see cref="Values"/>, and merging <paramref name="dataTokens"/> into <see cref="DataTokens"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Call <see cref="RouteDataSnapshot.Restore"/> to restore the state of this <see cref="RouteData"/>
|
||||
/// to the state at the time of calling
|
||||
/// <see cref="PushState(IRouter, RouteValueDictionary, RouteValueDictionary)"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="router">
|
||||
/// An <see cref="IRouter"/> to append to <see cref="Routers"/>. If <c>null</c>, then <see cref="Routers"/>
|
||||
/// will not be changed.
|
||||
/// </param>
|
||||
/// <param name="values">
|
||||
/// A <see cref="RouteValueDictionary"/> to merge into <see cref="Values"/>. If <c>null</c>, then
|
||||
/// <see cref="Values"/> will not be changed.
|
||||
/// </param>
|
||||
/// <param name="dataTokens">
|
||||
/// A <see cref="RouteValueDictionary"/> to merge into <see cref="DataTokens"/>. If <c>null</c>, then
|
||||
/// <see cref="DataTokens"/> will not be changed.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="RouteDataSnapshot"/> that captures the current state.</returns>
|
||||
public RouteDataSnapshot PushState(IRouter router, RouteValueDictionary values, RouteValueDictionary dataTokens)
|
||||
{
|
||||
// Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in
|
||||
// Array.CopyTo inside the List(IEnumerable<T>) constructor.
|
||||
List<IRouter> routers = null;
|
||||
var count = _routers?.Count;
|
||||
if (count > 0)
|
||||
{
|
||||
routers = new List<IRouter>(count.Value);
|
||||
for (var i = 0; i < count.Value; i++)
|
||||
{
|
||||
routers.Add(_routers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var snapshot = new RouteDataSnapshot(
|
||||
this,
|
||||
_dataTokens?.Count > 0 ? new RouteValueDictionary(_dataTokens) : null,
|
||||
routers,
|
||||
_values?.Count > 0 ? new RouteValueDictionary(_values) : null);
|
||||
|
||||
if (router != null)
|
||||
{
|
||||
Routers.Add(router);
|
||||
}
|
||||
|
||||
if (values != null)
|
||||
{
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
Values[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dataTokens != null)
|
||||
{
|
||||
foreach (var kvp in dataTokens)
|
||||
{
|
||||
DataTokens[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A snapshot of the state of a <see cref="RouteData"/> instance.
|
||||
/// </summary>
|
||||
public struct RouteDataSnapshot
|
||||
{
|
||||
private readonly RouteData _routeData;
|
||||
private readonly RouteValueDictionary _dataTokens;
|
||||
private readonly IList<IRouter> _routers;
|
||||
private readonly RouteValueDictionary _values;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteDataSnapshot"/> for <paramref name="routeData"/>.
|
||||
/// </summary>
|
||||
/// <param name="routeData">The <see cref="RouteData"/>.</param>
|
||||
/// <param name="dataTokens">The data tokens.</param>
|
||||
/// <param name="routers">The routers.</param>
|
||||
/// <param name="values">The route values.</param>
|
||||
public RouteDataSnapshot(
|
||||
RouteData routeData,
|
||||
RouteValueDictionary dataTokens,
|
||||
IList<IRouter> routers,
|
||||
RouteValueDictionary values)
|
||||
{
|
||||
if (routeData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeData));
|
||||
}
|
||||
|
||||
_routeData = routeData;
|
||||
_dataTokens = dataTokens;
|
||||
_routers = routers;
|
||||
_values = values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the <see cref="RouteData"/> to the captured state.
|
||||
/// </summary>
|
||||
public void Restore()
|
||||
{
|
||||
if (_routeData._dataTokens == null && _dataTokens == null)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else if (_dataTokens == null)
|
||||
{
|
||||
_routeData._dataTokens.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_routeData._dataTokens.Clear();
|
||||
|
||||
foreach (var kvp in _dataTokens)
|
||||
{
|
||||
_routeData._dataTokens.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (_routeData._routers == null && _routers == null)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else if (_routers == null)
|
||||
{
|
||||
// Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in
|
||||
// Array.Clear inside the List.Clear() method.
|
||||
var routers = _routeData._routers;
|
||||
for (var i = routers.Count - 1; i >= 0 ; i--)
|
||||
{
|
||||
routers.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in
|
||||
// Array.Clear inside the List.Clear() method.
|
||||
//
|
||||
// We want to basically copy the contents of _routers in _routeData._routers - this change does
|
||||
// that with the minimal number of reads/writes and without calling Clear().
|
||||
var routers = _routeData._routers;
|
||||
var snapshotRouters = _routers;
|
||||
|
||||
// This is made more complicated by the fact that List[int] throws if i == Count, so we have
|
||||
// to do two loops and call Add for those cases.
|
||||
var i = 0;
|
||||
for (; i < snapshotRouters.Count && i < routers.Count; i++)
|
||||
{
|
||||
routers[i] = snapshotRouters[i];
|
||||
}
|
||||
|
||||
for (; i < snapshotRouters.Count; i++)
|
||||
{
|
||||
routers.Add(snapshotRouters[i]);
|
||||
}
|
||||
|
||||
// Trim excess - again avoiding RemoveRange because it uses native methods.
|
||||
for (i = routers.Count - 1; i >= snapshotRouters.Count; i--)
|
||||
{
|
||||
routers.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (_routeData._values == null && _values == null)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else if (_values == null)
|
||||
{
|
||||
_routeData._values.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_routeData._values.Clear();
|
||||
|
||||
foreach (var kvp in _values)
|
||||
{
|
||||
_routeData._values.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether ASP.NET routing is processing a URL from an HTTP request or generating a URL.
|
||||
/// </summary>
|
||||
public enum RouteDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// A URL from a client is being processed.
|
||||
/// </summary>
|
||||
IncomingRequest,
|
||||
|
||||
/// <summary>
|
||||
/// A URL is being created based on the route definition.
|
||||
/// </summary>
|
||||
UrlGeneration,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,790 @@
|
|||
// 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 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>
|
||||
{
|
||||
internal Storage _storage;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty <see cref="RouteValueDictionary"/>.
|
||||
/// </summary>
|
||||
public RouteValueDictionary()
|
||||
{
|
||||
_storage = EmptyStorage.Instance;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var dictionary = values as RouteValueDictionary;
|
||||
if (dictionary != null)
|
||||
{
|
||||
var listStorage = dictionary._storage as ListStorage;
|
||||
if (listStorage != null)
|
||||
{
|
||||
_storage = new ListStorage(listStorage);
|
||||
return;
|
||||
}
|
||||
|
||||
var propertyStorage = dictionary._storage as PropertyStorage;
|
||||
if (propertyStorage != null)
|
||||
{
|
||||
// PropertyStorage is immutable so we can just copy it.
|
||||
_storage = dictionary._storage;
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get here, it's an EmptyStorage.
|
||||
_storage = EmptyStorage.Instance;
|
||||
return;
|
||||
}
|
||||
|
||||
var keyValueEnumerable = values as IEnumerable<KeyValuePair<string, object>>;
|
||||
if (keyValueEnumerable != null)
|
||||
{
|
||||
var listStorage = new ListStorage();
|
||||
_storage = listStorage;
|
||||
foreach (var kvp in keyValueEnumerable)
|
||||
{
|
||||
if (listStorage.ContainsKey(kvp.Key))
|
||||
{
|
||||
var message = Resources.FormatRouteValueDictionary_DuplicateKey(kvp.Key, nameof(RouteValueDictionary));
|
||||
throw new ArgumentException(message, nameof(values));
|
||||
}
|
||||
|
||||
listStorage.Add(kvp);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var stringValueEnumerable = values as IEnumerable<KeyValuePair<string, string>>;
|
||||
if (stringValueEnumerable != null)
|
||||
{
|
||||
var listStorage = new ListStorage();
|
||||
_storage = listStorage;
|
||||
foreach (var kvp in stringValueEnumerable)
|
||||
{
|
||||
if (listStorage.ContainsKey(kvp.Key))
|
||||
{
|
||||
var message = Resources.FormatRouteValueDictionary_DuplicateKey(kvp.Key, nameof(RouteValueDictionary));
|
||||
throw new ArgumentException(message, nameof(values));
|
||||
}
|
||||
|
||||
listStorage.Add(new KeyValuePair<string, object>(kvp.Key, kvp.Value));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (values != null)
|
||||
{
|
||||
_storage = new PropertyStorage(values);
|
||||
return;
|
||||
}
|
||||
|
||||
_storage = EmptyStorage.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
object value;
|
||||
TryGetValue(key, out value);
|
||||
return value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (!_storage.TrySetValue(key, value))
|
||||
{
|
||||
Upgrade();
|
||||
_storage.TrySetValue(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 => _storage.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
var keys = new string[list.Count];
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
{
|
||||
keys[i] = list[i].Key;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<string> IReadOnlyDictionary<string, object>.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<object> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
var values = new object[list.Count];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
values[i] = list[i].Value;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<object> IReadOnlyDictionary<string, object>.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return 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)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary));
|
||||
throw new ArgumentException(message, nameof(key));
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(new KeyValuePair<string, object>(key, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
if (_storage.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
|
||||
{
|
||||
if (_storage.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return EqualityComparer<object>.Default.Equals(list[i].Value, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _storage.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <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 (_storage.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <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 (_storage.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase) &&
|
||||
EqualityComparer<object>.Default.Equals(list[i].Value, item.Value))
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (_storage.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetValue(string key, out object value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _storage.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
private void Upgrade()
|
||||
{
|
||||
_storage.Upgrade(ref _storage);
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
|
||||
{
|
||||
private readonly Storage _storage;
|
||||
private int _index;
|
||||
|
||||
public Enumerator(RouteValueDictionary dictionary)
|
||||
{
|
||||
if (dictionary == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
_storage = dictionary._storage;
|
||||
|
||||
Current = default(KeyValuePair<string, object>);
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
public KeyValuePair<string, object> Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++_index < _storage.Count)
|
||||
{
|
||||
Current = _storage[_index];
|
||||
return true;
|
||||
}
|
||||
|
||||
Current = default(KeyValuePair<string, object>);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Current = default(KeyValuePair<string, object>);
|
||||
_index = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Storage and its subclasses are internal for testing.
|
||||
internal abstract class Storage
|
||||
{
|
||||
public abstract int Count { get; }
|
||||
|
||||
public abstract KeyValuePair<string, object> this[int index] { get; set; }
|
||||
|
||||
public abstract void Upgrade(ref Storage storage);
|
||||
|
||||
public abstract bool TryGetValue(string key, out object value);
|
||||
|
||||
public abstract bool ContainsKey(string key);
|
||||
|
||||
public abstract bool TrySetValue(string key, object value);
|
||||
}
|
||||
|
||||
internal class ListStorage : Storage
|
||||
{
|
||||
private KeyValuePair<string, object>[] _items;
|
||||
private int _count;
|
||||
|
||||
private static readonly KeyValuePair<string, object>[] _emptyArray = new KeyValuePair<string, object>[0];
|
||||
|
||||
public ListStorage()
|
||||
{
|
||||
_items = _emptyArray;
|
||||
}
|
||||
|
||||
public ListStorage(int capacity)
|
||||
{
|
||||
if (capacity == 0)
|
||||
{
|
||||
_items = _emptyArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
_items = new KeyValuePair<string, object>[capacity];
|
||||
}
|
||||
}
|
||||
|
||||
public ListStorage(ListStorage other)
|
||||
{
|
||||
if (other.Count == 0)
|
||||
{
|
||||
_items = _emptyArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
_items = new KeyValuePair<string, object>[other.Count];
|
||||
for (var i = 0; i < other.Count; i++)
|
||||
{
|
||||
this.Add(other[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Capacity => _items.Length;
|
||||
|
||||
public override int Count => _count;
|
||||
|
||||
public override KeyValuePair<string, object> this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= _count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
return _items[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (index < 0 || index >= _count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
_items[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
if (_count == _items.Length)
|
||||
{
|
||||
EnsureCapacity(_count + 1);
|
||||
}
|
||||
|
||||
_items[_count++] = item;
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
_count--;
|
||||
|
||||
for (var i = index; i < _count; i++)
|
||||
{
|
||||
_items[i] = _items[i + 1];
|
||||
}
|
||||
|
||||
_items[_count] = default(KeyValuePair<string, object>);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (var i = 0; i < _count; i++)
|
||||
{
|
||||
_items[i] = default(KeyValuePair<string, object>);
|
||||
}
|
||||
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
for (var i = 0; i < _count; i++)
|
||||
{
|
||||
array[arrayIndex++] = _items[i];
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ContainsKey(string key)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var kvp = _items[i];
|
||||
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TrySetValue(string key, object value)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var kvp = _items[i];
|
||||
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_items[i] = new KeyValuePair<string, object>(key, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Add(new KeyValuePair<string, object>(key, value));
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryGetValue(string key, out object value)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var kvp = _items[i];
|
||||
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = kvp.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Upgrade(ref Storage storage)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int min)
|
||||
{
|
||||
var newLength = _items.Length == 0 ? 4 : _items.Length * 2;
|
||||
var newItems = new KeyValuePair<string, object>[newLength];
|
||||
for (var i = 0; i < _count; i++)
|
||||
{
|
||||
newItems[i] = _items[i];
|
||||
}
|
||||
|
||||
_items = newItems;
|
||||
}
|
||||
}
|
||||
|
||||
internal class PropertyStorage : Storage
|
||||
{
|
||||
private static readonly PropertyCache _propertyCache = new PropertyCache();
|
||||
|
||||
internal readonly object _value;
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
|
||||
public PropertyStorage(PropertyStorage propertyStorage)
|
||||
{
|
||||
_value = propertyStorage._value;
|
||||
_properties = propertyStorage._properties;
|
||||
}
|
||||
|
||||
public override int Count => _properties.Length;
|
||||
|
||||
public override KeyValuePair<string, object> this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var property = _properties[index];
|
||||
return new KeyValuePair<string, object>(property.Name, property.GetValue(_value));
|
||||
}
|
||||
set
|
||||
{
|
||||
// PropertyStorage never sets a value.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool TryGetValue(string key, out object value)
|
||||
{
|
||||
for (var i = 0; i < _properties.Length; i++)
|
||||
{
|
||||
var property = _properties[i];
|
||||
if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = property.GetValue(_value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool ContainsKey(string key)
|
||||
{
|
||||
for (var i = 0; i < _properties.Length; i++)
|
||||
{
|
||||
var property = _properties[i];
|
||||
if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TrySetValue(string key, object value)
|
||||
{
|
||||
// PropertyStorage never sets a value.
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Upgrade(ref Storage storage)
|
||||
{
|
||||
storage = new ListStorage(Count);
|
||||
for (var i = 0; i < _properties.Length; i++)
|
||||
{
|
||||
var property = _properties[i];
|
||||
storage.TrySetValue(property.Name, property.GetValue(_value));
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
PropertyHelper duplicate;
|
||||
if (names.TryGetValue(property.Name, out duplicate))
|
||||
{
|
||||
var message = Resources.FormatRouteValueDictionary_DuplicatePropertyName(
|
||||
type.FullName,
|
||||
property.Name,
|
||||
duplicate.Name,
|
||||
nameof(RouteValueDictionary));
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
names.Add(property.Name, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class EmptyStorage : Storage
|
||||
{
|
||||
public static readonly EmptyStorage Instance = new EmptyStorage();
|
||||
|
||||
private EmptyStorage()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Count => 0;
|
||||
|
||||
public override KeyValuePair<string, object> this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ContainsKey(string key)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryGetValue(string key, out object value)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TrySetValue(string key, object value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Upgrade(ref Storage storage)
|
||||
{
|
||||
storage = new ListStorage();
|
||||
}
|
||||
}
|
||||
|
||||
private class PropertyCache : ConcurrentDictionary<Type, PropertyHelper[]>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="HttpContext"/> related to routing.
|
||||
/// </summary>
|
||||
public static class RoutingHttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="RouteData"/> associated with the provided <paramref name="httpContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <returns>The <see cref="RouteData"/>, or null.</returns>
|
||||
public static RouteData GetRouteData(this HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
var routingFeature = httpContext.Features[typeof(IRoutingFeature)] as IRoutingFeature;
|
||||
return routingFeature?.RouteData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a route value from <see cref="RouteData.Values"/> associated with the provided
|
||||
/// <paramref name="httpContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="key">The key of the route value.</param>
|
||||
/// <returns>The corresponding route value, or null.</returns>
|
||||
public static object GetRouteValue(this HttpContext httpContext, string key)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
var routingFeature = httpContext.Features[typeof(IRoutingFeature)] as IRoutingFeature;
|
||||
return routingFeature?.RouteData.Values[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// A context for virtual path generation operations.
|
||||
/// </summary>
|
||||
public class VirtualPathContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="VirtualPathContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="ambientValues">The set of route values associated with the current request.</param>
|
||||
/// <param name="values">The set of new values provided for virtual path generation.</param>
|
||||
public VirtualPathContext(
|
||||
HttpContext httpContext,
|
||||
RouteValueDictionary ambientValues,
|
||||
RouteValueDictionary values)
|
||||
: this(httpContext, ambientValues, values, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="VirtualPathContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="ambientValues">The set of route values associated with the current request.</param>
|
||||
/// <param name="values">The set of new values provided for virtual path generation.</param>
|
||||
/// <param name="routeName">The name of the route to use for virtual path generation.</param>
|
||||
public VirtualPathContext(
|
||||
HttpContext httpContext,
|
||||
RouteValueDictionary ambientValues,
|
||||
RouteValueDictionary values,
|
||||
string routeName)
|
||||
{
|
||||
HttpContext = httpContext;
|
||||
AmbientValues = ambientValues;
|
||||
Values = values;
|
||||
RouteName = routeName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of route values associated with the current request.
|
||||
/// </summary>
|
||||
public RouteValueDictionary AmbientValues { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Http.HttpContext"/> associated with the current request.
|
||||
/// </summary>
|
||||
public HttpContext HttpContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the route to use for virtual path generation.
|
||||
/// </summary>
|
||||
public string RouteName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the set of new values provided for virtual path generation.
|
||||
/// </summary>
|
||||
public RouteValueDictionary Values { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information about the route and virtual path that are the result of
|
||||
/// generating a URL with the ASP.NET routing middleware.
|
||||
/// </summary>
|
||||
public class VirtualPathData
|
||||
{
|
||||
private RouteValueDictionary _dataTokens;
|
||||
private string _virtualPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VirtualPathData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="router">The object that is used to generate the URL.</param>
|
||||
/// <param name="virtualPath">The generated URL.</param>
|
||||
public VirtualPathData(IRouter router, string virtualPath)
|
||||
: this(router, virtualPath, dataTokens: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VirtualPathData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="router">The object that is used to generate the URL.</param>
|
||||
/// <param name="virtualPath">The generated URL.</param>
|
||||
/// <param name="dataTokens">The collection of custom values.</param>
|
||||
public VirtualPathData(
|
||||
IRouter router,
|
||||
string virtualPath,
|
||||
RouteValueDictionary dataTokens)
|
||||
{
|
||||
if (router == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(router));
|
||||
}
|
||||
|
||||
Router = router;
|
||||
VirtualPath = virtualPath;
|
||||
_dataTokens = dataTokens == null ? null : new RouteValueDictionary(dataTokens);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of custom values for the <see cref="Router"/>.
|
||||
/// </summary>
|
||||
public RouteValueDictionary DataTokens
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_dataTokens == null)
|
||||
{
|
||||
_dataTokens = new RouteValueDictionary();
|
||||
}
|
||||
|
||||
return _dataTokens;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IRouter"/> that was used to generate the URL.
|
||||
/// </summary>
|
||||
public IRouter Router { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL that was generated from the <see cref="Router"/>.
|
||||
/// </summary>
|
||||
public string VirtualPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return _virtualPath;
|
||||
}
|
||||
set
|
||||
{
|
||||
_virtualPath = NormalizePath(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizePath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (!path.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
return "/" + path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,849 @@
|
|||
{
|
||||
"AssemblyIdentity": "Microsoft.AspNetCore.Routing.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||
"Types": [
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.IRouteConstraint",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Match",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
},
|
||||
{
|
||||
"Name": "route",
|
||||
"Type": "Microsoft.AspNetCore.Routing.IRouter"
|
||||
},
|
||||
{
|
||||
"Name": "routeKey",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "values",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
},
|
||||
{
|
||||
"Name": "routeDirection",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteDirection"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Boolean",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.IRouteHandler",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetRequestHandler",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
},
|
||||
{
|
||||
"Name": "routeData",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteData"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.AspNetCore.Http.RequestDelegate",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.IRouter",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "RouteAsync",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "context",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Threading.Tasks.Task",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetVirtualPath",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "context",
|
||||
"Type": "Microsoft.AspNetCore.Routing.VirtualPathContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.VirtualPathData",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.IRoutingFeature",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_RouteData",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteData",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_RouteData",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteData"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.RouteContext",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Handler",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Http.RequestDelegate",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_Handler",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "Microsoft.AspNetCore.Http.RequestDelegate"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_HttpContext",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_RouteData",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteData",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_RouteData",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteData"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.RouteData",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_DataTokens",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Routers",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Collections.Generic.IList<Microsoft.AspNetCore.Routing.IRouter>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Values",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "PushState",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "router",
|
||||
"Type": "Microsoft.AspNetCore.Routing.IRouter"
|
||||
},
|
||||
{
|
||||
"Name": "values",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
},
|
||||
{
|
||||
"Name": "dataTokens",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteData+RouteDataSnapshot",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "other",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteData"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.RouteDirection",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Enumeration",
|
||||
"Sealed": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Field",
|
||||
"Name": "IncomingRequest",
|
||||
"Parameters": [],
|
||||
"GenericParameter": [],
|
||||
"Literal": "0"
|
||||
},
|
||||
{
|
||||
"Kind": "Field",
|
||||
"Name": "UrlGeneration",
|
||||
"Parameters": [],
|
||||
"GenericParameter": [],
|
||||
"Literal": "1"
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.RouteValueDictionary",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [
|
||||
"System.Collections.Generic.IDictionary<System.String, System.Object>",
|
||||
"System.Collections.Generic.IReadOnlyDictionary<System.String, System.Object>"
|
||||
],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Count",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Int32",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String, System.Object>>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Clear",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Void",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String, System.Object>>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "key",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Object",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, System.Object>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "key",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.Object"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, System.Object>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Comparer",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Collections.Generic.IEqualityComparer<System.String>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Keys",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Collections.Generic.ICollection<System.String>",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, System.Object>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Values",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Collections.Generic.ICollection<System.Object>",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, System.Object>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Add",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "key",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.Object"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, System.Object>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "ContainsKey",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "key",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Boolean",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, System.Object>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetEnumerator",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary+Enumerator",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Remove",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "key",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Boolean",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, System.Object>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "TryGetValue",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "key",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.Object",
|
||||
"Direction": "Out"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Boolean",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, System.Object>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "values",
|
||||
"Type": "System.Object"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.RoutingHttpContextExtensions",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"Abstract": true,
|
||||
"Static": true,
|
||||
"Sealed": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetRouteData",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteData",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetRouteValue",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
},
|
||||
{
|
||||
"Name": "key",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Object",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.VirtualPathContext",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_AmbientValues",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_HttpContext",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_RouteName",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Values",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_Values",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
},
|
||||
{
|
||||
"Name": "ambientValues",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
},
|
||||
{
|
||||
"Name": "values",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
},
|
||||
{
|
||||
"Name": "ambientValues",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
},
|
||||
{
|
||||
"Name": "values",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
},
|
||||
{
|
||||
"Name": "routeName",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.VirtualPathData",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_DataTokens",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Router",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Routing.IRouter",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_Router",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "Microsoft.AspNetCore.Routing.IRouter"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_VirtualPath",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_VirtualPath",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "router",
|
||||
"Type": "Microsoft.AspNetCore.Routing.IRouter"
|
||||
},
|
||||
{
|
||||
"Name": "virtualPath",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "router",
|
||||
"Type": "Microsoft.AspNetCore.Routing.IRouter"
|
||||
},
|
||||
{
|
||||
"Name": "virtualPath",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "dataTokens",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.RouteData+RouteDataSnapshot",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Struct",
|
||||
"Sealed": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Restore",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "routeData",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteData"
|
||||
},
|
||||
{
|
||||
"Name": "dataTokens",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
},
|
||||
{
|
||||
"Name": "routers",
|
||||
"Type": "System.Collections.Generic.IList<Microsoft.AspNetCore.Routing.IRouter>"
|
||||
},
|
||||
{
|
||||
"Name": "values",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Routing.RouteValueDictionary+Enumerator",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Struct",
|
||||
"Sealed": true,
|
||||
"ImplementedInterfaces": [
|
||||
"System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.String, System.Object>>"
|
||||
],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Dispose",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Void",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.IDisposable",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "MoveNext",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Boolean",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.IEnumerator",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Reset",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Void",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.IEnumerator",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Current",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Collections.Generic.KeyValuePair<System.String, System.Object>",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.String, System.Object>>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "dictionary",
|
||||
"Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to contain only lowercase or uppercase letters A through Z in the English alphabet.
|
||||
/// </summary>
|
||||
public class AlphaRouteConstraint : RegexRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AlphaRouteConstraint" /> class.
|
||||
/// </summary>
|
||||
public AlphaRouteConstraint() : base(@"^[a-z]*$")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to represent only Boolean values.
|
||||
/// </summary>
|
||||
public class BoolRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
if (value is bool)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool result;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return bool.TryParse(valueString, out result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route by several child constraints.
|
||||
/// </summary>
|
||||
public class CompositeRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CompositeRouteConstraint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="constraints">The child constraints that must match for this constraint to match.</param>
|
||||
public CompositeRouteConstraint(IEnumerable<IRouteConstraint> constraints)
|
||||
{
|
||||
if (constraints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraints));
|
||||
}
|
||||
|
||||
Constraints = constraints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child constraints that must match for this constraint to match.
|
||||
/// </summary>
|
||||
public IEnumerable<IRouteConstraint> Constraints { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
foreach (var constraint in Constraints)
|
||||
{
|
||||
if (!constraint.Match(httpContext, route, routeKey, values, routeDirection))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to represent only <see cref="DateTime"/> values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This constraint tries to parse strings by using all of the formats returned by the
|
||||
/// CultureInfo.InvariantCulture.DateTimeFormat.GetAllDateTimePatterns() method.
|
||||
/// For a sample on how to list all formats which are considered, please visit
|
||||
/// http://msdn.microsoft.com/en-us/library/aszyst2c(v=vs.110).aspx
|
||||
/// </remarks>
|
||||
public class DateTimeRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
if (value is DateTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
DateTime result;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return DateTime.TryParse(valueString, CultureInfo.InvariantCulture, DateTimeStyles.None, out result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to represent only decimal values.
|
||||
/// </summary>
|
||||
public class DecimalRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
if (value is decimal)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
decimal result;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return decimal.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to represent only 64-bit floating-point values.
|
||||
/// </summary>
|
||||
public class DoubleRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
if (value is double)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
double result;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return double.TryParse(
|
||||
valueString,
|
||||
NumberStyles.Float | NumberStyles.AllowThousands,
|
||||
CultureInfo.InvariantCulture,
|
||||
out result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to represent only 32-bit floating-point values.
|
||||
/// </summary>
|
||||
public class FloatRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
if (value is float)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float result;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return float.TryParse(
|
||||
valueString,
|
||||
NumberStyles.Float | NumberStyles.AllowThousands,
|
||||
CultureInfo.InvariantCulture,
|
||||
out result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to represent only <see cref="Guid"/> values.
|
||||
/// Matches values specified in any of the five formats "N", "D", "B", "P", or "X",
|
||||
/// supported by Guid.ToString(string) and Guid.ToString(String, IFormatProvider) methods.
|
||||
/// </summary>
|
||||
public class GuidRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
if (value is Guid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Guid result;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return Guid.TryParse(valueString, out result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains the HTTP method of request or a route.
|
||||
/// </summary>
|
||||
public class HttpMethodRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HttpMethodRouteConstraint"/> that accepts the HTTP methods specified
|
||||
/// by <paramref name="allowedMethods"/>.
|
||||
/// </summary>
|
||||
/// <param name="allowedMethods">The allowed HTTP methods.</param>
|
||||
public HttpMethodRouteConstraint(params string[] allowedMethods)
|
||||
{
|
||||
if (allowedMethods == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(allowedMethods));
|
||||
}
|
||||
|
||||
AllowedMethods = new List<string>(allowedMethods);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP methods allowed by the constraint.
|
||||
/// </summary>
|
||||
public IList<string> AllowedMethods { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
switch (routeDirection)
|
||||
{
|
||||
case RouteDirection.IncomingRequest:
|
||||
return AllowedMethods.Contains(httpContext.Request.Method, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
case RouteDirection.UrlGeneration:
|
||||
// We need to see if the user specified the HTTP method explicitly. Consider these two routes:
|
||||
//
|
||||
// a) Route: template = "/{foo}", Constraints = { httpMethod = new HttpMethodRouteConstraint("GET") }
|
||||
// b) Route: template = "/{foo}", Constraints = { httpMethod = new HttpMethodRouteConstraint("POST") }
|
||||
//
|
||||
// A user might know ahead of time that a URI he/she is generating might be used with a particular HTTP
|
||||
// method. If a URI will be used for an HTTP POST but we match on (a) while generating the URI, then
|
||||
// the HTTP GET-specific route will be used for URI generation, which might have undesired behavior.
|
||||
//
|
||||
// To prevent this, a user might call GetVirtualPath(..., { httpMethod = "POST" }) to
|
||||
// signal that he is generating a URI that will be used for an HTTP POST, so he wants the URI
|
||||
// generation to be performed by the (b) route instead of the (a) route, consistent with what would
|
||||
// happen on incoming requests.
|
||||
object obj;
|
||||
if (!values.TryGetValue(routeKey, out obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return AllowedMethods.Contains(Convert.ToString(obj), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(routeDirection));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to represent only 32-bit integer values.
|
||||
/// </summary>
|
||||
public class IntRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
if (value is int)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int result;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return int.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to be a string of a given length or within a given range of lengths.
|
||||
/// </summary>
|
||||
public class LengthRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LengthRouteConstraint" /> class that constrains
|
||||
/// a route parameter to be a string of a given length.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the route parameter.</param>
|
||||
public LengthRouteConstraint(int length)
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
|
||||
throw new ArgumentOutOfRangeException(nameof(length), length, errorMessage);
|
||||
}
|
||||
|
||||
MinLength = MaxLength = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LengthRouteConstraint" /> class that constrains
|
||||
/// a route parameter to be a string of a given length.
|
||||
/// </summary>
|
||||
/// <param name="minLength">The minimum length allowed for the route parameter.</param>
|
||||
/// <param name="maxLength">The maximum length allowed for the route parameter.</param>
|
||||
public LengthRouteConstraint(int minLength, int maxLength)
|
||||
{
|
||||
if (minLength < 0)
|
||||
{
|
||||
var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
|
||||
throw new ArgumentOutOfRangeException(nameof(minLength), minLength, errorMessage);
|
||||
}
|
||||
|
||||
if (maxLength < 0)
|
||||
{
|
||||
var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
|
||||
throw new ArgumentOutOfRangeException(nameof(maxLength), maxLength, errorMessage);
|
||||
}
|
||||
|
||||
if (minLength > maxLength)
|
||||
{
|
||||
var errorMessage =
|
||||
Resources.FormatRangeConstraint_MinShouldBeLessThanOrEqualToMax("minLength", "maxLength");
|
||||
throw new ArgumentOutOfRangeException(nameof(minLength), minLength, errorMessage);
|
||||
}
|
||||
|
||||
MinLength = minLength;
|
||||
MaxLength = maxLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum length allowed for the route parameter.
|
||||
/// </summary>
|
||||
public int MinLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum length allowed for the route parameter.
|
||||
/// </summary>
|
||||
public int MaxLength { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
var length = valueString.Length;
|
||||
return length >= MinLength && length <= MaxLength;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to represent only 64-bit integer values.
|
||||
/// </summary>
|
||||
public class LongRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
if (value is long)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
long result;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to be a string with a maximum length.
|
||||
/// </summary>
|
||||
public class MaxLengthRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MaxLengthRouteConstraint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="maxLength">The maximum length allowed for the route parameter.</param>
|
||||
public MaxLengthRouteConstraint(int maxLength)
|
||||
{
|
||||
if (maxLength < 0)
|
||||
{
|
||||
var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
|
||||
throw new ArgumentOutOfRangeException(nameof(maxLength), maxLength, errorMessage);
|
||||
}
|
||||
|
||||
MaxLength = maxLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum length allowed for the route parameter.
|
||||
/// </summary>
|
||||
public int MaxLength { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return valueString.Length <= MaxLength;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to be an integer with a maximum value.
|
||||
/// </summary>
|
||||
public class MaxRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MaxRouteConstraint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="max">The maximum value allowed for the route parameter.</param>
|
||||
public MaxRouteConstraint(long max)
|
||||
{
|
||||
Max = max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum allowed value of the route parameter.
|
||||
/// </summary>
|
||||
public long Max { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
long longValue;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue))
|
||||
{
|
||||
return longValue <= Max;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to be a string with a minimum length.
|
||||
/// </summary>
|
||||
public class MinLengthRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MinLengthRouteConstraint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="minLength">The minimum length allowed for the route parameter.</param>
|
||||
public MinLengthRouteConstraint(int minLength)
|
||||
{
|
||||
if (minLength < 0)
|
||||
{
|
||||
var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
|
||||
throw new ArgumentOutOfRangeException(nameof(minLength), minLength, errorMessage);
|
||||
}
|
||||
|
||||
MinLength = minLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum length allowed for the route parameter.
|
||||
/// </summary>
|
||||
public int MinLength { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return valueString.Length >= MinLength;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to be a long with a minimum value.
|
||||
/// </summary>
|
||||
public class MinRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MinRouteConstraint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum value allowed for the route parameter.</param>
|
||||
public MinRouteConstraint(long min)
|
||||
{
|
||||
Min = min;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum allowed value of the route parameter.
|
||||
/// </summary>
|
||||
public long Min { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
long longValue;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue))
|
||||
{
|
||||
return longValue >= Min;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a constraint on an optional parameter. If the parameter is present, then it is constrained by InnerConstraint.
|
||||
/// </summary>
|
||||
public class OptionalRouteConstraint : IRouteConstraint
|
||||
{
|
||||
public OptionalRouteConstraint(IRouteConstraint innerConstraint)
|
||||
{
|
||||
if (innerConstraint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(innerConstraint));
|
||||
}
|
||||
|
||||
InnerConstraint = innerConstraint;
|
||||
}
|
||||
|
||||
public IRouteConstraint InnerConstraint { get; }
|
||||
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value))
|
||||
{
|
||||
return InnerConstraint.Match(httpContext,
|
||||
route,
|
||||
routeKey,
|
||||
values,
|
||||
routeDirection);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constraints a route parameter to be an integer within a given range of values.
|
||||
/// </summary>
|
||||
public class RangeRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RangeRouteConstraint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum value.</param>
|
||||
/// <param name="max">The maximum value.</param>
|
||||
/// <remarks>The minimum value should be less than or equal to the maximum value.</remarks>
|
||||
public RangeRouteConstraint(long min, long max)
|
||||
{
|
||||
if (min > max)
|
||||
{
|
||||
var errorMessage = Resources.FormatRangeConstraint_MinShouldBeLessThanOrEqualToMax("min", "max");
|
||||
throw new ArgumentOutOfRangeException(nameof(min), min, errorMessage);
|
||||
}
|
||||
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum allowed value of the route parameter.
|
||||
/// </summary>
|
||||
public long Min { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum allowed value of the route parameter.
|
||||
/// </summary>
|
||||
public long Max { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
long longValue;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (Int64.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue))
|
||||
{
|
||||
return longValue >= Min && longValue <= Max;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a regex constraint which can be used as an inlineConstraint.
|
||||
/// </summary>
|
||||
public class RegexInlineRouteConstraint : RegexRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegexInlineRouteConstraint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="regexPattern">The regular expression pattern to match.</param>
|
||||
public RegexInlineRouteConstraint(string regexPattern)
|
||||
: base(regexPattern)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// 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.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
public class RegexRouteConstraint : IRouteConstraint
|
||||
{
|
||||
private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
public RegexRouteConstraint(Regex regex)
|
||||
{
|
||||
if (regex == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(regex));
|
||||
}
|
||||
|
||||
Constraint = regex;
|
||||
}
|
||||
|
||||
public RegexRouteConstraint(string regexPattern)
|
||||
{
|
||||
if (regexPattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(regexPattern));
|
||||
}
|
||||
|
||||
Constraint = new Regex(
|
||||
regexPattern,
|
||||
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
|
||||
RegexMatchTimeout);
|
||||
}
|
||||
|
||||
public Regex Constraint { get; private set; }
|
||||
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object routeValue;
|
||||
|
||||
if (values.TryGetValue(routeKey, out routeValue)
|
||||
&& routeValue != null)
|
||||
{
|
||||
var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
|
||||
|
||||
return Constraint.IsMatch(parameterValueString);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constraints a route parameter that must have a value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This constraint is primarily used to enforce that a non-parameter value is present during
|
||||
/// URL generation.
|
||||
/// </remarks>
|
||||
public class RequiredRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
{
|
||||
// In routing the empty string is equivalent to null, which is equivalent to an unset value.
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return !string.IsNullOrEmpty(valueString);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to contain only a specified strign.
|
||||
/// </summary>
|
||||
public class StringRouteConstraint : IRouteConstraint
|
||||
{
|
||||
private string _value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringRouteConstraint"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The constraint value to match.</param>
|
||||
public StringRouteConstraint(string value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_value = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (routeKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object routeValue;
|
||||
|
||||
if (values.TryGetValue(routeKey, out routeValue)
|
||||
&& routeValue != null)
|
||||
{
|
||||
var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
|
||||
|
||||
return parameterValueString.Equals(_value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
// 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.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// The default implementation of <see cref="IInlineConstraintResolver"/>. Resolves constraints by parsing
|
||||
/// a constraint key and constraint arguments, using a map to resolve the constraint type, and calling an
|
||||
/// appropriate constructor for the constraint type.
|
||||
/// </summary>
|
||||
public class DefaultInlineConstraintResolver : IInlineConstraintResolver
|
||||
{
|
||||
private readonly IDictionary<string, Type> _inlineConstraintMap;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultInlineConstraintResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="routeOptions">
|
||||
/// Accessor for <see cref="RouteOptions"/> containing the constraints of interest.
|
||||
/// </param>
|
||||
public DefaultInlineConstraintResolver(IOptions<RouteOptions> routeOptions)
|
||||
{
|
||||
_inlineConstraintMap = routeOptions.Value.ConstraintMap;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <example>
|
||||
/// A typical constraint looks like the following
|
||||
/// "exampleConstraint(arg1, arg2, 12)".
|
||||
/// Here if the type registered for exampleConstraint has a single constructor with one argument,
|
||||
/// The entire string "arg1, arg2, 12" will be treated as a single argument.
|
||||
/// In all other cases arguments are split at comma.
|
||||
/// </example>
|
||||
public virtual IRouteConstraint ResolveConstraint(string inlineConstraint)
|
||||
{
|
||||
if (inlineConstraint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inlineConstraint));
|
||||
}
|
||||
|
||||
string constraintKey;
|
||||
string argumentString;
|
||||
var indexOfFirstOpenParens = inlineConstraint.IndexOf('(');
|
||||
if (indexOfFirstOpenParens >= 0 && inlineConstraint.EndsWith(")", StringComparison.Ordinal))
|
||||
{
|
||||
constraintKey = inlineConstraint.Substring(0, indexOfFirstOpenParens);
|
||||
argumentString = inlineConstraint.Substring(indexOfFirstOpenParens + 1,
|
||||
inlineConstraint.Length - indexOfFirstOpenParens - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
constraintKey = inlineConstraint;
|
||||
argumentString = null;
|
||||
}
|
||||
|
||||
Type constraintType;
|
||||
if (!_inlineConstraintMap.TryGetValue(constraintKey, out constraintType))
|
||||
{
|
||||
// Cannot resolve the constraint key
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!typeof(IRouteConstraint).GetTypeInfo().IsAssignableFrom(constraintType.GetTypeInfo()))
|
||||
{
|
||||
throw new RouteCreationException(
|
||||
Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint(
|
||||
constraintType, constraintKey, typeof(IRouteConstraint).Name));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return CreateConstraint(constraintType, argumentString);
|
||||
}
|
||||
catch (RouteCreationException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new RouteCreationException(
|
||||
$"An error occurred while trying to create an instance of route constraint '{constraintType.FullName}'.",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static IRouteConstraint CreateConstraint(Type constraintType, string argumentString)
|
||||
{
|
||||
// No arguments - call the default constructor
|
||||
if (argumentString == null)
|
||||
{
|
||||
return (IRouteConstraint)Activator.CreateInstance(constraintType);
|
||||
}
|
||||
|
||||
var constraintTypeInfo = constraintType.GetTypeInfo();
|
||||
ConstructorInfo activationConstructor = null;
|
||||
object[] parameters = null;
|
||||
var constructors = constraintTypeInfo.DeclaredConstructors.ToArray();
|
||||
|
||||
// If there is only one constructor and it has a single parameter, pass the argument string directly
|
||||
// This is necessary for the Regex RouteConstraint to ensure that patterns are not split on commas.
|
||||
if (constructors.Length == 1 && constructors[0].GetParameters().Length == 1)
|
||||
{
|
||||
activationConstructor = constructors[0];
|
||||
parameters = ConvertArguments(activationConstructor.GetParameters(), new string[] { argumentString });
|
||||
}
|
||||
else
|
||||
{
|
||||
var arguments = argumentString.Split(',').Select(argument => argument.Trim()).ToArray();
|
||||
|
||||
var matchingConstructors = constructors.Where(ci => ci.GetParameters().Length == arguments.Length)
|
||||
.ToArray();
|
||||
var constructorMatches = matchingConstructors.Length;
|
||||
|
||||
if (constructorMatches == 0)
|
||||
{
|
||||
throw new RouteCreationException(
|
||||
Resources.FormatDefaultInlineConstraintResolver_CouldNotFindCtor(
|
||||
constraintTypeInfo.Name, arguments.Length));
|
||||
}
|
||||
else if (constructorMatches == 1)
|
||||
{
|
||||
activationConstructor = matchingConstructors[0];
|
||||
parameters = ConvertArguments(activationConstructor.GetParameters(), arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RouteCreationException(
|
||||
Resources.FormatDefaultInlineConstraintResolver_AmbiguousCtors(
|
||||
constraintTypeInfo.Name, arguments.Length));
|
||||
}
|
||||
}
|
||||
|
||||
return (IRouteConstraint)activationConstructor.Invoke(parameters);
|
||||
}
|
||||
|
||||
private static object[] ConvertArguments(ParameterInfo[] parameterInfos, string[] arguments)
|
||||
{
|
||||
var parameters = new object[parameterInfos.Length];
|
||||
for (var i = 0; i < parameterInfos.Length; i++)
|
||||
{
|
||||
var parameter = parameterInfos[i];
|
||||
var parameterType = parameter.ParameterType;
|
||||
parameters[i] = Convert.ChangeType(arguments[i], parameterType, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// 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;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods to <see cref="IServiceCollection"/>.
|
||||
/// </summary>
|
||||
public static class RoutingServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds services required for routing requests.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
|
||||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
|
||||
public static IServiceCollection AddRouting(this IServiceCollection services)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.TryAddTransient<IInlineConstraintResolver, DefaultInlineConstraintResolver>();
|
||||
services.TryAddSingleton<ObjectPool<UriBuildingContext>>(s =>
|
||||
{
|
||||
var provider = s.GetRequiredService<ObjectPoolProvider>();
|
||||
return provider.Create<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy());
|
||||
});
|
||||
|
||||
// The TreeRouteBuilder is a builder for creating routes, it should stay transient because it's
|
||||
// stateful.
|
||||
services.TryAdd(ServiceDescriptor.Transient<TreeRouteBuilder>(s =>
|
||||
{
|
||||
var loggerFactory = s.GetRequiredService<ILoggerFactory>();
|
||||
var objectPool = s.GetRequiredService<ObjectPool<UriBuildingContext>>();
|
||||
var constraintResolver = s.GetRequiredService<IInlineConstraintResolver>();
|
||||
return new TreeRouteBuilder(loggerFactory, objectPool, constraintResolver);
|
||||
}));
|
||||
|
||||
services.TryAddSingleton(typeof(RoutingMarkerService));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds services required for routing requests.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
|
||||
/// <param name="configureOptions">The routing options to configure the middleware with.</param>
|
||||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
|
||||
public static IServiceCollection AddRouting(
|
||||
this IServiceCollection services,
|
||||
Action<RouteOptions> configureOptions)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (configureOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configureOptions));
|
||||
}
|
||||
|
||||
services.Configure(configureOptions);
|
||||
services.AddRouting();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an abstraction for resolving inline constraints as instances of <see cref="IRouteConstraint"/>.
|
||||
/// </summary>
|
||||
public interface IInlineConstraintResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the inline constraint.
|
||||
/// </summary>
|
||||
/// <param name="inlineConstraint">The inline constraint to resolve.</param>
|
||||
/// <returns>The <see cref="IRouteConstraint"/> the inline constraint was resolved to.</returns>
|
||||
IRouteConstraint ResolveConstraint(string inlineConstraint);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public interface INamedRouter : IRouter
|
||||
{
|
||||
string Name { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a contract for a route builder in an application. A route builder specifies the routes for
|
||||
/// an application.
|
||||
/// </summary>
|
||||
public interface IRouteBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IApplicationBuilder"/>.
|
||||
/// </summary>
|
||||
IApplicationBuilder ApplicationBuilder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default <see cref="IRouter"/> that is used as a handler if an <see cref="IRouter"/>
|
||||
/// is added to the list of routes but does not specify its own.
|
||||
/// </summary>
|
||||
IRouter DefaultHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sets the <see cref="IServiceProvider"/> used to resolve services for routes.
|
||||
/// </summary>
|
||||
IServiceProvider ServiceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the routes configured in the builder.
|
||||
/// </summary>
|
||||
IList<IRouter> Routes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Builds an <see cref="IRouter"/> that routes the routes specified in the <see cref="Routes"/> property.
|
||||
/// </summary>
|
||||
IRouter Build();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public interface IRouteCollection : IRouter
|
||||
{
|
||||
void Add(IRouter router);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
// 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.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public static class InlineRouteParameterParser
|
||||
{
|
||||
public static TemplatePart ParseRouteParameter(string routeParameter)
|
||||
{
|
||||
if (routeParameter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeParameter));
|
||||
}
|
||||
|
||||
if (routeParameter.Length == 0)
|
||||
{
|
||||
return TemplatePart.CreateParameter(
|
||||
name: string.Empty,
|
||||
isCatchAll: false,
|
||||
isOptional: false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null);
|
||||
}
|
||||
|
||||
var startIndex = 0;
|
||||
var endIndex = routeParameter.Length - 1;
|
||||
|
||||
var isCatchAll = false;
|
||||
var isOptional = false;
|
||||
|
||||
if (routeParameter[0] == '*')
|
||||
{
|
||||
isCatchAll = true;
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (routeParameter[endIndex] == '?')
|
||||
{
|
||||
isOptional = true;
|
||||
endIndex--;
|
||||
}
|
||||
|
||||
var currentIndex = startIndex;
|
||||
|
||||
// Parse parameter name
|
||||
var parameterName = string.Empty;
|
||||
|
||||
while (currentIndex <= endIndex)
|
||||
{
|
||||
var currentChar = routeParameter[currentIndex];
|
||||
|
||||
if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
|
||||
{
|
||||
// Parameter names are allowed to start with delimiters used to denote constraints or default values.
|
||||
// i.e. "=foo" or ":bar" would be treated as parameter names rather than default value or constraint
|
||||
// specifications.
|
||||
parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
|
||||
// Roll the index back and move to the constraint parsing stage.
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
else if (currentIndex == endIndex)
|
||||
{
|
||||
parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
var parseResults = ParseConstraints(routeParameter, currentIndex, endIndex);
|
||||
currentIndex = parseResults.CurrentIndex;
|
||||
|
||||
string defaultValue = null;
|
||||
if (currentIndex <= endIndex &&
|
||||
routeParameter[currentIndex] == '=')
|
||||
{
|
||||
defaultValue = routeParameter.Substring(currentIndex + 1, endIndex - currentIndex);
|
||||
}
|
||||
|
||||
return TemplatePart.CreateParameter(parameterName,
|
||||
isCatchAll,
|
||||
isOptional,
|
||||
defaultValue,
|
||||
parseResults.Constraints);
|
||||
}
|
||||
|
||||
private static ConstraintParseResults ParseConstraints(
|
||||
string routeParameter,
|
||||
int currentIndex,
|
||||
int endIndex)
|
||||
{
|
||||
var inlineConstraints = new List<InlineConstraint>();
|
||||
var state = ParseState.Start;
|
||||
var startIndex = currentIndex;
|
||||
do
|
||||
{
|
||||
var currentChar = currentIndex > endIndex ? null : (char?)routeParameter[currentIndex];
|
||||
switch (state)
|
||||
{
|
||||
case ParseState.Start:
|
||||
switch (currentChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
break;
|
||||
case ':':
|
||||
state = ParseState.ParsingName;
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '(':
|
||||
state = ParseState.InsideParenthesis;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ParseState.InsideParenthesis:
|
||||
switch (currentChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
var constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
break;
|
||||
case ')':
|
||||
// Only consume a ')' token if
|
||||
// (a) it is the last token
|
||||
// (b) the next character is the start of the new constraint ':'
|
||||
// (c) the next character is the start of the default value.
|
||||
|
||||
var nextChar = currentIndex + 1 > endIndex ? null : (char?)routeParameter[currentIndex + 1];
|
||||
switch (nextChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
break;
|
||||
case ':':
|
||||
state = ParseState.Start;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
case '=':
|
||||
// In the original implementation, the Regex would've backtracked if it encountered an
|
||||
// unbalanced opening bracket followed by (not necessarily immediatiely) a delimiter.
|
||||
// Simply verifying that the parantheses will eventually be closed should suffice to
|
||||
// determine if the terminator needs to be consumed as part of the current constraint
|
||||
// specification.
|
||||
var indexOfClosingParantheses = routeParameter.IndexOf(')', currentIndex + 1);
|
||||
if (indexOfClosingParantheses == -1)
|
||||
{
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
|
||||
if (currentChar == ':')
|
||||
{
|
||||
state = ParseState.ParsingName;
|
||||
startIndex = currentIndex + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = ParseState.End;
|
||||
currentIndex--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentIndex = indexOfClosingParantheses;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ParseState.ParsingName:
|
||||
switch (currentChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
var constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
break;
|
||||
case ':':
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '(':
|
||||
state = ParseState.InsideParenthesis;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
|
||||
} while (state != ParseState.End);
|
||||
|
||||
return new ConstraintParseResults
|
||||
{
|
||||
CurrentIndex = currentIndex,
|
||||
Constraints = inlineConstraints
|
||||
};
|
||||
}
|
||||
|
||||
private enum ParseState
|
||||
{
|
||||
Start,
|
||||
ParsingName,
|
||||
InsideParenthesis,
|
||||
End
|
||||
}
|
||||
|
||||
private struct ConstraintParseResults
|
||||
{
|
||||
public int CurrentIndex;
|
||||
|
||||
public IEnumerable<InlineConstraint> Constraints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
public struct BufferValue
|
||||
{
|
||||
public BufferValue(string value, bool requiresEncoding)
|
||||
{
|
||||
Value = value;
|
||||
RequiresEncoding = requiresEncoding;
|
||||
}
|
||||
|
||||
public bool RequiresEncoding { get; }
|
||||
|
||||
public string Value { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
// 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.Routing.DecisionTree;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
// A decision tree that matches link generation entries based on route data.
|
||||
public class LinkGenerationDecisionTree
|
||||
{
|
||||
private readonly DecisionTreeNode<OutboundMatch> _root;
|
||||
|
||||
public LinkGenerationDecisionTree(IReadOnlyList<OutboundMatch> entries)
|
||||
{
|
||||
_root = DecisionTreeBuilder<OutboundMatch>.GenerateTree(
|
||||
entries,
|
||||
new OutboundMatchClassifier());
|
||||
}
|
||||
|
||||
public IList<OutboundMatchResult> GetMatches(VirtualPathContext context)
|
||||
{
|
||||
// Perf: Avoid allocation for List if there aren't any Matches or Criteria
|
||||
if (_root.Matches.Count > 0 || _root.Criteria.Count > 0)
|
||||
{
|
||||
var results = new List<OutboundMatchResult>();
|
||||
Walk(results, context, _root, isFallbackPath: false);
|
||||
results.Sort(OutboundMatchResultComparer.Instance);
|
||||
return results;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// We need to recursively walk the decision tree based on the provided route data
|
||||
// (context.Values + context.AmbientValues) to find all entries that match. This process is
|
||||
// virtually identical to action selection.
|
||||
//
|
||||
// Each entry has a collection of 'required link values' that must be satisfied. These are
|
||||
// key-value pairs that make up the decision tree.
|
||||
//
|
||||
// A 'require link value' is considered satisfied IF:
|
||||
// 1. The value in context.Values matches the required value OR
|
||||
// 2. There is no value in context.Values and the value in context.AmbientValues matches OR
|
||||
// 3. The required value is 'null' and there is no value in context.Values.
|
||||
//
|
||||
// Ex:
|
||||
// entry requires { area = null, controller = Store, action = Buy }
|
||||
// context.Values = { controller = Store, action = Buy }
|
||||
// context.AmbientValues = { area = Help, controller = AboutStore, action = HowToBuyThings }
|
||||
//
|
||||
// In this case the entry is a match. The 'controller' and 'action' are both supplied by context.Values,
|
||||
// and the 'area' is satisfied because there's NOT a value in context.Values. It's OK to ignore ambient
|
||||
// values in link generation.
|
||||
//
|
||||
// If another entry existed like { area = Help, controller = Store, action = Buy }, this would also
|
||||
// match.
|
||||
//
|
||||
// The decision tree uses a tree data structure to execute these rules across all candidates at once.
|
||||
private void Walk(
|
||||
List<OutboundMatchResult> results,
|
||||
VirtualPathContext context,
|
||||
DecisionTreeNode<OutboundMatch> node,
|
||||
bool isFallbackPath)
|
||||
{
|
||||
// Any entries in node.Matches have had all their required values satisfied, so add them
|
||||
// to the results.
|
||||
for (var i = 0; i < node.Matches.Count; i++)
|
||||
{
|
||||
results.Add(new OutboundMatchResult(node.Matches[i], isFallbackPath));
|
||||
}
|
||||
|
||||
for (var i = 0; i < node.Criteria.Count; i++)
|
||||
{
|
||||
var criterion = node.Criteria[i];
|
||||
var key = criterion.Key;
|
||||
|
||||
object value;
|
||||
if (context.Values.TryGetValue(key, out value))
|
||||
{
|
||||
DecisionTreeNode<OutboundMatch> branch;
|
||||
if (criterion.Branches.TryGetValue(value ?? string.Empty, out branch))
|
||||
{
|
||||
Walk(results, context, branch, isFallbackPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If a value wasn't explicitly supplied, match BOTH the ambient value and the empty value
|
||||
// if an ambient value was supplied. The path explored with the empty value is considered
|
||||
// the fallback path.
|
||||
DecisionTreeNode<OutboundMatch> branch;
|
||||
if (context.AmbientValues.TryGetValue(key, out value) &&
|
||||
!criterion.Branches.Comparer.Equals(value, string.Empty))
|
||||
{
|
||||
if (criterion.Branches.TryGetValue(value, out branch))
|
||||
{
|
||||
Walk(results, context, branch, isFallbackPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (criterion.Branches.TryGetValue(string.Empty, out branch))
|
||||
{
|
||||
Walk(results, context, branch, isFallbackPath: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OutboundMatchClassifier : IClassifier<OutboundMatch>
|
||||
{
|
||||
public OutboundMatchClassifier()
|
||||
{
|
||||
ValueComparer = new RouteValueEqualityComparer();
|
||||
}
|
||||
|
||||
public IEqualityComparer<object> ValueComparer { get; private set; }
|
||||
|
||||
public IDictionary<string, DecisionCriterionValue> GetCriteria(OutboundMatch item)
|
||||
{
|
||||
var results = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kvp in item.Entry.RequiredLinkValues)
|
||||
{
|
||||
results.Add(kvp.Key, new DecisionCriterionValue(kvp.Value ?? string.Empty));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
private class OutboundMatchResultComparer : IComparer<OutboundMatchResult>
|
||||
{
|
||||
public static readonly OutboundMatchResultComparer Instance = new OutboundMatchResultComparer();
|
||||
|
||||
public int Compare(OutboundMatchResult x, OutboundMatchResult y)
|
||||
{
|
||||
// For this comparison lower is better.
|
||||
if (x.Match.Entry.Order != y.Match.Entry.Order)
|
||||
{
|
||||
return x.Match.Entry.Order.CompareTo(y.Match.Entry.Order);
|
||||
}
|
||||
|
||||
if (x.Match.Entry.Precedence != y.Match.Entry.Precedence)
|
||||
{
|
||||
// Reversed because higher is better
|
||||
return y.Match.Entry.Precedence.CompareTo(x.Match.Entry.Precedence);
|
||||
}
|
||||
|
||||
if (x.IsFallbackMatch != y.IsFallbackMatch)
|
||||
{
|
||||
// A fallback match is worse than a non-fallback
|
||||
return x.IsFallbackMatch.CompareTo(y.IsFallbackMatch);
|
||||
}
|
||||
|
||||
return StringComparer.Ordinal.Compare(
|
||||
x.Match.Entry.RouteTemplate.TemplateText,
|
||||
y.Match.Entry.RouteTemplate.TemplateText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.Tree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
public struct OutboundMatchResult
|
||||
{
|
||||
public OutboundMatchResult(OutboundMatch match, bool isFallbackMatch)
|
||||
{
|
||||
Match = match;
|
||||
IsFallbackMatch = isFallbackMatch;
|
||||
}
|
||||
|
||||
public OutboundMatch Match { get; }
|
||||
|
||||
public bool IsFallbackMatch { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
// 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.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
public struct PathTokenizer : IReadOnlyList<StringSegment>
|
||||
{
|
||||
private readonly string _path;
|
||||
private int _count;
|
||||
|
||||
public PathTokenizer(PathString path)
|
||||
{
|
||||
_path = path.Value;
|
||||
_count = -1;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_count == -1)
|
||||
{
|
||||
// We haven't computed the real count of segments yet.
|
||||
if (_path.Length == 0)
|
||||
{
|
||||
// The empty string has length of 0.
|
||||
_count = 0;
|
||||
return _count;
|
||||
}
|
||||
|
||||
// A string of length 1 must be "/" - all PathStrings start with '/'
|
||||
if (_path.Length == 1)
|
||||
{
|
||||
// We treat this as empty - there's nothing to parse here for routing, because routing ignores
|
||||
// a trailing slash.
|
||||
Debug.Assert(_path[0] == '/');
|
||||
_count = 0;
|
||||
return _count;
|
||||
}
|
||||
|
||||
// This is a non-trival PathString
|
||||
_count = 1;
|
||||
|
||||
// Since a non-empty PathString must begin with a `/`, we can just count the number of occurrences
|
||||
// of `/` to find the number of segments. However, we don't look at the last character, because
|
||||
// routing ignores a trailing slash.
|
||||
for (var i = 1; i < _path.Length - 1; i++)
|
||||
{
|
||||
if (_path[i] == '/')
|
||||
{
|
||||
_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _count;
|
||||
}
|
||||
}
|
||||
|
||||
public StringSegment this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index >= Count)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
|
||||
var currentSegmentIndex = 0;
|
||||
var currentSegmentStart = 1;
|
||||
|
||||
// Skip the first `/`.
|
||||
var delimiterIndex = 1;
|
||||
while ((delimiterIndex = _path.IndexOf('/', delimiterIndex)) != -1)
|
||||
{
|
||||
if (currentSegmentIndex++ == index)
|
||||
{
|
||||
return new StringSegment(_path, currentSegmentStart, delimiterIndex - currentSegmentStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSegmentStart = delimiterIndex + 1;
|
||||
delimiterIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here we're at the end of the string. The implementation of .Count should protect us
|
||||
// from these cases.
|
||||
Debug.Assert(_path[_path.Length - 1] != '/');
|
||||
Debug.Assert(currentSegmentIndex == index);
|
||||
|
||||
return new StringSegment(_path, currentSegmentStart, _path.Length - currentSegmentStart);
|
||||
}
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator<StringSegment> IEnumerable<StringSegment>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<StringSegment>
|
||||
{
|
||||
private readonly string _path;
|
||||
|
||||
private int _index;
|
||||
private int _length;
|
||||
|
||||
public Enumerator(PathTokenizer tokenizer)
|
||||
{
|
||||
_path = tokenizer._path;
|
||||
|
||||
_index = -1;
|
||||
_length = -1;
|
||||
}
|
||||
|
||||
public StringSegment Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return new StringSegment(_path, _index, _length);
|
||||
}
|
||||
}
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_path == null || _path.Length <= 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_index == -1)
|
||||
{
|
||||
// Skip the first `/`.
|
||||
_index = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip to the end of the previous segment + the separator.
|
||||
_index += _length + 1;
|
||||
}
|
||||
|
||||
if (_index >= _path.Length)
|
||||
{
|
||||
// We're at the end
|
||||
return false;
|
||||
}
|
||||
|
||||
var delimiterIndex = _path.IndexOf('/', _index);
|
||||
if (delimiterIndex != -1)
|
||||
{
|
||||
_length = delimiterIndex - _index;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We might have some trailing text after the last separator.
|
||||
if (_path[_path.Length - 1] == '/')
|
||||
{
|
||||
// If the last char is a '/' then it's just a trailing slash, we don't have another segment.
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_length = _path.Length - _index;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = -1;
|
||||
_length = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A marker class used to determine if all the routing services were added
|
||||
/// to the <see cref="IServiceCollection"/> before routing is configured.
|
||||
/// </summary>
|
||||
public class RoutingMarkerService
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
// Segments are treated as all-or-none. We should never output a partial segment.
|
||||
// If we add any subsegment of this segment to the generated URI, we have to add
|
||||
// the complete match. For example, if the subsegment is "{p1}-{p2}.xml" and we
|
||||
// used a value for {p1}, we have to output the entire segment up to the next "/".
|
||||
// Otherwise we could end up with the partial segment "v1" instead of the entire
|
||||
// segment "v1-v2.xml".
|
||||
public enum SegmentState
|
||||
{
|
||||
Beginning,
|
||||
Inside,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// 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.Text.Encodings.Web;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
public class UriBuilderContextPooledObjectPolicy : IPooledObjectPolicy<UriBuildingContext>
|
||||
{
|
||||
public UriBuildingContext Create()
|
||||
{
|
||||
return new UriBuildingContext(UrlEncoder.Default);
|
||||
}
|
||||
|
||||
public bool Return(UriBuildingContext obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
// 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 System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Internal
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
public class UriBuildingContext
|
||||
{
|
||||
// Holds the 'accepted' parts of the uri.
|
||||
private readonly StringBuilder _uri;
|
||||
|
||||
// Holds the 'optional' parts of the uri. We need a secondary buffer to handle cases where an optional
|
||||
// segment is in the middle of the uri. We don't know if we need to write it out - if it's
|
||||
// followed by other optional segments than we will just throw it away.
|
||||
private readonly List<BufferValue> _buffer;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
|
||||
private bool _hasEmptySegment;
|
||||
private int _lastValueOffset;
|
||||
|
||||
public UriBuildingContext(UrlEncoder urlEncoder)
|
||||
{
|
||||
_urlEncoder = urlEncoder;
|
||||
_uri = new StringBuilder();
|
||||
_buffer = new List<BufferValue>();
|
||||
Writer = new StringWriter(_uri);
|
||||
_lastValueOffset = -1;
|
||||
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
public SegmentState BufferState { get; private set; }
|
||||
|
||||
public SegmentState UriState { get; private set; }
|
||||
|
||||
public TextWriter Writer { get; }
|
||||
|
||||
public bool Accept(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (UriState == SegmentState.Inside || BufferState == SegmentState.Inside)
|
||||
{
|
||||
// We can't write an 'empty' part inside a segment
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasEmptySegment = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (_hasEmptySegment)
|
||||
{
|
||||
// We're trying to write text after an empty segment - this is not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _buffer.Count; i++)
|
||||
{
|
||||
if (_buffer[i].RequiresEncoding)
|
||||
{
|
||||
_urlEncoder.Encode(Writer, _buffer[i].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_uri.Append(_buffer[i].Value);
|
||||
}
|
||||
}
|
||||
_buffer.Clear();
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0)
|
||||
{
|
||||
_uri.Append("/");
|
||||
}
|
||||
}
|
||||
|
||||
BufferState = SegmentState.Inside;
|
||||
UriState = SegmentState.Inside;
|
||||
|
||||
_lastValueOffset = _uri.Length;
|
||||
// Allow the first segment to have a leading slash.
|
||||
// This prevents the leading slash from PathString segments from being encoded.
|
||||
if (_uri.Length == 0 && value.Length > 0 && value[0] == '/')
|
||||
{
|
||||
_uri.Append("/");
|
||||
_urlEncoder.Encode(Writer, value, 1, value.Length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_urlEncoder.Encode(Writer, value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Remove(string literal)
|
||||
{
|
||||
Debug.Assert(_lastValueOffset != -1, "Cannot invoke Remove more than once.");
|
||||
_uri.Length = _lastValueOffset;
|
||||
_lastValueOffset = -1;
|
||||
}
|
||||
|
||||
public bool Buffer(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (BufferState == SegmentState.Inside)
|
||||
{
|
||||
// We can't write an 'empty' part inside a segment
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasEmptySegment = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (_hasEmptySegment)
|
||||
{
|
||||
// We're trying to write text after an empty segment - this is not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UriState == SegmentState.Inside)
|
||||
{
|
||||
// We've already written part of this segment so there's no point in buffering, we need to
|
||||
// write out the rest or give up.
|
||||
var result = Accept(value);
|
||||
|
||||
// We've already checked the conditions that could result in a rejected part, so this should
|
||||
// always be true.
|
||||
Debug.Assert(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0 || _buffer.Count != 0)
|
||||
{
|
||||
_buffer.Add(new BufferValue("/", requiresEncoding: false));
|
||||
}
|
||||
|
||||
BufferState = SegmentState.Inside;
|
||||
}
|
||||
|
||||
_buffer.Add(new BufferValue(value, requiresEncoding: true));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void EndSegment()
|
||||
{
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_uri.Clear();
|
||||
if (_uri.Capacity > 128)
|
||||
{
|
||||
// We don't want to retain too much memory if this is getting pooled.
|
||||
_uri.Capacity = 128;
|
||||
}
|
||||
|
||||
_buffer.Clear();
|
||||
if (_buffer.Capacity > 8)
|
||||
{
|
||||
_buffer.Capacity = 8;
|
||||
}
|
||||
|
||||
_hasEmptySegment = false;
|
||||
_lastValueOffset = -1;
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// We can ignore any currently buffered segments - they are are guaranteed to be 'defaults'.
|
||||
if (_uri.Length > 0 && _uri[0] != '/')
|
||||
{
|
||||
// Normalize generated paths so that they always contain a leading slash.
|
||||
_uri.Insert(0, '/');
|
||||
}
|
||||
|
||||
return _uri.ToString();
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format("{{Accepted: '{0}' Buffered: '{1}'}}", _uri, string.Join("", _buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Logging
|
||||
{
|
||||
internal static class RouteConstraintMatcherExtensions
|
||||
{
|
||||
private static readonly Action<ILogger, object, string, IRouteConstraint, Exception> _routeValueDoesNotMatchConstraint;
|
||||
|
||||
static RouteConstraintMatcherExtensions()
|
||||
{
|
||||
_routeValueDoesNotMatchConstraint = LoggerMessage.Define<object, string, IRouteConstraint>(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
"Route value '{RouteValue}' with key '{RouteKey}' did not match " +
|
||||
"the constraint '{RouteConstraint}'.");
|
||||
}
|
||||
|
||||
public static void RouteValueDoesNotMatchConstraint(
|
||||
this ILogger logger,
|
||||
object routeValue,
|
||||
string routeKey,
|
||||
IRouteConstraint routeConstraint)
|
||||
{
|
||||
_routeValueDoesNotMatchConstraint(logger, routeValue, routeKey, routeConstraint, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Logging
|
||||
{
|
||||
internal static class RouterMiddlewareLoggerExtensions
|
||||
{
|
||||
private static readonly Action<ILogger, Exception> _requestDidNotMatchRoutes;
|
||||
|
||||
static RouterMiddlewareLoggerExtensions()
|
||||
{
|
||||
_requestDidNotMatchRoutes = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
"Request did not match any routes.");
|
||||
}
|
||||
|
||||
public static void RequestDidNotMatchRoutes(this ILogger logger)
|
||||
{
|
||||
_requestDidNotMatchRoutes(logger, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// 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.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Logging
|
||||
{
|
||||
internal static class TreeRouterLoggerExtensions
|
||||
{
|
||||
private static readonly Action<ILogger, string, string, Exception> _matchedRoute;
|
||||
|
||||
static TreeRouterLoggerExtensions()
|
||||
{
|
||||
_matchedRoute = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
"Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.");
|
||||
}
|
||||
|
||||
public static void MatchedRoute(
|
||||
this ILogger logger,
|
||||
string routeName,
|
||||
string routeTemplate)
|
||||
{
|
||||
_matchedRoute(logger, routeName, routeTemplate, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
// 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;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="IRouteBuilder"/> to add routes.
|
||||
/// </summary>
|
||||
public static class MapRouteRouteBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> with the specified name and template.
|
||||
/// </summary>
|
||||
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
|
||||
/// <param name="name">The name of the route.</param>
|
||||
/// <param name="template">The URL pattern of the route.</param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static IRouteBuilder MapRoute(
|
||||
this IRouteBuilder routeBuilder,
|
||||
string name,
|
||||
string template)
|
||||
{
|
||||
MapRoute(routeBuilder, name, template, defaults: null);
|
||||
return routeBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, and default values.
|
||||
/// </summary>
|
||||
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
|
||||
/// <param name="name">The name of the route.</param>
|
||||
/// <param name="template">The URL pattern of the route.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for route parameters. The object's properties represent the names
|
||||
/// and values of the default values.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static IRouteBuilder MapRoute(
|
||||
this IRouteBuilder routeBuilder,
|
||||
string name,
|
||||
string template,
|
||||
object defaults)
|
||||
{
|
||||
return MapRoute(routeBuilder, name, template, defaults, constraints: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and
|
||||
/// constraints.
|
||||
/// </summary>
|
||||
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
|
||||
/// <param name="name">The name of the route.</param>
|
||||
/// <param name="template">The URL pattern of the route.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for route parameters. The object's properties represent the names
|
||||
/// and values of the default values.
|
||||
/// </param>
|
||||
/// <param name="constraints">
|
||||
/// An object that contains constraints for the route. The object's properties represent the names and values
|
||||
/// of the constraints.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static IRouteBuilder MapRoute(
|
||||
this IRouteBuilder routeBuilder,
|
||||
string name,
|
||||
string template,
|
||||
object defaults,
|
||||
object constraints)
|
||||
{
|
||||
return MapRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and
|
||||
/// data tokens.
|
||||
/// </summary>
|
||||
/// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
|
||||
/// <param name="name">The name of the route.</param>
|
||||
/// <param name="template">The URL pattern of the route.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for route parameters. The object's properties represent the names
|
||||
/// and values of the default values.
|
||||
/// </param>
|
||||
/// <param name="constraints">
|
||||
/// An object that contains constraints for the route. The object's properties represent the names and values
|
||||
/// of the constraints.
|
||||
/// </param>
|
||||
/// <param name="dataTokens">
|
||||
/// An object that contains data tokens for the route. The object's properties represent the names and values
|
||||
/// of the data tokens.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static IRouteBuilder MapRoute(
|
||||
this IRouteBuilder routeBuilder,
|
||||
string name,
|
||||
string template,
|
||||
object defaults,
|
||||
object constraints,
|
||||
object dataTokens)
|
||||
{
|
||||
if (routeBuilder.DefaultHandler == null)
|
||||
{
|
||||
throw new RouteCreationException(Resources.FormatDefaultHandler_MustBeSet(nameof(IRouteBuilder)));
|
||||
}
|
||||
|
||||
var inlineConstraintResolver = routeBuilder
|
||||
.ServiceProvider
|
||||
.GetRequiredService<IInlineConstraintResolver>();
|
||||
|
||||
routeBuilder.Routes.Add(new Route(
|
||||
routeBuilder.DefaultHandler,
|
||||
name,
|
||||
template,
|
||||
new RouteValueDictionary(defaults),
|
||||
new RouteValueDictionary(constraints),
|
||||
new RouteValueDictionary(dataTokens),
|
||||
inlineConstraintResolver));
|
||||
|
||||
return routeBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core middleware for routing requests to application logic and for generating links.
|
||||
Commonly used types:
|
||||
Microsoft.AspNetCore.Routing.Route
|
||||
Microsoft.AspNetCore.Routing.RouteCollection</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;routing</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\shared\Microsoft.AspNetCore.Routing.DecisionTree.Sources\**\*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Routing.Abstractions\Microsoft.AspNetCore.Routing.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.HashCodeCombiner.Sources" Version="$(MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion)" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="$(MicrosoftExtensionsObjectPoolPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" Version="$(MicrosoftExtensionsPropertyHelperSourcesPackageVersion)" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Routing.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
422
src/Routing/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs
generated
Normal file
422
src/Routing/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
// <auto-generated />
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNetCore.Routing.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Value must be greater than or equal to {0}.
|
||||
/// </summary>
|
||||
internal static string ArgumentMustBeGreaterThanOrEqualTo
|
||||
{
|
||||
get => GetString("ArgumentMustBeGreaterThanOrEqualTo");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value must be greater than or equal to {0}.
|
||||
/// </summary>
|
||||
internal static string FormatArgumentMustBeGreaterThanOrEqualTo(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeGreaterThanOrEqualTo"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The value for argument '{0}' should be less than or equal to the value for the argument '{1}'.
|
||||
/// </summary>
|
||||
internal static string RangeConstraint_MinShouldBeLessThanOrEqualToMax
|
||||
{
|
||||
get => GetString("RangeConstraint_MinShouldBeLessThanOrEqualToMax");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value for argument '{0}' should be less than or equal to the value for the argument '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatRangeConstraint_MinShouldBeLessThanOrEqualToMax(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("RangeConstraint_MinShouldBeLessThanOrEqualToMax"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' property of '{1}' must not be null.
|
||||
/// </summary>
|
||||
internal static string PropertyOfTypeCannotBeNull
|
||||
{
|
||||
get => GetString("PropertyOfTypeCannotBeNull");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' property of '{1}' must not be null.
|
||||
/// </summary>
|
||||
internal static string FormatPropertyOfTypeCannotBeNull(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("PropertyOfTypeCannotBeNull"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The supplied route name '{0}' is ambiguous and matched more than one route.
|
||||
/// </summary>
|
||||
internal static string NamedRoutes_AmbiguousRoutesFound
|
||||
{
|
||||
get => GetString("NamedRoutes_AmbiguousRoutesFound");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The supplied route name '{0}' is ambiguous and matched more than one route.
|
||||
/// </summary>
|
||||
internal static string FormatNamedRoutes_AmbiguousRoutesFound(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("NamedRoutes_AmbiguousRoutesFound"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// A default handler must be set on the {0}.
|
||||
/// </summary>
|
||||
internal static string DefaultHandler_MustBeSet
|
||||
{
|
||||
get => GetString("DefaultHandler_MustBeSet");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A default handler must be set on the {0}.
|
||||
/// </summary>
|
||||
internal static string FormatDefaultHandler_MustBeSet(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DefaultHandler_MustBeSet"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.
|
||||
/// </summary>
|
||||
internal static string DefaultInlineConstraintResolver_AmbiguousCtors
|
||||
{
|
||||
get => GetString("DefaultInlineConstraintResolver_AmbiguousCtors");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.
|
||||
/// </summary>
|
||||
internal static string FormatDefaultInlineConstraintResolver_AmbiguousCtors(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_AmbiguousCtors"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.
|
||||
/// </summary>
|
||||
internal static string DefaultInlineConstraintResolver_CouldNotFindCtor
|
||||
{
|
||||
get => GetString("DefaultInlineConstraintResolver_CouldNotFindCtor");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.
|
||||
/// </summary>
|
||||
internal static string FormatDefaultInlineConstraintResolver_CouldNotFindCtor(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_CouldNotFindCtor"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.
|
||||
/// </summary>
|
||||
internal static string DefaultInlineConstraintResolver_TypeNotConstraint
|
||||
{
|
||||
get => GetString("DefaultInlineConstraintResolver_TypeNotConstraint");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.
|
||||
/// </summary>
|
||||
internal static string FormatDefaultInlineConstraintResolver_TypeNotConstraint(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_TypeNotConstraint"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveCatchAllInMultiSegment
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveCatchAllInMultiSegment()
|
||||
=> GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveConsecutiveParameters
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveConsecutiveParameters");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveConsecutiveParameters()
|
||||
=> GetString("TemplateRoute_CannotHaveConsecutiveParameters");
|
||||
|
||||
/// <summary>
|
||||
/// The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveConsecutiveSeparators
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveConsecutiveSeparators");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveConsecutiveSeparators()
|
||||
=> GetString("TemplateRoute_CannotHaveConsecutiveSeparators");
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter cannot be marked optional.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CatchAllCannotBeOptional
|
||||
{
|
||||
get => GetString("TemplateRoute_CatchAllCannotBeOptional");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter cannot be marked optional.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CatchAllCannotBeOptional()
|
||||
=> GetString("TemplateRoute_CatchAllCannotBeOptional");
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter cannot have default value.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalCannotHaveDefaultValue
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter cannot have default value.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalCannotHaveDefaultValue()
|
||||
=> GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter can only appear as the last segment of the route template.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CatchAllMustBeLast
|
||||
{
|
||||
get => GetString("TemplateRoute_CatchAllMustBeLast");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter can only appear as the last segment of the route template.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CatchAllMustBeLast()
|
||||
=> GetString("TemplateRoute_CatchAllMustBeLast");
|
||||
|
||||
/// <summary>
|
||||
/// The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidLiteral
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidLiteral");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidLiteral(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidLiteral"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidParameterName
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidParameterName");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidParameterName(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidParameterName"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The route template cannot start with a '~' character unless followed by a '/'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidRouteTemplate
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidRouteTemplate");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route template cannot start with a '~' character unless followed by a '/'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidRouteTemplate()
|
||||
=> GetString("TemplateRoute_InvalidRouteTemplate");
|
||||
|
||||
/// <summary>
|
||||
/// There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_MismatchedParameter
|
||||
{
|
||||
get => GetString("TemplateRoute_MismatchedParameter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_MismatchedParameter()
|
||||
=> GetString("TemplateRoute_MismatchedParameter");
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' appears more than one time in the route template.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_RepeatedParameter
|
||||
{
|
||||
get => GetString("TemplateRoute_RepeatedParameter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' appears more than one time in the route template.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_RepeatedParameter(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_RepeatedParameter"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint
|
||||
{
|
||||
get => GetString("RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(object p0, object p1, object p2, object p3)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint"), p0, p1, p2, p3);
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// </summary>
|
||||
internal static string RouteConstraintBuilder_CouldNotResolveConstraint
|
||||
{
|
||||
get => GetString("RouteConstraintBuilder_CouldNotResolveConstraint");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatRouteConstraintBuilder_CouldNotResolveConstraint(object p0, object p1, object p2, object p3)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("RouteConstraintBuilder_CouldNotResolveConstraint"), p0, p1, p2, p3);
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_UnescapedBrace
|
||||
{
|
||||
get => GetString("TemplateRoute_UnescapedBrace");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_UnescapedBrace()
|
||||
=> GetString("TemplateRoute_UnescapedBrace");
|
||||
|
||||
/// <summary>
|
||||
/// In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalParameterCanbBePrecededByPeriod
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalParameterCanbBePrecededByPeriod");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_OptionalParameterCanbBePrecededByPeriod"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalParameterHasTobeTheLast
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalParameterHasTobeTheLast");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalParameterHasTobeTheLast(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_OptionalParameterHasTobeTheLast"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// Two or more routes named '{0}' have different templates.
|
||||
/// </summary>
|
||||
internal static string AttributeRoute_DifferentLinkGenerationEntries_SameName
|
||||
{
|
||||
get => GetString("AttributeRoute_DifferentLinkGenerationEntries_SameName");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Two or more routes named '{0}' have different templates.
|
||||
/// </summary>
|
||||
internal static string FormatAttributeRoute_DifferentLinkGenerationEntries_SameName(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_DifferentLinkGenerationEntries_SameName"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.
|
||||
/// </summary>
|
||||
internal static string UnableToFindServices
|
||||
{
|
||||
get => GetString("UnableToFindServices");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.
|
||||
/// </summary>
|
||||
internal static string FormatUnableToFindServices(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred while creating the route with name '{0}' and template '{1}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_Exception
|
||||
{
|
||||
get => GetString("TemplateRoute_Exception");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred while creating the route with name '{0}' and template '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_Exception(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_Exception"), p0, p1);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public static class RequestDelegateRouteBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> for the given <paramref name="template"/>, and
|
||||
/// <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapRoute(this IRouteBuilder builder, string template, RequestDelegate handler)
|
||||
{
|
||||
var route = new Route(
|
||||
new RouteHandler(handler),
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: GetConstraintResolver(builder));
|
||||
|
||||
builder.Routes.Add(route);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> for the given <paramref name="template"/>, and
|
||||
/// <paramref name="action"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapMiddlewareRoute(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
|
||||
{
|
||||
var nested = builder.ApplicationBuilder.New();
|
||||
action(nested);
|
||||
return builder.MapRoute(template, nested.Build());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP DELETE requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapDelete(this IRouteBuilder builder, string template, RequestDelegate handler)
|
||||
{
|
||||
return builder.MapVerb("DELETE", template, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP DELETE requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="action"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapMiddlewareDelete(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
|
||||
{
|
||||
return builder.MapMiddlewareVerb("DELETE", template, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP DELETE requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapDelete(
|
||||
this IRouteBuilder builder,
|
||||
string template,
|
||||
Func<HttpRequest, HttpResponse, RouteData, Task> handler)
|
||||
{
|
||||
return builder.MapVerb("DELETE", template, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP GET requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapGet(this IRouteBuilder builder, string template, RequestDelegate handler)
|
||||
{
|
||||
return builder.MapVerb("GET", template, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP GET requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="action"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapMiddlewareGet(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
|
||||
{
|
||||
return builder.MapMiddlewareVerb("GET", template, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP GET requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapGet(
|
||||
this IRouteBuilder builder,
|
||||
string template,
|
||||
Func<HttpRequest, HttpResponse, RouteData, Task> handler)
|
||||
{
|
||||
return builder.MapVerb("GET", template, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP POST requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapPost(this IRouteBuilder builder, string template, RequestDelegate handler)
|
||||
{
|
||||
return builder.MapVerb("POST", template, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP POST requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="action"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapMiddlewarePost(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
|
||||
{
|
||||
return builder.MapMiddlewareVerb("POST", template, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP POST requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapPost(
|
||||
this IRouteBuilder builder,
|
||||
string template,
|
||||
Func<HttpRequest, HttpResponse, RouteData, Task> handler)
|
||||
{
|
||||
return builder.MapVerb("POST", template, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP PUT requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapPut(this IRouteBuilder builder, string template, RequestDelegate handler)
|
||||
{
|
||||
return builder.MapVerb("PUT", template, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP PUT requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="action"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapMiddlewarePut(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
|
||||
{
|
||||
return builder.MapMiddlewareVerb("PUT", template, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP PUT requests for the given
|
||||
/// <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapPut(
|
||||
this IRouteBuilder builder,
|
||||
string template,
|
||||
Func<HttpRequest, HttpResponse, RouteData, Task> handler)
|
||||
{
|
||||
return builder.MapVerb("PUT", template, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP requests for the given
|
||||
/// <paramref name="verb"/>, <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="verb">The HTTP verb allowed by the route.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapVerb(
|
||||
this IRouteBuilder builder,
|
||||
string verb,
|
||||
string template,
|
||||
Func<HttpRequest, HttpResponse, RouteData, Task> handler)
|
||||
{
|
||||
RequestDelegate requestDelegate = (httpContext) =>
|
||||
{
|
||||
return handler(httpContext.Request, httpContext.Response, httpContext.GetRouteData());
|
||||
};
|
||||
|
||||
return builder.MapVerb(verb, template, requestDelegate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP requests for the given
|
||||
/// <paramref name="verb"/>, <paramref name="template"/>, and <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="verb">The HTTP verb allowed by the route.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapVerb(
|
||||
this IRouteBuilder builder,
|
||||
string verb,
|
||||
string template,
|
||||
RequestDelegate handler)
|
||||
{
|
||||
var route = new Route(
|
||||
new RouteHandler(handler),
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: new RouteValueDictionary(new { httpMethod = new HttpMethodRouteConstraint(verb) }),
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: GetConstraintResolver(builder));
|
||||
|
||||
builder.Routes.Add(route);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP requests for the given
|
||||
/// <paramref name="verb"/>, <paramref name="template"/>, and <paramref name="action"/>.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
|
||||
/// <param name="verb">The HTTP verb allowed by the route.</param>
|
||||
/// <param name="template">The route template.</param>
|
||||
/// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
|
||||
public static IRouteBuilder MapMiddlewareVerb(
|
||||
this IRouteBuilder builder,
|
||||
string verb,
|
||||
string template,
|
||||
Action<IApplicationBuilder> action)
|
||||
{
|
||||
var nested = builder.ApplicationBuilder.New();
|
||||
action(nested);
|
||||
return builder.MapVerb(verb, template, nested.Build());
|
||||
}
|
||||
|
||||
private static IInlineConstraintResolver GetConstraintResolver(IRouteBuilder builder)
|
||||
{
|
||||
return builder.ServiceProvider.GetRequiredService<IInlineConstraintResolver>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
<?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="ArgumentMustBeGreaterThanOrEqualTo" xml:space="preserve">
|
||||
<value>Value must be greater than or equal to {0}.</value>
|
||||
</data>
|
||||
<data name="RangeConstraint_MinShouldBeLessThanOrEqualToMax" xml:space="preserve">
|
||||
<value>The value for argument '{0}' should be less than or equal to the value for the argument '{1}'.</value>
|
||||
</data>
|
||||
<data name="PropertyOfTypeCannotBeNull" xml:space="preserve">
|
||||
<value>The '{0}' property of '{1}' must not be null.</value>
|
||||
</data>
|
||||
<data name="NamedRoutes_AmbiguousRoutesFound" xml:space="preserve">
|
||||
<value>The supplied route name '{0}' is ambiguous and matched more than one route.</value>
|
||||
</data>
|
||||
<data name="DefaultHandler_MustBeSet" xml:space="preserve">
|
||||
<value>A default handler must be set on the {0}.</value>
|
||||
</data>
|
||||
<data name="DefaultInlineConstraintResolver_AmbiguousCtors" xml:space="preserve">
|
||||
<value>The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.</value>
|
||||
</data>
|
||||
<data name="DefaultInlineConstraintResolver_CouldNotFindCtor" xml:space="preserve">
|
||||
<value>Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.</value>
|
||||
</data>
|
||||
<data name="DefaultInlineConstraintResolver_TypeNotConstraint" xml:space="preserve">
|
||||
<value>The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveCatchAllInMultiSegment" xml:space="preserve">
|
||||
<value>A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly" xml:space="preserve">
|
||||
<value>The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveConsecutiveParameters" xml:space="preserve">
|
||||
<value>A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveConsecutiveSeparators" xml:space="preserve">
|
||||
<value>The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
|
||||
<value>A catch-all parameter cannot be marked optional.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalCannotHaveDefaultValue" xml:space="preserve">
|
||||
<value>An optional parameter cannot have default value.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CatchAllMustBeLast" xml:space="preserve">
|
||||
<value>A catch-all parameter can only appear as the last segment of the route template.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidLiteral" xml:space="preserve">
|
||||
<value>The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidParameterName" xml:space="preserve">
|
||||
<value>The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidRouteTemplate" xml:space="preserve">
|
||||
<value>The route template cannot start with a '~' character unless followed by a '/'.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_MismatchedParameter" xml:space="preserve">
|
||||
<value>There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_RepeatedParameter" xml:space="preserve">
|
||||
<value>The route parameter name '{0}' appears more than one time in the route template.</value>
|
||||
</data>
|
||||
<data name="RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint" xml:space="preserve">
|
||||
<value>The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.</value>
|
||||
</data>
|
||||
<data name="RouteConstraintBuilder_CouldNotResolveConstraint" xml:space="preserve">
|
||||
<value>The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_UnescapedBrace" xml:space="preserve">
|
||||
<value>In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalParameterCanbBePrecededByPeriod" xml:space="preserve">
|
||||
<value>In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalParameterHasTobeTheLast" xml:space="preserve">
|
||||
<value>An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.</value>
|
||||
</data>
|
||||
<data name="AttributeRoute_DifferentLinkGenerationEntries_SameName" xml:space="preserve">
|
||||
<value>Two or more routes named '{0}' have different templates.</value>
|
||||
</data>
|
||||
<data name="UnableToFindServices" xml:space="preserve">
|
||||
<value>Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_Exception" xml:space="preserve">
|
||||
<value>An error occurred while creating the route with name '{0}' and template '{1}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// 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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class Route : RouteBase
|
||||
{
|
||||
private readonly IRouter _target;
|
||||
|
||||
public Route(
|
||||
IRouter target,
|
||||
string routeTemplate,
|
||||
IInlineConstraintResolver inlineConstraintResolver)
|
||||
: this(
|
||||
target,
|
||||
routeTemplate,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: inlineConstraintResolver)
|
||||
{
|
||||
}
|
||||
|
||||
public Route(
|
||||
IRouter target,
|
||||
string routeTemplate,
|
||||
RouteValueDictionary defaults,
|
||||
IDictionary<string, object> constraints,
|
||||
RouteValueDictionary dataTokens,
|
||||
IInlineConstraintResolver inlineConstraintResolver)
|
||||
: this(target, null, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
|
||||
{
|
||||
}
|
||||
|
||||
public Route(
|
||||
IRouter target,
|
||||
string routeName,
|
||||
string routeTemplate,
|
||||
RouteValueDictionary defaults,
|
||||
IDictionary<string, object> constraints,
|
||||
RouteValueDictionary dataTokens,
|
||||
IInlineConstraintResolver inlineConstraintResolver)
|
||||
: base(
|
||||
routeTemplate,
|
||||
routeName,
|
||||
inlineConstraintResolver,
|
||||
defaults,
|
||||
constraints,
|
||||
dataTokens)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public string RouteTemplate => ParsedTemplate.TemplateText;
|
||||
|
||||
protected override Task OnRouteMatched(RouteContext context)
|
||||
{
|
||||
context.RouteData.Routers.Add(_target);
|
||||
return _target.RouteAsync(context);
|
||||
}
|
||||
|
||||
protected override VirtualPathData OnVirtualPathGenerated(VirtualPathContext context)
|
||||
{
|
||||
return _target.GetVirtualPath(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
// 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.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Logging;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public abstract class RouteBase : IRouter, INamedRouter
|
||||
{
|
||||
private TemplateMatcher _matcher;
|
||||
private TemplateBinder _binder;
|
||||
private ILogger _logger;
|
||||
private ILogger _constraintLogger;
|
||||
|
||||
public RouteBase(
|
||||
string template,
|
||||
string name,
|
||||
IInlineConstraintResolver constraintResolver,
|
||||
RouteValueDictionary defaults,
|
||||
IDictionary<string, object> constraints,
|
||||
RouteValueDictionary dataTokens)
|
||||
{
|
||||
if (constraintResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraintResolver));
|
||||
}
|
||||
|
||||
template = template ?? string.Empty;
|
||||
Name = name;
|
||||
ConstraintResolver = constraintResolver;
|
||||
DataTokens = dataTokens ?? new RouteValueDictionary();
|
||||
|
||||
try
|
||||
{
|
||||
// Data we parse from the template will be used to fill in the rest of the constraints or
|
||||
// defaults. The parser will throw for invalid routes.
|
||||
ParsedTemplate = TemplateParser.Parse(template);
|
||||
|
||||
Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
|
||||
Defaults = GetDefaults(ParsedTemplate, defaults);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new RouteCreationException(Resources.FormatTemplateRoute_Exception(name, template), exception);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IDictionary<string, IRouteConstraint> Constraints { get; protected set; }
|
||||
|
||||
protected virtual IInlineConstraintResolver ConstraintResolver { get; set; }
|
||||
|
||||
public virtual RouteValueDictionary DataTokens { get; protected set; }
|
||||
|
||||
public virtual RouteValueDictionary Defaults { get; protected set; }
|
||||
|
||||
public virtual string Name { get; protected set; }
|
||||
|
||||
public virtual RouteTemplate ParsedTemplate { get; protected set; }
|
||||
|
||||
protected abstract Task OnRouteMatched(RouteContext context);
|
||||
|
||||
protected abstract VirtualPathData OnVirtualPathGenerated(VirtualPathContext context);
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Task RouteAsync(RouteContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
EnsureMatcher();
|
||||
EnsureLoggers(context.HttpContext);
|
||||
|
||||
var requestPath = context.HttpContext.Request.Path;
|
||||
|
||||
if (!_matcher.TryMatch(requestPath, context.RouteData.Values))
|
||||
{
|
||||
// If we got back a null value set, that means the URI did not match
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Perf: Avoid accessing dictionaries if you don't need to write to them, these dictionaries are all
|
||||
// created lazily.
|
||||
if (DataTokens.Count > 0)
|
||||
{
|
||||
MergeValues(context.RouteData.DataTokens, DataTokens);
|
||||
}
|
||||
|
||||
if (!RouteConstraintMatcher.Match(
|
||||
Constraints,
|
||||
context.RouteData.Values,
|
||||
context.HttpContext,
|
||||
this,
|
||||
RouteDirection.IncomingRequest,
|
||||
_constraintLogger))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
_logger.MatchedRoute(Name, ParsedTemplate.TemplateText);
|
||||
|
||||
return OnRouteMatched(context);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||
{
|
||||
EnsureBinder(context.HttpContext);
|
||||
EnsureLoggers(context.HttpContext);
|
||||
|
||||
var values = _binder.GetValues(context.AmbientValues, context.Values);
|
||||
if (values == null)
|
||||
{
|
||||
// We're missing one of the required values for this route.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!RouteConstraintMatcher.Match(
|
||||
Constraints,
|
||||
values.CombinedValues,
|
||||
context.HttpContext,
|
||||
this,
|
||||
RouteDirection.UrlGeneration,
|
||||
_constraintLogger))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
context.Values = values.CombinedValues;
|
||||
|
||||
var pathData = OnVirtualPathGenerated(context);
|
||||
if (pathData != null)
|
||||
{
|
||||
// If the target generates a value then that can short circuit.
|
||||
return pathData;
|
||||
}
|
||||
|
||||
// If we can produce a value go ahead and do it, the caller can check context.IsBound
|
||||
// to see if the values were validated.
|
||||
|
||||
// When we still cannot produce a value, this should return null.
|
||||
var virtualPath = _binder.BindValues(values.AcceptedValues);
|
||||
if (virtualPath == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
pathData = new VirtualPathData(this, virtualPath);
|
||||
if (DataTokens != null)
|
||||
{
|
||||
foreach (var dataToken in DataTokens)
|
||||
{
|
||||
pathData.DataTokens.Add(dataToken.Key, dataToken.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return pathData;
|
||||
}
|
||||
|
||||
protected static IDictionary<string, IRouteConstraint> GetConstraints(
|
||||
IInlineConstraintResolver inlineConstraintResolver,
|
||||
RouteTemplate parsedTemplate,
|
||||
IDictionary<string, object> constraints)
|
||||
{
|
||||
var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText);
|
||||
|
||||
if (constraints != null)
|
||||
{
|
||||
foreach (var kvp in constraints)
|
||||
{
|
||||
constraintBuilder.AddConstraint(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
if (parameter.IsOptional)
|
||||
{
|
||||
constraintBuilder.SetOptional(parameter.Name);
|
||||
}
|
||||
|
||||
foreach (var inlineConstraint in parameter.InlineConstraints)
|
||||
{
|
||||
constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint);
|
||||
}
|
||||
}
|
||||
|
||||
return constraintBuilder.Build();
|
||||
}
|
||||
|
||||
protected static RouteValueDictionary GetDefaults(
|
||||
RouteTemplate parsedTemplate,
|
||||
RouteValueDictionary defaults)
|
||||
{
|
||||
var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults);
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
if (parameter.DefaultValue != null)
|
||||
{
|
||||
if (result.ContainsKey(parameter.Name))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(
|
||||
parameter.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(parameter.Name, parameter.DefaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void MergeValues(
|
||||
RouteValueDictionary destination,
|
||||
RouteValueDictionary values)
|
||||
{
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
// This will replace the original value for the specified key.
|
||||
// Values from the matched route will take preference over previous
|
||||
// data in the route context.
|
||||
destination[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureBinder(HttpContext context)
|
||||
{
|
||||
if (_binder == null)
|
||||
{
|
||||
var pool = context.RequestServices.GetRequiredService<ObjectPool<UriBuildingContext>>();
|
||||
_binder = new TemplateBinder(UrlEncoder.Default, pool, ParsedTemplate, Defaults);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureLoggers(HttpContext context)
|
||||
{
|
||||
if (_logger == null)
|
||||
{
|
||||
var factory = context.RequestServices.GetRequiredService<ILoggerFactory>();
|
||||
_logger = factory.CreateLogger(typeof(RouteBase).FullName);
|
||||
_constraintLogger = factory.CreateLogger(typeof(RouteConstraintMatcher).FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureMatcher()
|
||||
{
|
||||
if (_matcher == null)
|
||||
{
|
||||
_matcher = new TemplateMatcher(ParsedTemplate, Defaults);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ParsedTemplate.TemplateText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// 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;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class RouteBuilder : IRouteBuilder
|
||||
{
|
||||
public RouteBuilder(IApplicationBuilder applicationBuilder)
|
||||
: this(applicationBuilder, defaultHandler: null)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteBuilder(IApplicationBuilder applicationBuilder, IRouter defaultHandler)
|
||||
{
|
||||
if (applicationBuilder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationBuilder));
|
||||
}
|
||||
|
||||
if (applicationBuilder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
|
||||
nameof(IServiceCollection),
|
||||
nameof(RoutingServiceCollectionExtensions.AddRouting),
|
||||
"ConfigureServices(...)"));
|
||||
}
|
||||
|
||||
ApplicationBuilder = applicationBuilder;
|
||||
DefaultHandler = defaultHandler;
|
||||
ServiceProvider = applicationBuilder.ApplicationServices;
|
||||
|
||||
Routes = new List<IRouter>();
|
||||
}
|
||||
|
||||
public IApplicationBuilder ApplicationBuilder { get; }
|
||||
|
||||
public IRouter DefaultHandler { get; set; }
|
||||
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public IList<IRouter> Routes { get; }
|
||||
|
||||
public IRouter Build()
|
||||
{
|
||||
var routeCollection = new RouteCollection();
|
||||
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
routeCollection.Add(route);
|
||||
}
|
||||
|
||||
return routeCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class RouteCollection : IRouteCollection
|
||||
{
|
||||
private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' };
|
||||
private readonly List<IRouter> _routes = new List<IRouter>();
|
||||
private readonly List<IRouter> _unnamedRoutes = new List<IRouter>();
|
||||
private readonly Dictionary<string, INamedRouter> _namedRoutes =
|
||||
new Dictionary<string, INamedRouter>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private RouteOptions _options;
|
||||
|
||||
public IRouter this[int index]
|
||||
{
|
||||
get { return _routes[index]; }
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return _routes.Count; }
|
||||
}
|
||||
|
||||
public void Add(IRouter router)
|
||||
{
|
||||
if (router == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(router));
|
||||
}
|
||||
|
||||
var namedRouter = router as INamedRouter;
|
||||
if (namedRouter != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(namedRouter.Name))
|
||||
{
|
||||
_namedRoutes.Add(namedRouter.Name, namedRouter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_unnamedRoutes.Add(router);
|
||||
}
|
||||
|
||||
_routes.Add(router);
|
||||
}
|
||||
|
||||
public async virtual Task RouteAsync(RouteContext context)
|
||||
{
|
||||
// Perf: We want to avoid allocating a new RouteData for each route we need to process.
|
||||
// We can do this by snapshotting the state at the beginning and then restoring it
|
||||
// for each router we execute.
|
||||
var snapshot = context.RouteData.PushState(null, values: null, dataTokens: null);
|
||||
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var route = this[i];
|
||||
context.RouteData.Routers.Add(route);
|
||||
|
||||
try
|
||||
{
|
||||
await route.RouteAsync(context);
|
||||
|
||||
if (context.Handler != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (context.Handler == null)
|
||||
{
|
||||
snapshot.Restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||
{
|
||||
EnsureOptions(context.HttpContext);
|
||||
|
||||
if (!string.IsNullOrEmpty(context.RouteName))
|
||||
{
|
||||
VirtualPathData namedRoutePathData = null;
|
||||
INamedRouter matchedNamedRoute;
|
||||
if (_namedRoutes.TryGetValue(context.RouteName, out matchedNamedRoute))
|
||||
{
|
||||
namedRoutePathData = matchedNamedRoute.GetVirtualPath(context);
|
||||
}
|
||||
|
||||
var pathData = GetVirtualPath(context, _unnamedRoutes);
|
||||
|
||||
// If the named route and one of the unnamed routes also matches, then we have an ambiguity.
|
||||
if (namedRoutePathData != null && pathData != null)
|
||||
{
|
||||
var message = Resources.FormatNamedRoutes_AmbiguousRoutesFound(context.RouteName);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
return NormalizeVirtualPath(namedRoutePathData ?? pathData);
|
||||
}
|
||||
else
|
||||
{
|
||||
return NormalizeVirtualPath(GetVirtualPath(context, _routes));
|
||||
}
|
||||
}
|
||||
|
||||
private VirtualPathData GetVirtualPath(VirtualPathContext context, List<IRouter> routes)
|
||||
{
|
||||
for (var i = 0; i < routes.Count; i++)
|
||||
{
|
||||
var route = routes[i];
|
||||
|
||||
var pathData = route.GetVirtualPath(context);
|
||||
if (pathData != null)
|
||||
{
|
||||
return pathData;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private VirtualPathData NormalizeVirtualPath(VirtualPathData pathData)
|
||||
{
|
||||
if (pathData == null)
|
||||
{
|
||||
return pathData;
|
||||
}
|
||||
|
||||
var url = pathData.VirtualPath;
|
||||
|
||||
if (!string.IsNullOrEmpty(url) && (_options.LowercaseUrls || _options.AppendTrailingSlash))
|
||||
{
|
||||
var indexOfSeparator = url.IndexOfAny(UrlQueryDelimiters);
|
||||
var urlWithoutQueryString = url;
|
||||
var queryString = string.Empty;
|
||||
|
||||
if (indexOfSeparator != -1)
|
||||
{
|
||||
urlWithoutQueryString = url.Substring(0, indexOfSeparator);
|
||||
queryString = url.Substring(indexOfSeparator);
|
||||
}
|
||||
|
||||
if (_options.LowercaseUrls)
|
||||
{
|
||||
urlWithoutQueryString = urlWithoutQueryString.ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (_options.AppendTrailingSlash && !urlWithoutQueryString.EndsWith("/"))
|
||||
{
|
||||
urlWithoutQueryString += "/";
|
||||
}
|
||||
|
||||
// queryString will contain the delimiter ? or # as the first character, so it's safe to append.
|
||||
url = urlWithoutQueryString + queryString;
|
||||
|
||||
return new VirtualPathData(pathData.Router, url, pathData.DataTokens);
|
||||
}
|
||||
|
||||
return pathData;
|
||||
}
|
||||
|
||||
private void EnsureOptions(HttpContext context)
|
||||
{
|
||||
if (_options == null)
|
||||
{
|
||||
_options = context.RequestServices.GetRequiredService<IOptions<RouteOptions>>().Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
// 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.Routing.Constraints;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// A builder for produding a mapping of keys to see <see cref="IRouteConstraint"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="RouteConstraintBuilder"/> allows iterative building a set of route constraints, and will
|
||||
/// merge multiple entries for the same key.
|
||||
/// </remarks>
|
||||
public class RouteConstraintBuilder
|
||||
{
|
||||
private readonly IInlineConstraintResolver _inlineConstraintResolver;
|
||||
private readonly string _displayName;
|
||||
|
||||
private readonly Dictionary<string, List<IRouteConstraint>> _constraints;
|
||||
private readonly HashSet<string> _optionalParameters;
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteConstraintBuilder"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="inlineConstraintResolver">The <see cref="IInlineConstraintResolver"/>.</param>
|
||||
/// <param name="displayName">The display name (for use in error messages).</param>
|
||||
public RouteConstraintBuilder(
|
||||
IInlineConstraintResolver inlineConstraintResolver,
|
||||
string displayName)
|
||||
{
|
||||
if (inlineConstraintResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inlineConstraintResolver));
|
||||
}
|
||||
|
||||
if (displayName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayName));
|
||||
}
|
||||
|
||||
_inlineConstraintResolver = inlineConstraintResolver;
|
||||
_displayName = displayName;
|
||||
|
||||
_constraints = new Dictionary<string, List<IRouteConstraint>>(StringComparer.OrdinalIgnoreCase);
|
||||
_optionalParameters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a mapping of constraints.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IDictionary{String, IRouteConstraint}"/> of the constraints.</returns>
|
||||
public IDictionary<string, IRouteConstraint> Build()
|
||||
{
|
||||
var constraints = new Dictionary<string, IRouteConstraint>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kvp in _constraints)
|
||||
{
|
||||
IRouteConstraint constraint;
|
||||
if (kvp.Value.Count == 1)
|
||||
{
|
||||
constraint = kvp.Value[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
constraint = new CompositeRouteConstraint(kvp.Value.ToArray());
|
||||
}
|
||||
|
||||
if (_optionalParameters.Contains(kvp.Key))
|
||||
{
|
||||
var optionalConstraint = new OptionalRouteConstraint(constraint);
|
||||
constraints.Add(kvp.Key, optionalConstraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
constraints.Add(kvp.Key, constraint);
|
||||
}
|
||||
}
|
||||
|
||||
return constraints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a constraint instance for the given key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">
|
||||
/// The constraint instance. Must either be a string or an instance of <see cref="IRouteConstraint"/>.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// If the <paramref name="value"/> is a string, it will be converted to a <see cref="RegexRouteConstraint"/>.
|
||||
///
|
||||
/// For example, the string <code>Product[0-9]+</code> will be converted to the regular expression
|
||||
/// <code>^(Product[0-9]+)</code>. See <see cref="System.Text.RegularExpressions.Regex"/> for more details.
|
||||
/// </remarks>
|
||||
public void AddConstraint(string key, object value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
var constraint = value as IRouteConstraint;
|
||||
if (constraint == null)
|
||||
{
|
||||
var regexPattern = value as string;
|
||||
if (regexPattern == null)
|
||||
{
|
||||
throw new RouteCreationException(
|
||||
Resources.FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(
|
||||
key,
|
||||
value,
|
||||
_displayName,
|
||||
typeof(IRouteConstraint)));
|
||||
}
|
||||
|
||||
var constraintsRegEx = "^(" + regexPattern + ")$";
|
||||
constraint = new RegexRouteConstraint(constraintsRegEx);
|
||||
}
|
||||
|
||||
Add(key, constraint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a constraint for the given key, resolved by the <see cref="IInlineConstraintResolver"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="constraintText">The text to be resolved by <see cref="IInlineConstraintResolver"/>.</param>
|
||||
/// <remarks>
|
||||
/// The <see cref="IInlineConstraintResolver"/> can create <see cref="IRouteConstraint"/> instances
|
||||
/// based on <paramref name="constraintText"/>. See <see cref="RouteOptions.ConstraintMap"/> to register
|
||||
/// custom constraint types.
|
||||
/// </remarks>
|
||||
public void AddResolvedConstraint(string key, string constraintText)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (constraintText == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraintText));
|
||||
}
|
||||
|
||||
var constraint = _inlineConstraintResolver.ResolveConstraint(constraintText);
|
||||
if (constraint == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatRouteConstraintBuilder_CouldNotResolveConstraint(
|
||||
key,
|
||||
constraintText,
|
||||
_displayName,
|
||||
_inlineConstraintResolver.GetType().Name));
|
||||
}
|
||||
|
||||
Add(key, constraint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the given key as optional.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
public void SetOptional(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
_optionalParameters.Add(key);
|
||||
}
|
||||
|
||||
private void Add(string key, IRouteConstraint constraint)
|
||||
{
|
||||
List<IRouteConstraint> list;
|
||||
if (!_constraints.TryGetValue(key, out list))
|
||||
{
|
||||
list = new List<IRouteConstraint>();
|
||||
_constraints.Add(key, list);
|
||||
}
|
||||
|
||||
list.Add(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public static class RouteConstraintMatcher
|
||||
{
|
||||
public static bool Match(
|
||||
IDictionary<string, IRouteConstraint> constraints,
|
||||
RouteValueDictionary routeValues,
|
||||
HttpContext httpContext,
|
||||
IRouter route,
|
||||
RouteDirection routeDirection,
|
||||
ILogger logger)
|
||||
{
|
||||
if (routeValues == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeValues));
|
||||
}
|
||||
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (constraints == null || constraints.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var kvp in constraints)
|
||||
{
|
||||
var constraint = kvp.Value;
|
||||
if (!constraint.Match(httpContext, route, kvp.Key, routeValues, routeDirection))
|
||||
{
|
||||
if (routeDirection.Equals(RouteDirection.IncomingRequest))
|
||||
{
|
||||
object routeValue;
|
||||
routeValues.TryGetValue(kvp.Key, out routeValue);
|
||||
|
||||
logger.RouteValueDoesNotMatchConstraint(routeValue, kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// The exception that is thrown for invalid routes or constraints.
|
||||
/// </summary>
|
||||
public class RouteCreationException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RouteCreationException"/> class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public RouteCreationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RouteCreationException"/> class with a specified error message
|
||||
/// and a reference to the inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception.</param>
|
||||
public RouteCreationException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class RouteHandler : IRouteHandler, IRouter
|
||||
{
|
||||
private readonly RequestDelegate _requestDelegate;
|
||||
|
||||
public RouteHandler(RequestDelegate requestDelegate)
|
||||
{
|
||||
_requestDelegate = requestDelegate;
|
||||
}
|
||||
|
||||
public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
|
||||
{
|
||||
return _requestDelegate;
|
||||
}
|
||||
|
||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||
{
|
||||
// Nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task RouteAsync(RouteContext context)
|
||||
{
|
||||
context.Handler = _requestDelegate;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// 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.Routing.Constraints;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class RouteOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether all generated URLs are lower-case.
|
||||
/// </summary>
|
||||
public bool LowercaseUrls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a trailing slash should be appended to the generated URLs.
|
||||
/// </summary>
|
||||
public bool AppendTrailingSlash { get; set; }
|
||||
|
||||
private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
|
||||
|
||||
public IDictionary<string, Type> ConstraintMap
|
||||
{
|
||||
get
|
||||
{
|
||||
return _constraintTypeMap;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ConstraintMap));
|
||||
}
|
||||
|
||||
_constraintTypeMap = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static IDictionary<string, Type> GetDefaultConstraintMap()
|
||||
{
|
||||
return new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// Type-specific constraints
|
||||
{ "int", typeof(IntRouteConstraint) },
|
||||
{ "bool", typeof(BoolRouteConstraint) },
|
||||
{ "datetime", typeof(DateTimeRouteConstraint) },
|
||||
{ "decimal", typeof(DecimalRouteConstraint) },
|
||||
{ "double", typeof(DoubleRouteConstraint) },
|
||||
{ "float", typeof(FloatRouteConstraint) },
|
||||
{ "guid", typeof(GuidRouteConstraint) },
|
||||
{ "long", typeof(LongRouteConstraint) },
|
||||
|
||||
// Length constraints
|
||||
{ "minlength", typeof(MinLengthRouteConstraint) },
|
||||
{ "maxlength", typeof(MaxLengthRouteConstraint) },
|
||||
{ "length", typeof(LengthRouteConstraint) },
|
||||
|
||||
// Min/Max value constraints
|
||||
{ "min", typeof(MinRouteConstraint) },
|
||||
{ "max", typeof(MaxRouteConstraint) },
|
||||
{ "range", typeof(RangeRouteConstraint) },
|
||||
|
||||
// Regex-based constraints
|
||||
{ "alpha", typeof(AlphaRouteConstraint) },
|
||||
{ "regex", typeof(RegexInlineRouteConstraint) },
|
||||
|
||||
{"required", typeof(RequiredRouteConstraint) },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// 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.Globalization;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IEqualityComparer{Object}"/> implementation that compares objects as-if
|
||||
/// they were route value strings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Values that are are not strings are converted to strings using
|
||||
/// <c>Convert.ToString(x, CultureInfo.InvariantCulture)</c>. <c>null</c> values are converted
|
||||
/// to the empty string.
|
||||
///
|
||||
/// strings are compared using <see cref="StringComparison.OrdinalIgnoreCase"/>.
|
||||
/// </remarks>
|
||||
public class RouteValueEqualityComparer : IEqualityComparer<object>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public new bool Equals(object x, object y)
|
||||
{
|
||||
var stringX = x as string ?? Convert.ToString(x, CultureInfo.InvariantCulture);
|
||||
var stringY = y as string ?? Convert.ToString(y, CultureInfo.InvariantCulture);
|
||||
|
||||
if (string.IsNullOrEmpty(stringX) && string.IsNullOrEmpty(stringY))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Equals(stringX, stringY, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int GetHashCode(object obj)
|
||||
{
|
||||
var stringObj = obj as string ?? Convert.ToString(obj, CultureInfo.InvariantCulture);
|
||||
if (string.IsNullOrEmpty(stringObj))
|
||||
{
|
||||
return StringComparer.OrdinalIgnoreCase.GetHashCode(string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
return StringComparer.OrdinalIgnoreCase.GetHashCode(stringObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public class RouterMiddleware
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IRouter _router;
|
||||
|
||||
public RouterMiddleware(
|
||||
RequestDelegate next,
|
||||
ILoggerFactory loggerFactory,
|
||||
IRouter router)
|
||||
{
|
||||
_next = next;
|
||||
_router = router;
|
||||
|
||||
_logger = loggerFactory.CreateLogger<RouterMiddleware>();
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
var context = new RouteContext(httpContext);
|
||||
context.RouteData.Routers.Add(_router);
|
||||
|
||||
await _router.RouteAsync(context);
|
||||
|
||||
if (context.Handler == null)
|
||||
{
|
||||
_logger.RequestDidNotMatchRoutes();
|
||||
await _next.Invoke(httpContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature()
|
||||
{
|
||||
RouteData = context.RouteData,
|
||||
};
|
||||
|
||||
await context.Handler(context.HttpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue