Merge remote-tracking branch 'Routing/rybrande/release21ToSrc' into rybrande/Mondo2.1

This commit is contained in:
Ryan Brandenburg 2018-11-21 15:17:30 -08:00
commit ff3891fa7a
177 changed files with 31168 additions and 0 deletions

41
src/Routing/.gitignore vendored Normal file
View File

@ -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/

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,13 @@
{
"adx-nonshipping": {
"rules": [],
"packages": {
"Microsoft.AspNetCore.Routing.DecisionTree.Sources": {}
}
},
"Default": {
"rules": [
"DefaultCompositeRule"
]
}
}

10
src/Routing/README.md Normal file
View File

@ -0,0 +1,10 @@
ASP.NET Routing
===
AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/fe4o5h1s9ve86nyv/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/Routing/branch/dev)
Travis: [![Travis](https://travis-ci.org/aspnet/Routing.svg?branch=dev)](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.

165
src/Routing/Routing.sln Normal file
View File

@ -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

View File

@ -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));
}
}
}

View File

@ -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>

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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
```

BIN
src/Routing/build/Key.snk Normal file

Binary file not shown.

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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)) });
}));
}
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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; }
}
}
}

View File

@ -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);
}
}
}

View File

@ -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)
{
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,7 @@
<Project>
<Import Project="..\Directory.Build.props" />
<ItemGroup>
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using 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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -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")]

View 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;
}
}
}

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}
}
}
}
}

View File

@ -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,
}
}

View File

@ -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[]>
{
}
}
}

View File

@ -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];
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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": []
}
]
}

View File

@ -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]*$")
{
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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));
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Routing
{
public interface INamedRouter : IRouter
{
string Name { get; }
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Routing
{
public interface IRouteCollection : IRouter
{
void Add(IRouter router);
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using 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
{
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
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,
}
}

View File

@ -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;
}
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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")]

View 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;
}
}
}

View File

@ -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>();
}
}
}

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
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)
{
}
}
}

View File

@ -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;
}
}
}

View File

@ -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) },
};
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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