Merge remote-tracking branch 'Routing/rybrande/release22ToSrc' into rybrande/Mondo2.2
This commit is contained in:
commit
ee050a4725
|
|
@ -8,6 +8,7 @@ packages/
|
|||
artifacts/
|
||||
PublishProfiles/
|
||||
.vs/
|
||||
.vscode/
|
||||
.build/
|
||||
.testPublish/
|
||||
bower_components/
|
||||
|
|
@ -39,3 +40,5 @@ node_modules
|
|||
*launchSettings.json
|
||||
global.json
|
||||
BenchmarkDotNet.Artifacts/
|
||||
*.etl.zip
|
||||
*.etl
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)build\Key.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.2' ">$(MicrosoftNETCoreApp22PackageVersion)</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
|
||||
<!-- aspnet/BuildTools#662 Don't police what version of NetCoreApp we use -->
|
||||
<NETCoreAppMaximumVersion>99.9</NETCoreAppMaximumVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
ASP.NET Routing
|
||||
===
|
||||
|
||||
AppVeyor: [](https://ci.appveyor.com/project/aspnetci/Routing/branch/dev)
|
||||
AppVeyor: [](https://ci.appveyor.com/project/aspnetci/Routing/branch/master)
|
||||
|
||||
Travis: [](https://travis-ci.org/aspnet/Routing)
|
||||
Travis: [](https://travis-ci.org/aspnet/Routing)
|
||||
|
||||
Contains routing middleware for routing requests to application logic.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27106.3000
|
||||
|
|
@ -18,10 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Routin
|
|||
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
|
||||
build\dependencies.props = build\dependencies.props
|
||||
global.json = global.json
|
||||
NuGet.config = NuGet.config
|
||||
EndProjectSection
|
||||
|
|
@ -45,6 +45,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Routin
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{D5F39F59-5725-4127-82E7-67028D006185}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{7F5914E2-C63F-4759-898E-462804357C90}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "benchmarkapps\Benchmarks\Benchmarks.csproj", "{91F47A60-9A78-4968-B10D-157D9BFAC37F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{6824486A-3EFF-45D1-BEE8-8B137639C890}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Swaggatherer", "tools\Swaggatherer\Swaggatherer.csproj", "{B8516771-E850-4724-BEC3-63FC00C2AE57}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{8E5E51D4-6B03-4FC6-9F34-6E9FA24702BD}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
test\WebSites\Directory.Build.props = test\WebSites\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoutingWebSite", "test\WebSites\RoutingWebSite\RoutingWebSite.csproj", "{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoutingSandbox", "samples\RoutingSandbox\RoutingSandbox.csproj", "{89317AF8-1893-4F40-BCE2-7FB52E5A5033}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -75,16 +92,6 @@ Global
|
|||
{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
|
||||
|
|
@ -145,6 +152,54 @@ Global
|
|||
{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
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57}.Release|x86.Build.0 = Release|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74}.Release|x86.Build.0 = Release|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -152,12 +207,16 @@ Global
|
|||
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}
|
||||
{91F47A60-9A78-4968-B10D-157D9BFAC37F} = {7F5914E2-C63F-4759-898E-462804357C90}
|
||||
{B8516771-E850-4724-BEC3-63FC00C2AE57} = {6824486A-3EFF-45D1-BEE8-8B137639C890}
|
||||
{8E5E51D4-6B03-4FC6-9F34-6E9FA24702BD} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
|
||||
{E91EC5EC-30A8-45EC-9B2F-67E2D6C39D74} = {8E5E51D4-6B03-4FC6-9F34-6E9FA24702BD}
|
||||
{89317AF8-1893-4F40-BCE2-7FB52E5A5033} = {C3ADD55B-B9C7-4061-8AD4-6A70D1AE3B2E}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {36C8D815-B7F1-479D-894B-E606FB8DECDA}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,430 @@
|
|||
<StyleCopSettings Version="105">
|
||||
<GlobalSettings>
|
||||
<StringProperty Name="MergeSettingsFiles">NoMerge</StringProperty>
|
||||
</GlobalSettings>
|
||||
<Parsers>
|
||||
<Parser ParserId="StyleCop.CSharp.CsParser">
|
||||
<ParserSettings>
|
||||
<BooleanProperty Name="AnalyzeDesignerFiles">False</BooleanProperty>
|
||||
</ParserSettings>
|
||||
</Parser>
|
||||
</Parsers>
|
||||
<Analyzers>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.NamingRules">
|
||||
<Rules>
|
||||
<Rule Name="FieldNamesMustNotBeginWithUnderscore">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="FieldNamesMustNotUseHungarianNotation">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings>
|
||||
<CollectionProperty Name="Hungarian">
|
||||
<Value>as</Value>
|
||||
<Value>db</Value>
|
||||
<Value>dc</Value>
|
||||
<Value>do</Value>
|
||||
<Value>ef</Value>
|
||||
<Value>id</Value>
|
||||
<Value>if</Value>
|
||||
<Value>in</Value>
|
||||
<Value>is</Value>
|
||||
<Value>my</Value>
|
||||
<Value>no</Value>
|
||||
<Value>on</Value>
|
||||
<Value>sl</Value>
|
||||
<Value>to</Value>
|
||||
<Value>ui</Value>
|
||||
<Value>vs</Value>
|
||||
</CollectionProperty>
|
||||
</AnalyzerSettings>
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.DocumentationRules">
|
||||
<Rules>
|
||||
<Rule Name="FileMustHaveHeader">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="FileHeaderMustContainFileName">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="FileHeaderFileNameDocumentationMustMatchFileName">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="FileHeaderMustHaveValidCompanyText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ConstructorSummaryDocumentationMustBeginWithStandardText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="DestructorSummaryDocumentationMustBeginWithStandardText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="DocumentationHeadersMustNotContainBlankLines">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementsMustBeDocumented">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="PartialElementsMustBeDocumented">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="EnumerationItemsMustBeDocumented">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="DocumentationMustContainValidXml">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementDocumentationMustHaveSummary">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="PartialElementDocumentationMustHaveSummary">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementDocumentationMustHaveSummaryText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="PartialElementDocumentationMustHaveSummaryText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementDocumentationMustNotHaveDefaultSummary">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementParametersMustBeDocumented">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementParameterDocumentationMustMatchElementParameters">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementParameterDocumentationMustDeclareParameterName">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementParameterDocumentationMustHaveText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementReturnValueMustBeDocumented">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementReturnValueDocumentationMustHaveText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="VoidReturnValueMustNotBeDocumented">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="GenericTypeParametersMustBeDocumented">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="GenericTypeParametersMustBeDocumentedPartialClass">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="GenericTypeParameterDocumentationMustMatchTypeParameters">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="GenericTypeParameterDocumentationMustDeclareParameterName">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="GenericTypeParameterDocumentationMustHaveText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="PropertySummaryDocumentationMustMatchAccessors">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="PropertySummaryDocumentationMustOmitSetAccessorWithRestrictedAccess">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementDocumentationMustNotBeCopiedAndPasted">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="SingleLineCommentsMustNotUseDocumentationStyleSlashes">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="DocumentationTextMustNotBeEmpty">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="DocumentationTextMustContainWhitespace">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="DocumentationMustMeetCharacterPercentage">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="IncludedDocumentationXPathDoesNotExist">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="IncludeNodeDoesNotContainValidFileAndPath">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="InheritDocMustBeUsedWithInheritingClass">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="FileHeaderMustShowCopyright">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="FileHeaderMustHaveCopyrightText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="FileHeaderFileNameDocumentationMustMatchTypeName">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings>
|
||||
<BooleanProperty Name="IgnorePrivates">True</BooleanProperty>
|
||||
<BooleanProperty Name="IgnoreInternals">True</BooleanProperty>
|
||||
</AnalyzerSettings>
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.ReadabilityRules">
|
||||
<Rules>
|
||||
<Rule Name="OpeningParenthesisMustBeOnDeclarationLine">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ParameterMustNotSpanMultipleLines">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="UseStringEmptyForEmptyStrings">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="PrefixLocalCallsWithThis">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ParameterMustFollowComma">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="SplitParametersMustStartOnLineAfterDeclaration">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ParametersMustBeOnSameLineOrSeparateLines">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="UseBuiltInTypeAlias">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings />
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="Microsoft.Web.StyleCop.Rules">
|
||||
<AnalyzerSettings>
|
||||
<StringProperty Name="FileHeaderText"> Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.</StringProperty>
|
||||
</AnalyzerSettings>
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.LayoutRules">
|
||||
<Rules>
|
||||
<Rule Name="AllAccessorsMustBeMultiLineOrSingleLine">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="SingleLineCommentsMustNotBeFollowedByBlankLine">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ClosingCurlyBracketMustBeFollowedByBlankLine">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="SingleLineCommentMustBePrecededByBlankLine">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementsMustBeSeparatedByBlankLine">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings />
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.MaintainabilityRules">
|
||||
<Rules>
|
||||
<Rule Name="ConditionalExpressionsMustDeclarePrecedence">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="FieldsMustBePrivate">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="DebugAssertMustProvideMessageText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="StatementMustNotUseUnnecessaryParenthesis">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings />
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.OrderingRules">
|
||||
<Rules>
|
||||
<Rule Name="UsingDirectivesMustBePlacedWithinNamespace">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementsMustAppearInTheCorrectOrder">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ElementsMustBeOrderedByAccess">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ConstantsMustAppearBeforeFields">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="StaticElementsMustAppearBeforeInstanceElements">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings />
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.SpacingRules">
|
||||
<Rules>
|
||||
<Rule Name="SingleLineCommentsMustBeginWithSingleSpace">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
|
||||
<!-- Creates a lot of noise with anonymous objects -->
|
||||
<Rule Name="OpeningCurlyBracketsMustBeSpacedCorrectly">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ClosingCurlyBracketsMustBeSpacedCorrectly">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings />
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.KRules.FileHeaderRule">
|
||||
<Rules />
|
||||
<AnalyzerSettings>
|
||||
<StringProperty Name="FileHeaderText"> 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.</StringProperty>
|
||||
</AnalyzerSettings>
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.KRules.LineLengthRule">
|
||||
<Rules />
|
||||
<AnalyzerSettings>
|
||||
<IntegerProperty Name="LineLength">120</IntegerProperty>
|
||||
</AnalyzerSettings>
|
||||
</Analyzer>
|
||||
</Analyzers>
|
||||
</StyleCopSettings>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
.NET Core uses third-party libraries or other resources that may be
|
||||
distributed under licenses different than the .NET Core software.
|
||||
|
||||
In the event that we accidentally failed to list a required notice, please
|
||||
bring it to our attention. Post an issue or email us:
|
||||
|
||||
dotnet@microsoft.com
|
||||
|
||||
The attached notices are provided for information only.
|
||||
|
||||
License notice for viz.js
|
||||
------------------------------------
|
||||
|
||||
Copyright (c) 2014-2018 Michael Daines
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<TargetFramework Condition="'$(BenchmarksTargetFramework)' != ''">$(BenchmarksTargetFramework)</TargetFramework>
|
||||
<UseP2PReferences Condition="'$(UseP2PReferences)'=='' AND '$(BenchmarksTargetFramework)'==''">true</UseP2PReferences>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--These references are used when running locally-->
|
||||
<ItemGroup Condition="'$(UseP2PReferences)'=='true'">
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(MicrosoftExtensionsConfigurationCommandLinePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="$(MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--These references are used when running on the Benchmarks Server-->
|
||||
<ItemGroup Condition="'$(UseP2PReferences)'!='true'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="$(MicrosoftAspNetCoreAppPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Benchmarks
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
GetWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder GetWebHostBuilder(string[] args)
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddCommandLine(args)
|
||||
.AddEnvironmentVariables(prefix: "RoutingBenchmarks_")
|
||||
.Build();
|
||||
|
||||
// Consoler logger has a major impact on perf results, so do not use
|
||||
// default builder.
|
||||
|
||||
var webHostBuilder = new WebHostBuilder()
|
||||
.UseConfiguration(config)
|
||||
.UseKestrel();
|
||||
|
||||
var scenario = config["scenarios"]?.ToLower();
|
||||
if (scenario == "plaintextdispatcher" || scenario == "plaintextendpointrouting")
|
||||
{
|
||||
webHostBuilder.UseStartup<StartupUsingEndpointRouting>();
|
||||
// for testing
|
||||
webHostBuilder.UseSetting("Startup", nameof(StartupUsingEndpointRouting));
|
||||
}
|
||||
else if (scenario == "plaintextrouting" || scenario == "plaintextrouter")
|
||||
{
|
||||
webHostBuilder.UseStartup<StartupUsingRouter>();
|
||||
// for testing
|
||||
webHostBuilder.UseSetting("Startup", nameof(StartupUsingRouter));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Invalid scenario '{scenario}'. Allowed scenarios are PlaintextEndpointRouting and PlaintextRouter");
|
||||
}
|
||||
|
||||
return webHostBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Benchmarks
|
||||
{
|
||||
public class StartupUsingEndpointRouting
|
||||
{
|
||||
private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddRouting();
|
||||
|
||||
var endpointDataSource = new DefaultEndpointDataSource(new[]
|
||||
{
|
||||
new RouteEndpoint(
|
||||
requestDelegate: (httpContext) =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
var payloadLength = _helloWorldPayload.Length;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
response.ContentLength = payloadLength;
|
||||
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
||||
},
|
||||
routePattern: RoutePatternFactory.Parse("/plaintext"),
|
||||
order: 0,
|
||||
metadata: EndpointMetadataCollection.Empty,
|
||||
displayName: "Plaintext"),
|
||||
});
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
||||
}
|
||||
|
||||
public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
|
||||
{
|
||||
app.UseEndpointRouting();
|
||||
|
||||
app.UseEndpoint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Text;
|
||||
|
||||
namespace Benchmarks
|
||||
{
|
||||
public class StartupUsingRouter
|
||||
{
|
||||
private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddRouting();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseRouter(routes =>
|
||||
{
|
||||
routes.MapRoute("/plaintext", (httpContext) =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
var payloadLength = _helloWorldPayload.Length;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
response.ContentLength = payloadLength;
|
||||
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"Default": {
|
||||
"Client": "Wrk",
|
||||
"PresetHeaders": "Plaintext",
|
||||
"ClientProperties": {
|
||||
"ScriptName": "pipeline",
|
||||
"PipelineDepth": 16
|
||||
},
|
||||
"Source": {
|
||||
"Repository": "https://github.com/aspnet/routing.git",
|
||||
"BranchOrCommit": "release/2.2",
|
||||
"Project": "benchmarkapps/Benchmarks/Benchmarks.csproj"
|
||||
},
|
||||
"Port": 8080
|
||||
},
|
||||
"PlaintextRouting": {
|
||||
"Path": "/plaintext"
|
||||
},
|
||||
"PlaintextRouter": {
|
||||
"Path": "/plaintext"
|
||||
},
|
||||
"PlaintextDispatcher": {
|
||||
"Path": "/plaintext"
|
||||
},
|
||||
"PlaintextEndpointRouting": {
|
||||
"Path": "/plaintext"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BenchmarkDotNet.Columns;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Validators;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Performance
|
||||
{
|
||||
public class CoreConfig : ManualConfig
|
||||
{
|
||||
public CoreConfig()
|
||||
{
|
||||
Add(JitOptimizationsValidator.FailOnError);
|
||||
Add(MemoryDiagnoser.Default);
|
||||
Add(StatisticColumn.OperationsPerSecond);
|
||||
|
||||
Add(Job.Default
|
||||
.With(BenchmarkDotNet.Environments.Runtime.Core)
|
||||
.WithRemoveOutliers(false)
|
||||
.With(new GcMode() { Server = true })
|
||||
.With(RunStrategy.Throughput)
|
||||
.WithLaunchCount(3)
|
||||
.WithWarmupCount(5)
|
||||
.WithTargetCount(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class EndpointMetadataCollectionBenchmark
|
||||
{
|
||||
private object[] _items;
|
||||
private EndpointMetadataCollection _collection;
|
||||
|
||||
[Params(3, 10, 25)]
|
||||
public int Count { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var seeds = new Type[]
|
||||
{
|
||||
typeof(Metadata1),
|
||||
typeof(Metadata2),
|
||||
typeof(Metadata3),
|
||||
typeof(Metadata4),
|
||||
typeof(Metadata5),
|
||||
typeof(Metadata6),
|
||||
typeof(Metadata7),
|
||||
typeof(Metadata8),
|
||||
typeof(Metadata9),
|
||||
};
|
||||
|
||||
_items = new object[Count];
|
||||
for (var i = 0; i < _items.Length; i++)
|
||||
{
|
||||
_items[i] = seeds[i % seeds.Length];
|
||||
}
|
||||
|
||||
_collection = new EndpointMetadataCollection(_items);
|
||||
}
|
||||
|
||||
// This is a synthetic baseline that visits each item and does an as-cast.
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = 5)]
|
||||
public void Baseline()
|
||||
{
|
||||
var items = _items;
|
||||
for (var i = items.Length - 1; i >= 0; i--)
|
||||
{
|
||||
GC.KeepAlive(_items[i] as IMetadata1);
|
||||
}
|
||||
|
||||
for (var i = items.Length - 1; i >= 0; i--)
|
||||
{
|
||||
GC.KeepAlive(_items[i] as IMetadata2);
|
||||
}
|
||||
|
||||
for (var i = items.Length - 1; i >= 0; i--)
|
||||
{
|
||||
GC.KeepAlive(_items[i] as IMetadata3);
|
||||
}
|
||||
|
||||
for (var i = items.Length - 1; i >= 0; i--)
|
||||
{
|
||||
GC.KeepAlive(_items[i] as IMetadata4);
|
||||
}
|
||||
|
||||
for (var i = items.Length - 1; i >= 0; i--)
|
||||
{
|
||||
GC.KeepAlive(_items[i] as IMetadata5);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 5)]
|
||||
public void GetMetadata()
|
||||
{
|
||||
GC.KeepAlive(_collection.GetMetadata<IMetadata1>());
|
||||
GC.KeepAlive(_collection.GetMetadata<IMetadata2>());
|
||||
GC.KeepAlive(_collection.GetMetadata<IMetadata3>());
|
||||
GC.KeepAlive(_collection.GetMetadata<IMetadata4>());
|
||||
GC.KeepAlive(_collection.GetMetadata<IMetadata5>());
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 5)]
|
||||
public void GetOrderedMetadata()
|
||||
{
|
||||
foreach (var item in _collection.GetOrderedMetadata<IMetadata1>())
|
||||
{
|
||||
GC.KeepAlive(item);
|
||||
}
|
||||
|
||||
foreach (var item in _collection.GetOrderedMetadata<IMetadata2>())
|
||||
{
|
||||
GC.KeepAlive(item);
|
||||
}
|
||||
|
||||
foreach (var item in _collection.GetOrderedMetadata<IMetadata3>())
|
||||
{
|
||||
GC.KeepAlive(item);
|
||||
}
|
||||
|
||||
foreach (var item in _collection.GetOrderedMetadata<IMetadata4>())
|
||||
{
|
||||
GC.KeepAlive(item);
|
||||
}
|
||||
|
||||
foreach (var item in _collection.GetOrderedMetadata<IMetadata5>())
|
||||
{
|
||||
GC.KeepAlive(item);
|
||||
}
|
||||
}
|
||||
|
||||
private interface IMetadata1 { }
|
||||
private interface IMetadata2 { }
|
||||
private interface IMetadata3 { }
|
||||
private interface IMetadata4 { }
|
||||
private interface IMetadata5 { }
|
||||
private class Metadata1 : IMetadata1 { }
|
||||
private class Metadata2 : IMetadata2 { }
|
||||
private class Metadata3 : IMetadata3 { }
|
||||
private class Metadata4 : IMetadata4 { }
|
||||
private class Metadata5 : IMetadata5 { }
|
||||
private class Metadata6 : IMetadata1, IMetadata2 { }
|
||||
private class Metadata7 : IMetadata2, IMetadata3 { }
|
||||
private class Metadata8 : IMetadata4, IMetadata5 { }
|
||||
private class Metadata9 : IMetadata1, IMetadata2 { }
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public abstract class EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private protected RouteEndpoint[] Endpoints;
|
||||
private protected HttpContext[] Requests;
|
||||
|
||||
private protected void SetupEndpoints(params RouteEndpoint[] endpoints)
|
||||
{
|
||||
Endpoints = endpoints;
|
||||
}
|
||||
|
||||
// The older routing implementations retrieve services when they first execute.
|
||||
private protected IServiceProvider CreateServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||
services.AddLogging();
|
||||
services.AddOptions();
|
||||
services.AddRouting();
|
||||
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<EndpointDataSource>(new DefaultEndpointDataSource(Endpoints)));
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private protected DfaMatcherBuilder CreateDfaMatcherBuilder()
|
||||
{
|
||||
return CreateServices().GetRequiredService<DfaMatcherBuilder>();
|
||||
}
|
||||
|
||||
private protected static int[] SampleRequests(int endpointCount, int count)
|
||||
{
|
||||
// This isn't very high tech, but it's at least regular distribution.
|
||||
// We sort the route templates by precedence, so this should result in
|
||||
// an even distribution of the 'complexity' of the routes that are exercised.
|
||||
var frequency = endpointCount / count;
|
||||
if (frequency < 2)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The sample count is too high. This won't produce an accurate sampling" +
|
||||
"of the request data.");
|
||||
}
|
||||
|
||||
var samples = new int[count];
|
||||
for (var i = 0; i < samples.Length; i++)
|
||||
{
|
||||
samples[i] = i * frequency;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private protected void Validate(HttpContext httpContext, Endpoint expected, Endpoint actual)
|
||||
{
|
||||
if (!object.ReferenceEquals(expected, actual))
|
||||
{
|
||||
var message = new StringBuilder();
|
||||
message.AppendLine($"Validation failed for request {Array.IndexOf(Requests, httpContext)}");
|
||||
message.AppendLine($"{httpContext.Request.Method} {httpContext.Request.Path}");
|
||||
message.AppendLine($"expected: '{((RouteEndpoint)expected)?.DisplayName ?? "null"}'");
|
||||
message.AppendLine($"actual: '{((RouteEndpoint)actual)?.DisplayName ?? "null"}'");
|
||||
throw new InvalidOperationException(message.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
protected void AssertUrl(string expectedUrl, string actualUrl)
|
||||
{
|
||||
AssertUrl(expectedUrl, actualUrl, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
protected void AssertUrl(string expectedUrl, string actualUrl, StringComparison stringComparison)
|
||||
{
|
||||
if (!string.Equals(expectedUrl, actualUrl, stringComparison))
|
||||
{
|
||||
throw new InvalidOperationException($"Expected: {expectedUrl}, Actual: {actualUrl}");
|
||||
}
|
||||
}
|
||||
|
||||
protected RouteEndpoint CreateEndpoint(string template, string httpMethod)
|
||||
{
|
||||
return CreateEndpoint(template, metadata: new object[]
|
||||
{
|
||||
new HttpMethodMetadata(new string[]{ httpMethod, }),
|
||||
});
|
||||
}
|
||||
|
||||
protected RouteEndpoint CreateEndpoint(
|
||||
string template,
|
||||
object defaults = null,
|
||||
object constraints = null,
|
||||
object requiredValues = null,
|
||||
int order = 0,
|
||||
string displayName = null,
|
||||
string routeName = null,
|
||||
params object[] metadata)
|
||||
{
|
||||
var endpointMetadata = new List<object>(metadata ?? Array.Empty<object>());
|
||||
endpointMetadata.Add(new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues)));
|
||||
|
||||
return new RouteEndpoint(
|
||||
(context) => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse(template, defaults, constraints),
|
||||
order,
|
||||
new EndpointMetadataCollection(endpointMetadata),
|
||||
displayName);
|
||||
}
|
||||
|
||||
protected (HttpContext httpContext, RouteValueDictionary ambientValues) CreateCurrentRequestContext(
|
||||
object ambientValues = null)
|
||||
{
|
||||
var feature = new EndpointSelectorContext { RouteValues = new RouteValueDictionary(ambientValues) };
|
||||
var context = new DefaultHttpContext();
|
||||
context.Features.Set<IEndpointFeature>(feature);
|
||||
context.Features.Set<IRouteValuesFeature>(feature);
|
||||
|
||||
return (context, feature.RouteValues);
|
||||
}
|
||||
|
||||
protected void CreateOutboundRouteEntry(TreeRouteBuilder treeRouteBuilder, RouteEndpoint endpoint)
|
||||
{
|
||||
var routeValuesAddressMetadata = endpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||
var requiredValues = routeValuesAddressMetadata?.RequiredValues ?? new RouteValueDictionary();
|
||||
|
||||
treeRouteBuilder.MapOutbound(
|
||||
NullRouter.Instance,
|
||||
new RouteTemplate(RoutePatternFactory.Parse(
|
||||
endpoint.RoutePattern.RawText,
|
||||
defaults: endpoint.RoutePattern.Defaults,
|
||||
parameterPolicies: null)),
|
||||
requiredLinkValues: new RouteValueDictionary(requiredValues),
|
||||
routeName: null,
|
||||
order: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// 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.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public partial class LinkGenerationGithubBenchmark
|
||||
{
|
||||
private LinkGenerator _linkGenerator;
|
||||
private TreeRouter _treeRouter;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
private RouteValueDictionary _lookUpValues;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
SetupEndpoints();
|
||||
|
||||
var services = CreateServices();
|
||||
_linkGenerator = services.GetRequiredService<LinkGenerator>();
|
||||
|
||||
// Attribute routing related
|
||||
var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
|
||||
foreach (var endpoint in Endpoints)
|
||||
{
|
||||
CreateOutboundRouteEntry(treeRouteBuilder, endpoint);
|
||||
}
|
||||
_treeRouter = treeRouteBuilder.Build();
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
|
||||
// Get the endpoint to test and pre-populate the lookup values with the defaults
|
||||
// (as they are dynamically generated) and update with other required parameter values.
|
||||
// /repos/{owner}/{repo}/issues/comments/{commentId}
|
||||
var endpointToTest = Endpoints[176];
|
||||
_lookUpValues = new RouteValueDictionary(endpointToTest.RoutePattern.Defaults);
|
||||
_lookUpValues["owner"] = "aspnet";
|
||||
_lookUpValues["repo"] = "routing";
|
||||
_lookUpValues["commentId"] = "20202";
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Baseline()
|
||||
{
|
||||
var url = $"/repos/{_lookUpValues["owner"]}/{_lookUpValues["repo"]}/issues/comments/{_lookUpValues["commentId"]}";
|
||||
AssertUrl("/repos/aspnet/routing/issues/comments/20202", url);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void TreeRouter()
|
||||
{
|
||||
var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
|
||||
_requestContext.HttpContext,
|
||||
ambientValues: _requestContext.AmbientValues,
|
||||
values: new RouteValueDictionary(_lookUpValues)));
|
||||
|
||||
AssertUrl("/repos/aspnet/routing/issues/comments/20202", virtualPathData?.VirtualPath);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetPathByRouteValues(
|
||||
_requestContext.HttpContext,
|
||||
routeName: null,
|
||||
values: _lookUpValues);
|
||||
|
||||
AssertUrl("/repos/aspnet/routing/issues/comments/20202", actualUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,74 @@
|
|||
// 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 BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public class SingleRouteRouteValuesAddressSchemeBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private IEndpointAddressScheme<RouteValuesAddress> _implementation;
|
||||
private TestAddressScheme _baseline;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var template = "Products/Details";
|
||||
var defaults = new { controller = "Products", action = "Details" };
|
||||
var requiredValues = new { controller = "Products", action = "Details" };
|
||||
|
||||
SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues, routeName: "ProductDetails"));
|
||||
var services = CreateServices();
|
||||
_implementation = services.GetRequiredService<IEndpointAddressScheme<RouteValuesAddress>>();
|
||||
_baseline = new TestAddressScheme(Endpoints[0]);
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Baseline()
|
||||
{
|
||||
var actual = _baseline.FindEndpoints(address: 0);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void RouteValues()
|
||||
{
|
||||
var actual = _implementation.FindEndpoints(new RouteValuesAddress
|
||||
{
|
||||
AmbientValues = _requestContext.AmbientValues,
|
||||
ExplicitValues = new RouteValueDictionary(new { controller = "Products", action = "Details" }),
|
||||
RouteName = null
|
||||
});
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void RouteName()
|
||||
{
|
||||
var actual = _implementation.FindEndpoints(new RouteValuesAddress
|
||||
{
|
||||
AmbientValues = _requestContext.AmbientValues,
|
||||
RouteName = "ProductDetails"
|
||||
});
|
||||
}
|
||||
|
||||
private class TestAddressScheme : IEndpointAddressScheme<int>
|
||||
{
|
||||
private readonly Endpoint _endpoint;
|
||||
|
||||
public TestAddressScheme(Endpoint endpoint)
|
||||
{
|
||||
_endpoint = endpoint;
|
||||
}
|
||||
|
||||
public IEnumerable<Endpoint> FindEndpoints(int address)
|
||||
{
|
||||
return new[] { _endpoint };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// 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.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public class SingleRouteWithConstraintsBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private TreeRouter _treeRouter;
|
||||
private LinkGenerator _linkGenerator;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var template = "Customers/Details/{category}/{region}/{id:int}";
|
||||
var defaults = new { controller = "Customers", action = "Details" };
|
||||
var requiredValues = new { controller = "Customers", action = "Details" };
|
||||
|
||||
// Endpoint routing related
|
||||
SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
|
||||
var services = CreateServices();
|
||||
_linkGenerator = services.GetRequiredService<LinkGenerator>();
|
||||
|
||||
// Attribute routing related
|
||||
var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
|
||||
CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
|
||||
_treeRouter = treeRouteBuilder.Build();
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void TreeRouter()
|
||||
{
|
||||
var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
|
||||
_requestContext.HttpContext,
|
||||
ambientValues: _requestContext.AmbientValues,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
})));
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", virtualPathData?.VirtualPath);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetPathByRouteValues(
|
||||
_requestContext.HttpContext,
|
||||
routeName: null,
|
||||
values: new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
});
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public class SingleRouteWithNoParametersBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private TreeRouter _treeRouter;
|
||||
private LinkGenerator _linkGenerator;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var template = "Products/Details";
|
||||
var defaults = new { controller = "Products", action = "Details" };
|
||||
var requiredValues = new { controller = "Products", action = "Details" };
|
||||
|
||||
// Endpoint routing related
|
||||
SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
|
||||
var services = CreateServices();
|
||||
_linkGenerator = services.GetRequiredService<LinkGenerator>();
|
||||
|
||||
// Attribute routing related
|
||||
var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
|
||||
CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
|
||||
_treeRouter = treeRouteBuilder.Build();
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void TreeRouter()
|
||||
{
|
||||
var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
|
||||
_requestContext.HttpContext,
|
||||
ambientValues: _requestContext.AmbientValues,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Products",
|
||||
action = "Details",
|
||||
})));
|
||||
|
||||
AssertUrl("/Products/Details", virtualPathData?.VirtualPath);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetPathByRouteValues(
|
||||
_requestContext.HttpContext,
|
||||
routeName: null,
|
||||
values: new
|
||||
{
|
||||
controller = "Products",
|
||||
action = "Details",
|
||||
});
|
||||
|
||||
AssertUrl("/Products/Details", actualUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// 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.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public class SingleRouteWithParametersBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private TreeRouter _treeRouter;
|
||||
private LinkGenerator _linkGenerator;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var template = "Customers/Details/{category}/{region}/{id}";
|
||||
var defaults = new { controller = "Customers", action = "Details" };
|
||||
var requiredValues = new { controller = "Customers", action = "Details" };
|
||||
|
||||
// Endpoint routing related
|
||||
SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
|
||||
var services = CreateServices();
|
||||
_linkGenerator = services.GetRequiredService<LinkGenerator>();
|
||||
|
||||
// Attribute routing related
|
||||
var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
|
||||
CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
|
||||
_treeRouter = treeRouteBuilder.Build();
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void TreeRouter()
|
||||
{
|
||||
var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
|
||||
_requestContext.HttpContext,
|
||||
ambientValues: _requestContext.AmbientValues,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
})));
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", virtualPathData?.VirtualPath);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetPathByRouteValues(
|
||||
_requestContext.HttpContext,
|
||||
routeName: null,
|
||||
values: new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
});
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public abstract class FastPathTokenizerBenchmarkBase
|
||||
{
|
||||
internal unsafe void NaiveBaseline(string path, PathSegment* segments, int maxCount)
|
||||
{
|
||||
int count = 0;
|
||||
int start = 1; // Paths always start with a leading /
|
||||
int end;
|
||||
while ((end = path.IndexOf('/', start)) >= 0 && count < maxCount)
|
||||
{
|
||||
segments[count++] = new PathSegment(start, end - start);
|
||||
start = end + 1; // resume search after the current character
|
||||
}
|
||||
|
||||
// Residue
|
||||
var length = path.Length - start;
|
||||
if (length > 0 && count < maxCount)
|
||||
{
|
||||
segments[count++] = new PathSegment(start, length);
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe void MinimalBaseline(string path, PathSegment* segments, int maxCount)
|
||||
{
|
||||
var start = 1;
|
||||
var length = path.Length - start;
|
||||
segments[0] = new PathSegment(start, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class FastPathTokenizerEmptyBenchmark : FastPathTokenizerBenchmarkBase
|
||||
{
|
||||
private const int MaxCount = 32;
|
||||
private static readonly string Input = "/";
|
||||
|
||||
// This is super hardcoded implementation for comparison, we dont't expect to do better.
|
||||
[Benchmark(Baseline = true)]
|
||||
public unsafe void Baseline()
|
||||
{
|
||||
var path = Input;
|
||||
var segments = stackalloc PathSegment[MaxCount];
|
||||
|
||||
MinimalBaseline(path, segments, MaxCount);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Implementation()
|
||||
{
|
||||
var path = Input;
|
||||
Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
|
||||
|
||||
FastPathTokenizer.Tokenize(path, segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class FastPathTokenizerLargeBenchmark : FastPathTokenizerBenchmarkBase
|
||||
{
|
||||
private static readonly int MaxCount = 32;
|
||||
private static readonly string Input =
|
||||
"/heeeeeeeeeeyyyyyyyyyyy/this/is/a/string/with/lots/of/segments" +
|
||||
"/hoooooooooooooooooooooooooooooooooow long/do you think it should be?/I think" +
|
||||
"/like/32/segments/is /a/goood/number/dklfl/20303/dlflkf" +
|
||||
"/Im/tired/of/thinking/of/more/things/to/so";
|
||||
|
||||
// This is a naive reference implementation. We expect to do better.
|
||||
[Benchmark(Baseline = true)]
|
||||
public unsafe void Baseline()
|
||||
{
|
||||
var path = Input;
|
||||
var segments = stackalloc PathSegment[MaxCount];
|
||||
|
||||
NaiveBaseline(path, segments, MaxCount);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Implementation()
|
||||
{
|
||||
var path = Input;
|
||||
Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
|
||||
|
||||
FastPathTokenizer.Tokenize(path, segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class FastPathTokenizerPlaintextBenchmark : FastPathTokenizerBenchmarkBase
|
||||
{
|
||||
private const int MaxCount = 32;
|
||||
private static readonly string Input = "/plaintext";
|
||||
|
||||
// This is super hardcoded implementation for comparison, we dont't expect to do better.
|
||||
[Benchmark(Baseline = true)]
|
||||
public unsafe void Baseline()
|
||||
{
|
||||
var path = Input;
|
||||
var segments = stackalloc PathSegment[MaxCount];
|
||||
|
||||
MinimalBaseline(path, segments, MaxCount);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Implementation()
|
||||
{
|
||||
var path = Input;
|
||||
Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
|
||||
|
||||
FastPathTokenizer.Tokenize(path, segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class FastPathTokenizerSmallBenchmark : FastPathTokenizerBenchmarkBase
|
||||
{
|
||||
private const int MaxCount = 32;
|
||||
private static readonly string Input = "/hello/world/cool";
|
||||
|
||||
// This is a naive reference implementation. We expect to do better.
|
||||
[Benchmark(Baseline = true)]
|
||||
public unsafe void Baseline()
|
||||
{
|
||||
var path = Input;
|
||||
var segments = stackalloc PathSegment[MaxCount];
|
||||
|
||||
NaiveBaseline(path, segments, MaxCount);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Implementation()
|
||||
{
|
||||
var path = Input;
|
||||
Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
|
||||
|
||||
FastPathTokenizer.Tokenize(path, segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
// 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 BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class JumpTableMultipleEntryBenchmark
|
||||
{
|
||||
private string[] _strings;
|
||||
private PathSegment[] _segments;
|
||||
|
||||
private JumpTable _linearSearch;
|
||||
private JumpTable _dictionary;
|
||||
private JumpTable _trie;
|
||||
private JumpTable _vectorTrie;
|
||||
|
||||
// All factors of 100 to support sampling
|
||||
[Params(2, 5, 10, 25, 50, 100)]
|
||||
public int Count;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_strings = GetStrings(100);
|
||||
_segments = new PathSegment[100];
|
||||
|
||||
for (var i = 0; i < _strings.Length; i++)
|
||||
{
|
||||
_segments[i] = new PathSegment(0, _strings[i].Length);
|
||||
}
|
||||
|
||||
var samples = new int[Count];
|
||||
for (var i = 0; i < samples.Length; i++)
|
||||
{
|
||||
samples[i] = i * (_strings.Length / Count);
|
||||
}
|
||||
|
||||
var entries = new List<(string text, int _)>();
|
||||
for (var i = 0; i < samples.Length; i++)
|
||||
{
|
||||
entries.Add((_strings[samples[i]], i));
|
||||
}
|
||||
|
||||
_linearSearch = new LinearSearchJumpTable(0, -1, entries.ToArray());
|
||||
_dictionary = new DictionaryJumpTable(0, -1, entries.ToArray());
|
||||
_trie = new ILEmitTrieJumpTable(0, -1, entries.ToArray(), vectorize: false, _dictionary);
|
||||
_vectorTrie = new ILEmitTrieJumpTable(0, -1, entries.ToArray(), vectorize: true, _dictionary);
|
||||
}
|
||||
|
||||
// This baseline is similar to SingleEntryJumpTable. We just want
|
||||
// something stable to compare against.
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = 100)]
|
||||
public int Baseline()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
var @string = strings[i];
|
||||
var segment = segments[i];
|
||||
|
||||
if (segment.Length == 0)
|
||||
{
|
||||
destination = -1;
|
||||
}
|
||||
else if (segment.Length != @string.Length)
|
||||
{
|
||||
destination = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
destination = string.Compare(
|
||||
@string,
|
||||
segment.Start,
|
||||
@string,
|
||||
0,
|
||||
segment.Length,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 100)]
|
||||
public int LinearSearch()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _linearSearch.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 100)]
|
||||
public int Dictionary()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _dictionary.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 100)]
|
||||
public int Trie()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _trie.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 100)]
|
||||
public int VectorTrie()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _vectorTrie.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static string[] GetStrings(int count)
|
||||
{
|
||||
var strings = new string[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var guid = Guid.NewGuid().ToString();
|
||||
|
||||
// Between 5 and 36 characters
|
||||
var text = guid.Substring(0, Math.Max(5, Math.Min(i, 36)));
|
||||
if (char.IsDigit(text[0]))
|
||||
{
|
||||
// Convert first character to a letter.
|
||||
text = ((char)(text[0] + ('G' - '0'))) + text.Substring(1);
|
||||
}
|
||||
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
// Lowercase half of them
|
||||
text = text.ToLowerInvariant();
|
||||
}
|
||||
|
||||
strings[i] = text;
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class JumpTableSingleEntryBenchmark
|
||||
{
|
||||
private JumpTable _default;
|
||||
private JumpTable _trie;
|
||||
private JumpTable _vectorTrie;
|
||||
private JumpTable _ascii;
|
||||
|
||||
private string[] _strings;
|
||||
private PathSegment[] _segments;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_default = new SingleEntryJumpTable(0, -1, "hello-world", 1);
|
||||
_trie = new ILEmitTrieJumpTable(0, -1, new[] { ("hello-world", 1), }, vectorize: false, _default);
|
||||
_vectorTrie = new ILEmitTrieJumpTable(0, -1, new[] { ("hello-world", 1), }, vectorize: true, _default);
|
||||
_ascii = new SingleEntryAsciiJumpTable(0, -1, "hello-world", 1);
|
||||
|
||||
_strings = new string[]
|
||||
{
|
||||
"index/foo/2",
|
||||
"index/hello-world1/2",
|
||||
"index/hello-world/2",
|
||||
"index//2",
|
||||
"index/hillo-goodbye/2",
|
||||
};
|
||||
_segments = new PathSegment[]
|
||||
{
|
||||
new PathSegment(6, 3),
|
||||
new PathSegment(6, 12),
|
||||
new PathSegment(6, 11),
|
||||
new PathSegment(6, 0),
|
||||
new PathSegment(6, 13),
|
||||
};
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = 5)]
|
||||
public int Baseline()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
int destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
var @string = strings[i];
|
||||
var segment = segments[i];
|
||||
|
||||
if (segment.Length == 0)
|
||||
{
|
||||
destination = -1;
|
||||
}
|
||||
else if (segment.Length != "hello-world".Length)
|
||||
{
|
||||
destination = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
destination = string.Compare(
|
||||
@string,
|
||||
segment.Start,
|
||||
"hello-world",
|
||||
0,
|
||||
segment.Length,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 5)]
|
||||
public int Default()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _default.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 5)]
|
||||
public int Ascii()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _ascii.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 5)]
|
||||
public int Trie()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _trie.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 5)]
|
||||
public int VectorTrie()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _vectorTrie.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class JumpTableZeroEntryBenchmark
|
||||
{
|
||||
private JumpTable _table;
|
||||
private string[] _strings;
|
||||
private PathSegment[] _segments;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_table = new ZeroEntryJumpTable(0, -1);
|
||||
_strings = new string[]
|
||||
{
|
||||
"index/foo/2",
|
||||
"index/hello-world1/2",
|
||||
"index/hello-world/2",
|
||||
"index//2",
|
||||
"index/hillo-goodbye/2",
|
||||
};
|
||||
_segments = new PathSegment[]
|
||||
{
|
||||
new PathSegment(6, 3),
|
||||
new PathSegment(6, 12),
|
||||
new PathSegment(6, 11),
|
||||
new PathSegment(6, 0),
|
||||
new PathSegment(6, 13),
|
||||
};
|
||||
}
|
||||
|
||||
[Benchmark(Baseline=true, OperationsPerInvoke = 5)]
|
||||
public int Baseline()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = segments[i].Length == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 5)]
|
||||
public int Implementation()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _table.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Generated from https://github.com/Azure/azure-rest-api-specs
|
||||
public class MatcherAzureBenchmark : MatcherAzureBenchmarkBase
|
||||
{
|
||||
private const int SampleCount = 100;
|
||||
|
||||
private BarebonesMatcher _baseline;
|
||||
private Matcher _dfa;
|
||||
|
||||
private int[] _samples;
|
||||
private EndpointSelectorContext _feature;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
SetupEndpoints();
|
||||
|
||||
SetupRequests();
|
||||
|
||||
// The perf is kinda slow for these benchmarks, so we do some sampling
|
||||
// of the request data.
|
||||
_samples = SampleRequests(EndpointCount, SampleCount);
|
||||
|
||||
_baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
|
||||
_dfa = SetupMatcher(CreateDfaMatcherBuilder());
|
||||
|
||||
_feature = new EndpointSelectorContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = SampleCount)]
|
||||
public async Task Baseline()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < SampleCount; i++)
|
||||
{
|
||||
var sample = _samples[i];
|
||||
var httpContext = Requests[sample];
|
||||
await _baseline.Matchers[sample].MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, Endpoints[sample], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = SampleCount)]
|
||||
public async Task Dfa()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < SampleCount; i++)
|
||||
{
|
||||
var sample = _samples[i];
|
||||
var httpContext = Requests[sample];
|
||||
await _dfa.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, Endpoints[sample], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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 BenchmarkDotNet.Attributes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Generated from https://github.com/APIs-guru/openapi-directory
|
||||
// Use https://editor2.swagger.io/ to convert from yaml to json-
|
||||
public class MatcherBuilderAzureBenchmark : MatcherAzureBenchmarkBase
|
||||
{
|
||||
private IServiceProvider _services;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
SetupEndpoints();
|
||||
|
||||
_services = CreateServices();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Dfa()
|
||||
{
|
||||
var builder = _services.GetRequiredService<DfaMatcherBuilder>();
|
||||
SetupMatcher(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 BenchmarkDotNet.Attributes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Generated from https://github.com/APIs-guru/openapi-directory
|
||||
// Use https://editor2.swagger.io/ to convert from yaml to json-
|
||||
public class MatcherBuilderGithubBenchmark : MatcherGithubBenchmarkBase
|
||||
{
|
||||
private IServiceProvider _services;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
SetupEndpoints();
|
||||
|
||||
_services = CreateServices();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Dfa()
|
||||
{
|
||||
var builder = _services.GetRequiredService<DfaMatcherBuilder>();
|
||||
SetupMatcher(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
// 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 BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.TestObjects;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public partial class MatcherBuilderMultipleEntryBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private IServiceProvider _services;
|
||||
private List<MatcherPolicy> _policies;
|
||||
private ILoggerFactory _loggerFactory;
|
||||
private DefaultEndpointSelector _selector;
|
||||
private DefaultParameterPolicyFactory _parameterPolicyFactory;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
Endpoints = new RouteEndpoint[10];
|
||||
Endpoints[0] = CreateEndpoint("/product", "GET");
|
||||
Endpoints[1] = CreateEndpoint("/product/{id}", "GET");
|
||||
|
||||
Endpoints[2] = CreateEndpoint("/account", "GET");
|
||||
Endpoints[3] = CreateEndpoint("/account/{id}");
|
||||
Endpoints[4] = CreateEndpoint("/account/{id}", "POST");
|
||||
Endpoints[5] = CreateEndpoint("/account/{id}", "UPDATE");
|
||||
|
||||
Endpoints[6] = CreateEndpoint("/v2/account", "GET");
|
||||
Endpoints[7] = CreateEndpoint("/v2/account/{id}");
|
||||
Endpoints[8] = CreateEndpoint("/v2/account/{id}", "POST");
|
||||
Endpoints[9] = CreateEndpoint("/v2/account/{id}", "UPDATE");
|
||||
|
||||
// Define an unordered mixture of policies that implement INodeBuilderPolicy,
|
||||
// IEndpointComparerPolicy and/or IEndpointSelectorPolicy
|
||||
_policies = new List<MatcherPolicy>()
|
||||
{
|
||||
CreateNodeBuilderPolicy(4),
|
||||
CreateUberPolicy(2),
|
||||
CreateNodeBuilderPolicy(3),
|
||||
CreateEndpointComparerPolicy(5),
|
||||
CreateNodeBuilderPolicy(1),
|
||||
CreateEndpointSelectorPolicy(9),
|
||||
CreateEndpointComparerPolicy(7),
|
||||
CreateNodeBuilderPolicy(6),
|
||||
CreateEndpointSelectorPolicy(10),
|
||||
CreateUberPolicy(12),
|
||||
CreateEndpointComparerPolicy(11)
|
||||
};
|
||||
_loggerFactory = NullLoggerFactory.Instance;
|
||||
_selector = new DefaultEndpointSelector();
|
||||
_parameterPolicyFactory = new DefaultParameterPolicyFactory(Options.Create(new RouteOptions()), new TestServiceProvider());
|
||||
|
||||
_services = CreateServices();
|
||||
}
|
||||
|
||||
private Matcher SetupMatcher(MatcherBuilder builder)
|
||||
{
|
||||
for (int i = 0; i < Endpoints.Length; i++)
|
||||
{
|
||||
builder.AddEndpoint(Endpoints[i]);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Dfa()
|
||||
{
|
||||
var builder = _services.GetRequiredService<DfaMatcherBuilder>();
|
||||
SetupMatcher(builder);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Constructor_Policies()
|
||||
{
|
||||
new DfaMatcherBuilder(_loggerFactory, _parameterPolicyFactory, _selector, _policies);
|
||||
}
|
||||
|
||||
private static MatcherPolicy CreateNodeBuilderPolicy(int order)
|
||||
{
|
||||
return new TestNodeBuilderPolicy(order);
|
||||
}
|
||||
private static MatcherPolicy CreateEndpointComparerPolicy(int order)
|
||||
{
|
||||
return new TestEndpointComparerPolicy(order);
|
||||
}
|
||||
|
||||
private static MatcherPolicy CreateEndpointSelectorPolicy(int order)
|
||||
{
|
||||
return new TestEndpointSelectorPolicy(order);
|
||||
}
|
||||
|
||||
private static MatcherPolicy CreateUberPolicy(int order)
|
||||
{
|
||||
return new TestUberPolicy(order);
|
||||
}
|
||||
|
||||
private class TestUberPolicy : TestMatcherPolicyBase, INodeBuilderPolicy, IEndpointComparerPolicy
|
||||
{
|
||||
public TestUberPolicy(int order) : base(order)
|
||||
{
|
||||
}
|
||||
|
||||
public IComparer<Endpoint> Comparer => new TestEndpointComparer();
|
||||
|
||||
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestNodeBuilderPolicy : TestMatcherPolicyBase, INodeBuilderPolicy
|
||||
{
|
||||
public TestNodeBuilderPolicy(int order) : base(order)
|
||||
{
|
||||
}
|
||||
|
||||
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestEndpointComparerPolicy : TestMatcherPolicyBase, IEndpointComparerPolicy
|
||||
{
|
||||
public TestEndpointComparerPolicy(int order) : base(order)
|
||||
{
|
||||
}
|
||||
|
||||
public IComparer<Endpoint> Comparer => new TestEndpointComparer();
|
||||
|
||||
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestEndpointSelectorPolicy : TestMatcherPolicyBase, IEndpointSelectorPolicy
|
||||
{
|
||||
public TestEndpointSelectorPolicy(int order) : base(order)
|
||||
{
|
||||
}
|
||||
|
||||
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class TestMatcherPolicyBase : MatcherPolicy
|
||||
{
|
||||
private int _order;
|
||||
|
||||
protected TestMatcherPolicyBase(int order)
|
||||
{
|
||||
_order = order;
|
||||
}
|
||||
|
||||
public override int Order { get { return _order; } }
|
||||
}
|
||||
|
||||
private class TestEndpointComparer : IComparer<Endpoint>
|
||||
{
|
||||
public int Compare(Endpoint x, Endpoint y)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// 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 BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Generated from https://github.com/APIs-guru/openapi-directory
|
||||
// Use https://editor2.swagger.io/ to convert from yaml to json-
|
||||
public class MatcherGithubBenchmark : MatcherGithubBenchmarkBase
|
||||
{
|
||||
private BarebonesMatcher _baseline;
|
||||
private Matcher _dfa;
|
||||
|
||||
private EndpointSelectorContext _feature;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
SetupEndpoints();
|
||||
|
||||
SetupRequests();
|
||||
|
||||
_baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
|
||||
_dfa = SetupMatcher(CreateDfaMatcherBuilder());
|
||||
|
||||
_feature = new EndpointSelectorContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = EndpointCount)]
|
||||
public async Task Baseline()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < EndpointCount; i++)
|
||||
{
|
||||
var httpContext = Requests[i];
|
||||
await _baseline.Matchers[i].MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, Endpoints[i], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark( OperationsPerInvoke = EndpointCount)]
|
||||
public async Task Dfa()
|
||||
{
|
||||
var feature = _feature;
|
||||
for (var i = 0; i < EndpointCount; i++)
|
||||
{
|
||||
var httpContext = Requests[i];
|
||||
await _dfa.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, Endpoints[i], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,95 @@
|
|||
// 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 BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Just like TechEmpower Plaintext
|
||||
public partial class MatcherSingleEntryBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private const int SampleCount = 100;
|
||||
|
||||
private BarebonesMatcher _baseline;
|
||||
private Matcher _dfa;
|
||||
private Matcher _route;
|
||||
private Matcher _tree;
|
||||
|
||||
private EndpointSelectorContext _feature;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
Endpoints = new RouteEndpoint[1];
|
||||
Endpoints[0] = CreateEndpoint("/plaintext");
|
||||
|
||||
Requests = new HttpContext[1];
|
||||
Requests[0] = new DefaultHttpContext();
|
||||
Requests[0].RequestServices = CreateServices();
|
||||
Requests[0].Request.Path = "/plaintext";
|
||||
|
||||
_baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
|
||||
_dfa = SetupMatcher(CreateDfaMatcherBuilder());
|
||||
_route = SetupMatcher(new RouteMatcherBuilder());
|
||||
_tree = SetupMatcher(new TreeRouterMatcherBuilder());
|
||||
|
||||
_feature = new EndpointSelectorContext();
|
||||
}
|
||||
|
||||
private Matcher SetupMatcher(MatcherBuilder builder)
|
||||
{
|
||||
builder.AddEndpoint(Endpoints[0]);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async Task Baseline()
|
||||
{
|
||||
var feature = _feature;
|
||||
var httpContext = Requests[0];
|
||||
|
||||
await _baseline.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, Endpoints[0], feature.Endpoint);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task Dfa()
|
||||
{
|
||||
var feature = _feature;
|
||||
var httpContext = Requests[0];
|
||||
|
||||
await _dfa.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, Endpoints[0], feature.Endpoint);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task LegacyTreeRouter()
|
||||
{
|
||||
var feature = _feature;
|
||||
|
||||
var httpContext = Requests[0];
|
||||
|
||||
// This is required to make the legacy router implementation work with global routing.
|
||||
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _tree.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, Endpoints[0], feature.Endpoint);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task LegacyRouter()
|
||||
{
|
||||
var feature = _feature;
|
||||
var httpContext = Requests[0];
|
||||
|
||||
// This is required to make the legacy router implementation work with global routing.
|
||||
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
await _route.MatchAsync(httpContext, feature);
|
||||
Validate(httpContext, Endpoints[0], feature.Endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class RouteEndpointAzureBenchmark : MatcherAzureBenchmarkBase
|
||||
{
|
||||
[Benchmark]
|
||||
public void CreateEndpoints()
|
||||
{
|
||||
SetupEndpoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// A test-only matcher implementation - used as a baseline for simpler
|
||||
// perf tests. The idea with this matcher is that we can cheat on the requirements
|
||||
// to establish a lower bound for perf comparisons.
|
||||
internal sealed class TrivialMatcher : Matcher
|
||||
{
|
||||
private readonly RouteEndpoint _endpoint;
|
||||
private readonly Candidate[] _candidates;
|
||||
|
||||
public TrivialMatcher(RouteEndpoint endpoint)
|
||||
{
|
||||
_endpoint = endpoint;
|
||||
|
||||
_candidates = new Candidate[] { new Candidate(endpoint), };
|
||||
}
|
||||
|
||||
public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var path = httpContext.Request.Path.Value;
|
||||
if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
context.Endpoint = _endpoint;
|
||||
context.RouteValues = new RouteValueDictionary();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// This is here so this can be tested alongside DFA matcher.
|
||||
internal Candidate[] FindCandidateSet(string path, ReadOnlySpan<PathSegment> segments)
|
||||
{
|
||||
if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return _candidates;
|
||||
}
|
||||
|
||||
return Array.Empty<Candidate>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// 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.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
internal class TrivialMatcherBuilder : MatcherBuilder
|
||||
{
|
||||
private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
|
||||
|
||||
public override void AddEndpoint(RouteEndpoint endpoint)
|
||||
{
|
||||
_endpoints.Add(endpoint);
|
||||
}
|
||||
|
||||
public override Matcher Build()
|
||||
{
|
||||
return new TrivialMatcher(_endpoints.Last());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,50 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Microsoft.AspNetCore.Routing</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Some sources are shared with the unit test so we can benchmark some 'test only' implementations
|
||||
for perf comparisons.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matching\BarebonesMatcher.cs">
|
||||
<Link>Matching\BarebonesMatcher.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matching\BarebonesMatcherBuilder.cs">
|
||||
<Link>Matching\BarebonesMatcherBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matching\RouteMatcher.cs">
|
||||
<Link>Matching\RouteMatcher.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matching\RouteMatcherBuilder.cs">
|
||||
<Link>Matching\RouteMatcherBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matching\TreeRouterMatcher.cs">
|
||||
<Link>Matching\TreeRouterMatcher.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\Matching\TreeRouterMatcherBuilder.cs">
|
||||
<Link>Matching\TreeRouterMatcherBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\test\Microsoft.AspNetCore.Routing.Tests\TestObjects\TestServiceProvider.cs" Link="Matching\TestServiceProvider.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Performance
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark]
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class RouteValueDictionaryBenchmark
|
||||
{
|
||||
private RouteValueDictionary _arrayValues;
|
||||
private RouteValueDictionary _propertyValues;
|
||||
|
||||
// We modify the route value dictionaries in many of these benchmarks.
|
||||
[IterationSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_arrayValues = new RouteValueDictionary()
|
||||
{
|
||||
{ "action", "Index" },
|
||||
{ "controller", "Home" },
|
||||
{ "id", "17" },
|
||||
};
|
||||
_propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary AddSingleItem()
|
||||
{
|
||||
var dictionary = new RouteValueDictionary
|
||||
{
|
||||
{ "action", "Index" }
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary AddThreeItems()
|
||||
{
|
||||
var dictionary = new RouteValueDictionary
|
||||
{
|
||||
{ "action", "Index" },
|
||||
{ "controller", "Home" },
|
||||
{ "id", "15" }
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary ConditionalAdd_ContainsKeyAdd()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
|
||||
if (!dictionary.ContainsKey("action"))
|
||||
{
|
||||
dictionary.Add("action", "Index");
|
||||
}
|
||||
|
||||
if (!dictionary.ContainsKey("controller"))
|
||||
{
|
||||
dictionary.Add("controller", "Home");
|
||||
}
|
||||
|
||||
if (!dictionary.ContainsKey("area"))
|
||||
{
|
||||
dictionary.Add("area", "Admin");
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary ConditionalAdd_TryAdd()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
|
||||
dictionary.TryAdd("action", "Index");
|
||||
dictionary.TryAdd("controller", "Home");
|
||||
dictionary.TryAdd("area", "Admin");
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary ForEachThreeItems_Array()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
foreach (var kvp in dictionary)
|
||||
{
|
||||
GC.KeepAlive(kvp.Value);
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary ForEachThreeItems_Properties()
|
||||
{
|
||||
var dictionary = _propertyValues;
|
||||
foreach (var kvp in dictionary)
|
||||
{
|
||||
GC.KeepAlive(kvp.Value);
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary GetThreeItems_Array()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
GC.KeepAlive(dictionary["action"]);
|
||||
GC.KeepAlive(dictionary["controller"]);
|
||||
GC.KeepAlive(dictionary["id"]);
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary GetThreeItems_Properties()
|
||||
{
|
||||
var dictionary = _propertyValues;
|
||||
GC.KeepAlive(dictionary["action"]);
|
||||
GC.KeepAlive(dictionary["controller"]);
|
||||
GC.KeepAlive(dictionary["id"]);
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary SetSingleItem()
|
||||
{
|
||||
var dictionary = new RouteValueDictionary
|
||||
{
|
||||
["action"] = "Index"
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary SetExistingItem()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
dictionary["action"] = "About";
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary SetThreeItems()
|
||||
{
|
||||
var dictionary = new RouteValueDictionary
|
||||
{
|
||||
["action"] = "Index",
|
||||
["controller"] = "Home",
|
||||
["id"] = "15"
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary TryGetValueThreeItems_Array()
|
||||
{
|
||||
var dictionary = _arrayValues;
|
||||
dictionary.TryGetValue("action", out var action);
|
||||
dictionary.TryGetValue("controller", out var controller);
|
||||
dictionary.TryGetValue("id", out var id);
|
||||
GC.KeepAlive(action);
|
||||
GC.KeepAlive(controller);
|
||||
GC.KeepAlive(id);
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public RouteValueDictionary TryGetValueThreeItems_Properties()
|
||||
{
|
||||
var dictionary = _propertyValues;
|
||||
dictionary.TryGetValue("action", out var action);
|
||||
dictionary.TryGetValue("controller", out var controller);
|
||||
dictionary.TryGetValue("id", out var id);
|
||||
GC.KeepAlive(action);
|
||||
GC.KeepAlive(controller);
|
||||
GC.KeepAlive(id);
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,15 @@ 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>
|
||||
dotnet run -c Release --framework <tfm> <benchmark_name>
|
||||
```
|
||||
|
||||
To run all benchmarks use '*' as the name.
|
||||
```
|
||||
dotnet run -c Release --framework <tfm> *
|
||||
```
|
||||
|
||||
If you run without any parameters, you'll be offered the list of all benchmarks and get to choose.
|
||||
```
|
||||
dotnet run -c Release
|
||||
dotnet run -c Release --framework <tfm>
|
||||
```
|
||||
|
|
@ -2,45 +2,49 @@
|
|||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- These package versions may be overridden or updated by automation. -->
|
||||
<PropertyGroup Label="Package Versions: Auto">
|
||||
<PropertyGroup Label="Package Versions">
|
||||
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
|
||||
<InternalAspNetCoreSdkPackageVersion>2.1.3-rtm-15802</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
||||
<MicrosoftNETCoreApp21PackageVersion>2.1.2</MicrosoftNETCoreApp21PackageVersion>
|
||||
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview2-20181011.10</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftAspNetCoreAppPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreAppPackageVersion>
|
||||
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHostingPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreHttpPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
||||
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview3-35496</MicrosoftAspNetCoreTestingPackageVersion>
|
||||
<MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>
|
||||
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
|
||||
<MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion>
|
||||
<MicrosoftExtensionsConfigurationPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsConfigurationPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
||||
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
|
||||
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||
<MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsLoggingPackageVersion>
|
||||
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsLoggingTestingPackageVersion>
|
||||
<MicrosoftExtensionsObjectPoolPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsObjectPoolPackageVersion>
|
||||
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsOptionsPackageVersion>
|
||||
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
|
||||
<MicrosoftExtensionsWebEncodersPackageVersion>2.2.0-preview3-35496</MicrosoftExtensionsWebEncodersPackageVersion>
|
||||
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
|
||||
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
|
||||
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-27008-03</MicrosoftNETCoreApp22PackageVersion>
|
||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||
<MoqPackageVersion>4.7.49</MoqPackageVersion>
|
||||
<MoqPackageVersion>4.10.0</MoqPackageVersion>
|
||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
|
||||
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
|
||||
<SystemReflectionEmitLightweightPackageVersion>4.3.0</SystemReflectionEmitLightweightPackageVersion>
|
||||
<SystemReflectionEmitPackageVersion>4.3.0</SystemReflectionEmitPackageVersion>
|
||||
<XunitAnalyzersPackageVersion>0.10.0</XunitAnalyzersPackageVersion>
|
||||
<XunitPackageVersion>2.3.1</XunitPackageVersion>
|
||||
<XunitRunnerVisualStudioPackageVersion>2.4.0-beta.1.build3945</XunitRunnerVisualStudioPackageVersion>
|
||||
<XunitRunnerVisualStudioPackageVersion>2.4.0</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>
|
||||
<PropertyGroup Label="Package Versions: Pinned" />
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,17 @@
|
|||
<PropertyGroup>
|
||||
<!-- These properties are use by the automation that updates dependencies.props -->
|
||||
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
|
||||
<LineupPackageVersion>2.1.0-rc1-*</LineupPackageVersion>
|
||||
<LineupPackageVersion>2.2.0-*</LineupPackageVersion>
|
||||
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp22PackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableBenchmarkValidation>true</EnableBenchmarkValidation>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.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)) });
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RoutingSample.Web
|
||||
{
|
||||
public static class RouteBuilderExtensions
|
||||
{
|
||||
public static IRouteBuilder AddPrefixRoute(
|
||||
this IRouteBuilder routeBuilder,
|
||||
string prefix,
|
||||
IRouteHandler handler)
|
||||
{
|
||||
routeBuilder.Routes.Add(new PrefixRoute(handler, prefix));
|
||||
return routeBuilder;
|
||||
}
|
||||
|
||||
public static IRouteBuilder MapLocaleRoute(
|
||||
this IRouteBuilder routeBuilder,
|
||||
string locale,
|
||||
string routeTemplate,
|
||||
object defaults)
|
||||
{
|
||||
var defaultsDictionary = new RouteValueDictionary(defaults);
|
||||
defaultsDictionary.Add("locale", locale);
|
||||
|
||||
var constraintResolver = routeBuilder.ServiceProvider.GetService<IInlineConstraintResolver>();
|
||||
|
||||
var route = new Route(
|
||||
target: routeBuilder.DefaultHandler,
|
||||
routeTemplate: routeTemplate,
|
||||
defaults: defaultsDictionary,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: constraintResolver);
|
||||
routeBuilder.Routes.Add(route);
|
||||
|
||||
return routeBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
// 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.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace RoutingSandbox
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public const string EndpointRoutingScenario = "endpointrouting";
|
||||
public const string RouterScenario = "router";
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var webHost = GetWebHostBuilder(args).Build();
|
||||
webHost.Run();
|
||||
}
|
||||
|
||||
// For unit testing
|
||||
public static IWebHostBuilder GetWebHostBuilder(string[] args)
|
||||
{
|
||||
string scenario;
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Choose a sample to run:");
|
||||
Console.WriteLine($"1. {EndpointRoutingScenario}");
|
||||
Console.WriteLine($"2. {RouterScenario}");
|
||||
Console.WriteLine();
|
||||
|
||||
scenario = Console.ReadLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
scenario = args[0];
|
||||
}
|
||||
|
||||
Type startupType;
|
||||
switch (scenario)
|
||||
{
|
||||
case "1":
|
||||
case EndpointRoutingScenario:
|
||||
startupType = typeof(UseEndpointRoutingStartup);
|
||||
break;
|
||||
|
||||
case "2":
|
||||
case RouterScenario:
|
||||
startupType = typeof(UseRouterStartup);
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"unknown scenario {scenario}");
|
||||
Console.WriteLine($"usage: dotnet run -- ({EndpointRoutingScenario}|{RouterScenario})");
|
||||
throw new InvalidOperationException();
|
||||
|
||||
}
|
||||
|
||||
return new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseIISIntegration()
|
||||
.ConfigureLogging(b =>
|
||||
{
|
||||
b.AddConsole();
|
||||
b.SetMinimumLevel(LogLevel.Critical);
|
||||
})
|
||||
.UseContentRoot(Environment.CurrentDirectory)
|
||||
.UseStartup(startupType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;netcoreapp2.0</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net461</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
@ -10,8 +10,10 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace RoutingSandbox
|
||||
{
|
||||
public class UseEndpointRoutingStartup
|
||||
{
|
||||
private static readonly byte[] _homePayload = Encoding.UTF8.GetBytes("Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext");
|
||||
private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var endpointDataSource = new DefaultEndpointDataSource(new[]
|
||||
{
|
||||
new RouteEndpoint((httpContext) =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
var payloadLength = _homePayload.Length;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
response.ContentLength = payloadLength;
|
||||
return response.Body.WriteAsync(_homePayload, 0, payloadLength);
|
||||
},
|
||||
RoutePatternFactory.Parse("/"),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
"Home"),
|
||||
new RouteEndpoint((httpContext) =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
var payloadLength = _helloWorldPayload.Length;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
response.ContentLength = payloadLength;
|
||||
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
||||
},
|
||||
RoutePatternFactory.Parse("/plaintext"),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
"Plaintext"),
|
||||
new RouteEndpoint((httpContext) =>
|
||||
{
|
||||
using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
|
||||
{
|
||||
var graphWriter = httpContext.RequestServices.GetRequiredService<DfaGraphWriter>();
|
||||
var dataSource = httpContext.RequestServices.GetRequiredService<CompositeEndpointDataSource>();
|
||||
graphWriter.Write(dataSource, writer);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
RoutePatternFactory.Parse("/graph"),
|
||||
0,
|
||||
new EndpointMetadataCollection(new HttpMethodMetadata(new[]{ "GET", })),
|
||||
"DFA Graph"),
|
||||
});
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(endpointDataSource));
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseEndpointRouting();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Imagine some more stuff here...
|
||||
|
||||
app.UseEndpoint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RoutingSandbox
|
||||
{
|
||||
public class UseRouterStartup
|
||||
{
|
||||
private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddRouting();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder 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)) });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>GraphViz UI</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Your App's Routing state machine</h2>
|
||||
<h2 id="loading">Loading...</h2>
|
||||
<div id="graph-yo"></div>
|
||||
|
||||
<!-- Using https://github.com/mdaines/viz.js -->
|
||||
<script src="/viz.js"></script>
|
||||
<script src="/full.render.js"></script>
|
||||
<script type="text/javascript">
|
||||
let viz = new Viz();
|
||||
|
||||
fetch('/graph')
|
||||
.then(function (response) {
|
||||
return response.text();
|
||||
})
|
||||
.then(function (graph) {
|
||||
return viz.renderSVGElement(graph);
|
||||
})
|
||||
.then(function (element) {
|
||||
document.getElementById('loading').remove();
|
||||
document.getElementById('graph-yo').appendChild(element);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error(error);
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
Viz.js 2.0.0 (Graphviz 2.40.1, Expat 2.2.5, Emscripten 1.37.36)
|
||||
Copyright (c) 2014-2018 Michael Daines
|
||||
Licensed under MIT license
|
||||
|
||||
This distribution contains other software in object code form:
|
||||
|
||||
Graphviz
|
||||
Licensed under Eclipse Public License - v 1.0
|
||||
http://www.graphviz.org
|
||||
|
||||
Expat
|
||||
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd and Clark Cooper
|
||||
Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers.
|
||||
Licensed under MIT license
|
||||
http://www.libexpat.org
|
||||
|
||||
zlib
|
||||
Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler
|
||||
http://www.zlib.net/zlib_license.html
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.Viz = factory());
|
||||
}(this, (function () { 'use strict';
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
|
||||
return typeof obj;
|
||||
} : function (obj) {
|
||||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
||||
};
|
||||
|
||||
var classCallCheck = function (instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
};
|
||||
|
||||
var createClass = function () {
|
||||
function defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var descriptor = props[i];
|
||||
descriptor.enumerable = descriptor.enumerable || false;
|
||||
descriptor.configurable = true;
|
||||
if ("value" in descriptor) descriptor.writable = true;
|
||||
Object.defineProperty(target, descriptor.key, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return function (Constructor, protoProps, staticProps) {
|
||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||
if (staticProps) defineProperties(Constructor, staticProps);
|
||||
return Constructor;
|
||||
};
|
||||
}();
|
||||
|
||||
var _extends = Object.assign || function (target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i];
|
||||
|
||||
for (var key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
var WorkerWrapper = function () {
|
||||
function WorkerWrapper(worker) {
|
||||
var _this = this;
|
||||
|
||||
classCallCheck(this, WorkerWrapper);
|
||||
|
||||
this.worker = worker;
|
||||
this.listeners = [];
|
||||
this.nextId = 0;
|
||||
|
||||
this.worker.addEventListener('message', function (event) {
|
||||
var id = event.data.id;
|
||||
var error = event.data.error;
|
||||
var result = event.data.result;
|
||||
|
||||
_this.listeners[id](error, result);
|
||||
delete _this.listeners[id];
|
||||
});
|
||||
}
|
||||
|
||||
createClass(WorkerWrapper, [{
|
||||
key: 'render',
|
||||
value: function render(src, options) {
|
||||
var _this2 = this;
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var id = _this2.nextId++;
|
||||
|
||||
_this2.listeners[id] = function (error, result) {
|
||||
if (error) {
|
||||
reject(new Error(error.message, error.fileName, error.lineNumber));
|
||||
return;
|
||||
}
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
_this2.worker.postMessage({ id: id, src: src, options: options });
|
||||
});
|
||||
}
|
||||
}]);
|
||||
return WorkerWrapper;
|
||||
}();
|
||||
|
||||
var ModuleWrapper = function ModuleWrapper(module, render) {
|
||||
classCallCheck(this, ModuleWrapper);
|
||||
|
||||
var instance = module();
|
||||
this.render = function (src, options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
resolve(render(instance, src, options));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||
|
||||
|
||||
function b64EncodeUnicode(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
||||
return String.fromCharCode('0x' + p1);
|
||||
}));
|
||||
}
|
||||
|
||||
function defaultScale() {
|
||||
if ('devicePixelRatio' in window && window.devicePixelRatio > 1) {
|
||||
return window.devicePixelRatio;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function svgXmlToImageElement(svgXml) {
|
||||
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
||||
_ref$scale = _ref.scale,
|
||||
scale = _ref$scale === undefined ? defaultScale() : _ref$scale,
|
||||
_ref$mimeType = _ref.mimeType,
|
||||
mimeType = _ref$mimeType === undefined ? "image/png" : _ref$mimeType,
|
||||
_ref$quality = _ref.quality,
|
||||
quality = _ref$quality === undefined ? 1 : _ref$quality;
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var svgImage = new Image();
|
||||
|
||||
svgImage.onload = function () {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = svgImage.width * scale;
|
||||
canvas.height = svgImage.height * scale;
|
||||
|
||||
var context = canvas.getContext("2d");
|
||||
context.drawImage(svgImage, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
canvas.toBlob(function (blob) {
|
||||
var image = new Image();
|
||||
image.src = URL.createObjectURL(blob);
|
||||
image.width = svgImage.width;
|
||||
image.height = svgImage.height;
|
||||
|
||||
resolve(image);
|
||||
}, mimeType, quality);
|
||||
};
|
||||
|
||||
svgImage.onerror = function (e) {
|
||||
var error;
|
||||
|
||||
if ('error' in e) {
|
||||
error = e.error;
|
||||
} else {
|
||||
error = new Error('Error loading SVG');
|
||||
}
|
||||
|
||||
reject(error);
|
||||
};
|
||||
|
||||
svgImage.src = 'data:image/svg+xml;base64,' + b64EncodeUnicode(svgXml);
|
||||
});
|
||||
}
|
||||
|
||||
function svgXmlToImageElementFabric(svgXml) {
|
||||
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
||||
_ref2$scale = _ref2.scale,
|
||||
scale = _ref2$scale === undefined ? defaultScale() : _ref2$scale,
|
||||
_ref2$mimeType = _ref2.mimeType,
|
||||
mimeType = _ref2$mimeType === undefined ? 'image/png' : _ref2$mimeType,
|
||||
_ref2$quality = _ref2.quality,
|
||||
quality = _ref2$quality === undefined ? 1 : _ref2$quality;
|
||||
|
||||
var multiplier = scale;
|
||||
|
||||
var format = void 0;
|
||||
if (mimeType == 'image/jpeg') {
|
||||
format = 'jpeg';
|
||||
} else if (mimeType == 'image/png') {
|
||||
format = 'png';
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
fabric.loadSVGFromString(svgXml, function (objects, options) {
|
||||
// If there's something wrong with the SVG, Fabric may return an empty array of objects. Graphviz appears to give us at least one <g> element back even given an empty graph, so we will assume an error in this case.
|
||||
if (objects.length == 0) {
|
||||
reject(new Error('Error loading SVG with Fabric'));
|
||||
}
|
||||
|
||||
var element = document.createElement("canvas");
|
||||
element.width = options.width;
|
||||
element.height = options.height;
|
||||
|
||||
var canvas = new fabric.Canvas(element, { enableRetinaScaling: false });
|
||||
var obj = fabric.util.groupSVGElements(objects, options);
|
||||
canvas.add(obj).renderAll();
|
||||
|
||||
var image = new Image();
|
||||
image.src = canvas.toDataURL({ format: format, multiplier: multiplier, quality: quality });
|
||||
image.width = options.width;
|
||||
image.height = options.height;
|
||||
|
||||
resolve(image);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var Viz = function () {
|
||||
function Viz() {
|
||||
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||
workerURL = _ref3.workerURL,
|
||||
worker = _ref3.worker,
|
||||
Module = _ref3.Module,
|
||||
render = _ref3.render;
|
||||
|
||||
classCallCheck(this, Viz);
|
||||
|
||||
if (typeof workerURL !== 'undefined') {
|
||||
this.wrapper = new WorkerWrapper(new Worker(workerURL));
|
||||
} else if (typeof worker !== 'undefined') {
|
||||
this.wrapper = new WorkerWrapper(worker);
|
||||
} else if (typeof Module !== 'undefined' && typeof render !== 'undefined') {
|
||||
this.wrapper = new ModuleWrapper(Module, render);
|
||||
} else if (typeof Viz.Module !== 'undefined' && typeof Viz.render !== 'undefined') {
|
||||
this.wrapper = new ModuleWrapper(Viz.Module, Viz.render);
|
||||
} else {
|
||||
throw new Error('Must specify workerURL or worker option, Module and render options, or include one of full.render.js or lite.render.js after viz.js.');
|
||||
}
|
||||
}
|
||||
|
||||
createClass(Viz, [{
|
||||
key: 'renderString',
|
||||
value: function renderString(src) {
|
||||
var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
||||
_ref4$format = _ref4.format,
|
||||
format = _ref4$format === undefined ? 'svg' : _ref4$format,
|
||||
_ref4$engine = _ref4.engine,
|
||||
engine = _ref4$engine === undefined ? 'dot' : _ref4$engine,
|
||||
_ref4$files = _ref4.files,
|
||||
files = _ref4$files === undefined ? [] : _ref4$files,
|
||||
_ref4$images = _ref4.images,
|
||||
images = _ref4$images === undefined ? [] : _ref4$images,
|
||||
_ref4$yInvert = _ref4.yInvert,
|
||||
yInvert = _ref4$yInvert === undefined ? false : _ref4$yInvert;
|
||||
|
||||
for (var i = 0; i < images.length; i++) {
|
||||
files.push({
|
||||
path: images[i].path,
|
||||
data: '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg width="' + images[i].width + '" height="' + images[i].height + '"></svg>'
|
||||
});
|
||||
}
|
||||
|
||||
return this.wrapper.render(src, { format: format, engine: engine, files: files, images: images, yInvert: yInvert });
|
||||
}
|
||||
}, {
|
||||
key: 'renderSVGElement',
|
||||
value: function renderSVGElement(src) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
|
||||
return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) {
|
||||
var parser = new DOMParser();
|
||||
return parser.parseFromString(str, 'image/svg+xml').documentElement;
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'renderImageElement',
|
||||
value: function renderImageElement(src) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
var scale = options.scale,
|
||||
mimeType = options.mimeType,
|
||||
quality = options.quality;
|
||||
|
||||
|
||||
return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) {
|
||||
if ((typeof fabric === 'undefined' ? 'undefined' : _typeof(fabric)) === "object" && fabric.loadSVGFromString) {
|
||||
return svgXmlToImageElementFabric(str, { scale: scale, mimeType: mimeType, quality: quality });
|
||||
} else {
|
||||
return svgXmlToImageElement(str, { scale: scale, mimeType: mimeType, quality: quality });
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'renderJSONObject',
|
||||
value: function renderJSONObject(src) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
var format = options.format;
|
||||
|
||||
|
||||
if (format !== 'json' || format !== 'json0') {
|
||||
format = 'json';
|
||||
}
|
||||
|
||||
return this.renderString(src, _extends({}, options, { format: format })).then(function (str) {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
}
|
||||
}]);
|
||||
return Viz;
|
||||
}();
|
||||
|
||||
return Viz;
|
||||
|
||||
})));
|
||||
|
|
@ -74,14 +74,16 @@ namespace Microsoft.AspNetCore.Routing.DecisionTree
|
|||
{
|
||||
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++)
|
||||
var itemCount = items.Count;
|
||||
var itemDescriptors = new List<ItemDescriptor<TItem>>(itemCount);
|
||||
for (var i = 0; i < itemCount; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
itemDescriptors.Add(new ItemDescriptor<TItem>()
|
||||
{
|
||||
Criteria = classifier.GetCriteria(items[i]),
|
||||
Criteria = classifier.GetCriteria(item),
|
||||
Index = i,
|
||||
Item = items[i],
|
||||
Item = item,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +97,7 @@ namespace Microsoft.AspNetCore.Routing.DecisionTree
|
|||
private static DecisionTreeNode<TItem> GenerateNode(
|
||||
TreeBuilderContext context,
|
||||
DecisionCriterionValueEqualityComparer comparer,
|
||||
IList<ItemDescriptor<TItem>> items)
|
||||
List<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
|
||||
|
|
@ -123,15 +125,13 @@ namespace Microsoft.AspNetCore.Routing.DecisionTree
|
|||
|
||||
unsatisfiedCriteria++;
|
||||
|
||||
Criterion criterion;
|
||||
if (!criteria.TryGetValue(kvp.Key, out criterion))
|
||||
if (!criteria.TryGetValue(kvp.Key, out var criterion))
|
||||
{
|
||||
criterion = new Criterion(comparer);
|
||||
criteria.Add(kvp.Key, criterion);
|
||||
}
|
||||
|
||||
List<ItemDescriptor<TItem>> branch;
|
||||
if (!criterion.TryGetValue(kvp.Value, out branch))
|
||||
if (!criterion.TryGetValue(kvp.Value, out var branch))
|
||||
{
|
||||
branch = new List<ItemDescriptor<TItem>>();
|
||||
criterion.Add(kvp.Value, branch);
|
||||
|
|
@ -156,16 +156,17 @@ namespace Microsoft.AspNetCore.Routing.DecisionTree
|
|||
|
||||
foreach (var branch in criterion.Value)
|
||||
{
|
||||
var reducedItems = new List<ItemDescriptor<TItem>>();
|
||||
bool hasReducedItems = false;
|
||||
|
||||
foreach (var item in branch.Value)
|
||||
{
|
||||
if (context.MatchedItems.Add(item))
|
||||
{
|
||||
reducedItems.Add(item);
|
||||
hasReducedItems = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (reducedItems.Count > 0)
|
||||
if (hasReducedItems)
|
||||
{
|
||||
var childContext = new TreeBuilderContext(context);
|
||||
childContext.CurrentCriteria.Add(criterion.Key);
|
||||
|
|
@ -189,7 +190,7 @@ namespace Microsoft.AspNetCore.Routing.DecisionTree
|
|||
|
||||
return new DecisionTreeNode<TItem>()
|
||||
{
|
||||
Criteria = reducedCriteria.ToList(),
|
||||
Criteria = reducedCriteria,
|
||||
Matches = matches,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Respresents a logical endpoint in an application.
|
||||
/// </summary>
|
||||
public class Endpoint
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="Endpoint"/>.
|
||||
/// </summary>
|
||||
/// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
|
||||
/// <param name="metadata">
|
||||
/// The endpoint <see cref="EndpointMetadataCollection"/>. May be null.
|
||||
/// </param>
|
||||
/// <param name="displayName">
|
||||
/// The informational display name of the endpoint. May be null.
|
||||
/// </param>
|
||||
public Endpoint(
|
||||
RequestDelegate requestDelegate,
|
||||
EndpointMetadataCollection metadata,
|
||||
string displayName)
|
||||
{
|
||||
// All are allowed to be null
|
||||
RequestDelegate = requestDelegate;
|
||||
Metadata = metadata ?? EndpointMetadataCollection.Empty;
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the informational display name of this endpoint.
|
||||
/// </summary>
|
||||
public string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of metadata associated with this endpoint.
|
||||
/// </summary>
|
||||
public EndpointMetadataCollection Metadata { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate used to process requests for the endpoint.
|
||||
/// </summary>
|
||||
public RequestDelegate RequestDelegate { get; }
|
||||
|
||||
public override string ToString() => DisplayName ?? base.ToString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of arbitrary metadata associated with an endpoint.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="EndpointMetadataCollection"/> instances contain a list of metadata items
|
||||
/// of arbitrary types. The metadata items are stored as an ordered collection with
|
||||
/// items arranged in ascending order of precedence.
|
||||
/// </remarks>
|
||||
public sealed class EndpointMetadataCollection : IReadOnlyList<object>
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty <see cref="EndpointMetadataCollection"/>.
|
||||
/// </summary>
|
||||
public static readonly EndpointMetadataCollection Empty = new EndpointMetadataCollection(Array.Empty<object>());
|
||||
|
||||
private readonly object[] _items;
|
||||
private readonly ConcurrentDictionary<Type, object[]> _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="EndpointMetadataCollection"/>.
|
||||
/// </summary>
|
||||
/// <param name="items">The metadata items.</param>
|
||||
public EndpointMetadataCollection(IEnumerable<object> items)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
}
|
||||
|
||||
_items = items.ToArray();
|
||||
_cache = new ConcurrentDictionary<Type, object[]>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="EndpointMetadataCollection"/>.
|
||||
/// </summary>
|
||||
/// <param name="items">The metadata items.</param>
|
||||
public EndpointMetadataCollection(params object[] items)
|
||||
: this((IEnumerable<object>)items)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item at <paramref name="index"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item to retrieve.</param>
|
||||
/// <returns>The item at <paramref name="index"/>.</returns>
|
||||
public object this[int index] => _items[index];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of metadata items.
|
||||
/// </summary>
|
||||
public int Count => _items.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the most significant metadata item of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of metadata to retrieve.</typeparam>
|
||||
/// <returns>
|
||||
/// The most significant metadata of type <typeparamref name="T"/> or <c>null</c>.
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetMetadata<T>() where T : class
|
||||
{
|
||||
if (_cache.TryGetValue(typeof(T), out var result))
|
||||
{
|
||||
var length = result.Length;
|
||||
return length > 0 ? (T)result[length - 1] : default;
|
||||
}
|
||||
|
||||
return GetMetadataSlow<T>();
|
||||
}
|
||||
|
||||
private T GetMetadataSlow<T>() where T : class
|
||||
{
|
||||
var array = GetOrderedMetadataSlow<T>();
|
||||
var length = array.Length;
|
||||
return length > 0 ? array[length - 1] : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the metadata items of type <typeparamref name="T"/> in ascending
|
||||
/// order of precedence.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of metadata.</typeparam>
|
||||
/// <returns>A sequence of metadata items of <typeparamref name="T"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IEnumerable<T> GetOrderedMetadata<T>() where T : class
|
||||
{
|
||||
if (_cache.TryGetValue(typeof(T), out var result))
|
||||
{
|
||||
return (T[])result;
|
||||
}
|
||||
|
||||
return GetOrderedMetadataSlow<T>();
|
||||
}
|
||||
|
||||
private T[] GetOrderedMetadataSlow<T>() where T : class
|
||||
{
|
||||
var items = new List<T>();
|
||||
for (var i = 0; i < _items.Length; i++)
|
||||
{
|
||||
if (_items[i] is T item)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
var array = items.ToArray();
|
||||
_cache.TryAdd(typeof(T), array);
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IEnumerator"/> of all metadata items.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
|
||||
public Enumerator GetEnumerator() => new Enumerator(this);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IEnumerator{Object}"/> of all metadata items.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IEnumerator{Object}"/> of all metadata items.</returns>
|
||||
IEnumerator<object> IEnumerable<object>.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IEnumerator"/> of all metadata items.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the elements of an <see cref="EndpointMetadataCollection"/>.
|
||||
/// </summary>
|
||||
public struct Enumerator : IEnumerator<object>
|
||||
{
|
||||
// Intentionally not readonly to prevent defensive struct copies
|
||||
private object[] _items;
|
||||
private int _index;
|
||||
|
||||
internal Enumerator(EndpointMetadataCollection collection)
|
||||
{
|
||||
_items = collection._items;
|
||||
_index = 0;
|
||||
Current = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at the current position of the enumerator
|
||||
/// </summary>
|
||||
public object Current { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources used by the <see cref="Enumerator"/>.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances the enumerator to the next element of the <see cref="Enumerator"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the enumerator was successfully advanced to the next element;
|
||||
/// <c>false</c> if the enumerator has passed the end of the collection.
|
||||
/// </returns>
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index < _items.Length)
|
||||
{
|
||||
Current = _items[_index++];
|
||||
return true;
|
||||
}
|
||||
|
||||
Current = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the enumerator to its initial position, which is before the first element in the collection.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_index = 0;
|
||||
Current = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Http.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// A feature interface for endpoint routing. Use <see cref="HttpContext.Features"/>
|
||||
/// to access an instance associated with the current request.
|
||||
/// </summary>
|
||||
public interface IEndpointFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the selected <see cref="Http.Endpoint"/> for the current
|
||||
/// request.
|
||||
/// </summary>
|
||||
Endpoint Endpoint { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract that a class must implement to transform route values while building
|
||||
/// a URI.
|
||||
/// </summary>
|
||||
public interface IOutboundParameterTransformer : IParameterPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Transforms the specified route value to a string for inclusion in a URI.
|
||||
/// </summary>
|
||||
/// <param name="value">The route value to transform.</param>
|
||||
/// <returns>The transformed value.</returns>
|
||||
string TransformOutbound(object value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// A marker interface for types that are associated with route parameters.
|
||||
/// </summary>
|
||||
public interface IParameterPolicy
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
/// 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
|
||||
public interface IRouteConstraint : IParameterPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the URL parameter contains a valid value for this constraint.
|
||||
|
|
|
|||
|
|
@ -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 Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Features
|
||||
{
|
||||
public interface IRouteValuesFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="RouteValueDictionary"/> associated with the currrent
|
||||
/// request.
|
||||
/// </summary>
|
||||
RouteValueDictionary RouteValues { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
// 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 to generate absolute and related URIs based on endpoint routing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Generating URIs in endpoint routing occurs in two phases. First, an address is bound to a list of
|
||||
/// endpoints that match the address. Secondly, each endpoint's <c>RoutePattern</c> is evaluated, until
|
||||
/// a route pattern that matches the supplied values is found. The resulting output is combined with
|
||||
/// the other URI parts supplied to the link generator and returned.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The methods provided by the <see cref="LinkGenerator"/> type are general infrastructure, and support
|
||||
/// the standard link generator functionality for any type of address. The most convenient way to use
|
||||
/// <see cref="LinkGenerator"/> is through extension methods that perform operations for a specific
|
||||
/// address type.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class LinkGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a URI with an absolute path based on the provided values and <see cref="HttpContext"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAddress">The address type.</typeparam>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="address">The address value. Used to resolve endpoints.</param>
|
||||
/// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
|
||||
/// <param name="ambientValues">The values associated with the current request. Optional.</param>
|
||||
/// <param name="pathBase">
|
||||
/// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
|
||||
/// </param>
|
||||
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||
/// <param name="options">
|
||||
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||
/// names from <c>RouteOptions</c>.
|
||||
/// </param>
|
||||
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
|
||||
public abstract string GetPathByAddress<TAddress>(
|
||||
HttpContext httpContext,
|
||||
TAddress address,
|
||||
RouteValueDictionary values,
|
||||
RouteValueDictionary ambientValues = default,
|
||||
PathString? pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a URI with an absolute path based on the provided values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAddress">The address type.</typeparam>
|
||||
/// <param name="address">The address value. Used to resolve endpoints.</param>
|
||||
/// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
|
||||
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||
/// <param name="options">
|
||||
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||
/// names from <c>RouteOptions</c>.
|
||||
/// </param>
|
||||
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
|
||||
public abstract string GetPathByAddress<TAddress>(
|
||||
TAddress address,
|
||||
RouteValueDictionary values,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Generates an absolute URI based on the provided values and <see cref="HttpContext"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAddress">The address type.</typeparam>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="address">The address value. Used to resolve endpoints.</param>
|
||||
/// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
|
||||
/// <param name="ambientValues">The values associated with the current request. Optional.</param>
|
||||
/// <param name="scheme">
|
||||
/// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of <see cref="HttpRequest.Scheme"/> will be used.
|
||||
/// </param>
|
||||
/// <param name="host">
|
||||
/// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value <see cref="HttpRequest.Host"/> will be used.
|
||||
/// See the remarks section for details about the security implications of the <paramref name="host"/>.
|
||||
/// </param>
|
||||
/// <param name="pathBase">
|
||||
/// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
|
||||
/// </param>
|
||||
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||
/// <param name="options">
|
||||
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||
/// names from <c>RouteOptions</c>.
|
||||
/// </param>
|
||||
/// <returns>A URI with an absolute path, or <c>null</c>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
|
||||
/// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
|
||||
/// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
|
||||
/// your deployment environment.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract string GetUriByAddress<TAddress>(
|
||||
HttpContext httpContext,
|
||||
TAddress address,
|
||||
RouteValueDictionary values,
|
||||
RouteValueDictionary ambientValues = default,
|
||||
string scheme = default,
|
||||
HostString? host = default,
|
||||
PathString? pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Generates an absolute URI based on the provided values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAddress">The address type.</typeparam>
|
||||
/// <param name="address">The address value. Used to resolve endpoints.</param>
|
||||
/// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
|
||||
/// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
|
||||
/// <param name="host">
|
||||
/// The URI host/authority, applied to the resulting URI.
|
||||
/// See the remarks section for details about the security implications of the <paramref name="host"/>.
|
||||
/// </param>
|
||||
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||
/// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
|
||||
/// <param name="options">
|
||||
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||
/// names from <c>RouteOptions</c>.
|
||||
/// </param>
|
||||
/// <returns>An absolute URI, or <c>null</c>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
|
||||
/// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
|
||||
/// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
|
||||
/// your deployment environment.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract string GetUriByAddress<TAddress>(
|
||||
TAddress address,
|
||||
RouteValueDictionary values,
|
||||
string scheme,
|
||||
HostString host,
|
||||
PathString pathBase = default,
|
||||
FragmentString fragment = default,
|
||||
LinkOptions options = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public class LinkOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether all generated paths URLs are lower-case.
|
||||
/// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
|
||||
/// </summary>
|
||||
public bool? LowercaseUrls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a generated query strings are lower-case.
|
||||
/// This property will be unless <see cref="LowercaseUrls" /> is also <c>true</c>.
|
||||
/// </summary>
|
||||
public bool? LowercaseQueryStrings { 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,4 +3,5 @@
|
|||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
private RouteData _routeData;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteContext"/> for the provided <paramref name="httpContext"/>.
|
||||
/// Creates a new instance of <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)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
private RouteValueDictionary _values;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteData"/> instance.
|
||||
/// Creates a new instance of <see cref="RouteData"/> instance.
|
||||
/// </summary>
|
||||
public RouteData()
|
||||
{
|
||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteData"/> instance with values copied from <paramref name="other"/>.
|
||||
/// Creates a new instance of <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)
|
||||
|
|
@ -51,6 +51,20 @@ namespace Microsoft.AspNetCore.Routing
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="RouteData"/> instance with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="values">The <see cref="RouteValueDictionary"/> values.</param>
|
||||
public RouteData(RouteValueDictionary values)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
_values = values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data tokens produced by routes on the current routing path.
|
||||
/// </summary>
|
||||
|
|
@ -84,7 +98,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of values produced by routes on the current routing path.
|
||||
/// Gets the values produced by routes on the current routing path.
|
||||
/// </summary>
|
||||
public RouteValueDictionary Values
|
||||
{
|
||||
|
|
@ -183,7 +197,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
private readonly RouteValueDictionary _values;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteDataSnapshot"/> for <paramref name="routeData"/>.
|
||||
/// Creates a new instance of <see cref="RouteDataSnapshot"/> for <paramref name="routeData"/>.
|
||||
/// </summary>
|
||||
/// <param name="routeData">The <see cref="RouteData"/>.</param>
|
||||
/// <param name="dataTokens">The data tokens.</param>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
public class VirtualPathContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="VirtualPathContext"/>.
|
||||
/// Creates a new instance of <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>
|
||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="VirtualPathContext"/>.
|
||||
/// Creates a new instance of <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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,189 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an <see cref="EndpointDataSource"/> whose values come from a collection of <see cref="EndpointDataSource"/> instances.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{DebuggerDisplayString,nq}")]
|
||||
public sealed class CompositeEndpointDataSource : EndpointDataSource
|
||||
{
|
||||
private readonly EndpointDataSource[] _dataSources;
|
||||
private readonly object _lock;
|
||||
private IReadOnlyList<Endpoint> _endpoints;
|
||||
private IChangeToken _consumerChangeToken;
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
internal CompositeEndpointDataSource(IEnumerable<EndpointDataSource> dataSources)
|
||||
{
|
||||
if (dataSources == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataSources));
|
||||
}
|
||||
|
||||
CreateChangeToken();
|
||||
_dataSources = dataSources.ToArray();
|
||||
_lock = new object();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
|
||||
/// instances.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IChangeToken"/>.</returns>
|
||||
public override IChangeToken GetChangeToken()
|
||||
{
|
||||
EnsureInitialized();
|
||||
return _consumerChangeToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a read-only collection of <see cref="Endpoint"/> instances.
|
||||
/// </summary>
|
||||
public override IReadOnlyList<Endpoint> Endpoints
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureInitialized();
|
||||
return _endpoints;
|
||||
}
|
||||
}
|
||||
|
||||
// Defer initialization to avoid doing lots of reflection on startup.
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (_endpoints == null)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
// Note: we can't use DataSourceDependentCache here because we also need to handle a list of change
|
||||
// tokens, which is a complication most of our code doesn't have.
|
||||
private void Initialize()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_endpoints == null)
|
||||
{
|
||||
_endpoints = _dataSources.SelectMany(d => d.Endpoints).ToArray();
|
||||
|
||||
foreach (var dataSource in _dataSources)
|
||||
{
|
||||
ChangeToken.OnChange(
|
||||
dataSource.GetChangeToken,
|
||||
HandleChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleChange()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// Refresh the endpoints from datasource so that callbacks can get the latest endpoints
|
||||
_endpoints = _dataSources.SelectMany(d => d.Endpoints).ToArray();
|
||||
|
||||
// Prevent consumers from re-registering callback to inflight events as that can
|
||||
// cause a stackoverflow
|
||||
// Example:
|
||||
// 1. B registers A
|
||||
// 2. A fires event causing B's callback to get called
|
||||
// 3. B executes some code in its callback, but needs to re-register callback
|
||||
// in the same callback
|
||||
var oldTokenSource = _cts;
|
||||
var oldToken = _consumerChangeToken;
|
||||
|
||||
CreateChangeToken();
|
||||
|
||||
// Raise consumer callbacks. Any new callback registration would happen on the new token
|
||||
// created in earlier step.
|
||||
oldTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateChangeToken()
|
||||
{
|
||||
_cts = new CancellationTokenSource();
|
||||
_consumerChangeToken = new CancellationChangeToken(_cts.Token);
|
||||
}
|
||||
|
||||
private string DebuggerDisplayString
|
||||
{
|
||||
get
|
||||
{
|
||||
// Try using private variable '_endpoints' to avoid initialization
|
||||
if (_endpoints == null)
|
||||
{
|
||||
return "No endpoints";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var endpoint in _endpoints)
|
||||
{
|
||||
if (endpoint is RouteEndpoint routeEndpoint)
|
||||
{
|
||||
var template = routeEndpoint.RoutePattern.RawText;
|
||||
template = string.IsNullOrEmpty(template) ? "\"\"" : template;
|
||||
sb.Append(template);
|
||||
sb.Append(", Defaults: new { ");
|
||||
sb.Append(string.Join(", ", FormatValues(routeEndpoint.RoutePattern.Defaults)));
|
||||
sb.Append(" }");
|
||||
var routeValuesAddressMetadata = routeEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||
sb.Append(", Route Name: ");
|
||||
sb.Append(routeValuesAddressMetadata?.RouteName);
|
||||
if (routeValuesAddressMetadata?.RequiredValues != null)
|
||||
{
|
||||
sb.Append(", Required Values: new { ");
|
||||
sb.Append(string.Join(", ", FormatValues(routeValuesAddressMetadata.RequiredValues)));
|
||||
sb.Append(" }");
|
||||
}
|
||||
sb.Append(", Order: ");
|
||||
sb.Append(routeEndpoint.Order);
|
||||
|
||||
var httpMethodMetadata = routeEndpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
|
||||
if (httpMethodMetadata != null)
|
||||
{
|
||||
sb.Append(", Http Methods: ");
|
||||
sb.Append(string.Join(", ", httpMethodMetadata.HttpMethods));
|
||||
}
|
||||
sb.Append(", Display Name: ");
|
||||
sb.Append(routeEndpoint.DisplayName);
|
||||
sb.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("Non-RouteEndpoint. DisplayName:");
|
||||
sb.AppendLine(endpoint.DisplayName);
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
|
||||
IEnumerable<string> FormatValues(IEnumerable<KeyValuePair<string, object>> values)
|
||||
{
|
||||
return values.Select(
|
||||
kvp =>
|
||||
{
|
||||
var value = "null";
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
value = "\"" + kvp.Value.ToString() + "\"";
|
||||
}
|
||||
return kvp.Key + " = " + value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -40,17 +30,15 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var 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 bool.TryParse(valueString, out _);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -39,16 +39,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
|
|||
|
|
@ -26,16 +26,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -46,17 +36,15 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var 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 DateTime.TryParse(valueString, CultureInfo.InvariantCulture, DateTimeStyles.None, out _);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -40,17 +30,15 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var 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 decimal.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out _);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -40,21 +30,19 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var 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);
|
||||
out _);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -40,21 +30,19 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var 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);
|
||||
out _);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -42,17 +32,15 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var 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 Guid.TryParse(valueString, out _);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
public class HttpMethodRouteConstraint : IRouteConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HttpMethodRouteConstraint"/> that accepts the HTTP methods specified
|
||||
/// Creates a new instance of <see cref="HttpMethodRouteConstraint"/> that accepts the HTTP methods specified
|
||||
/// by <paramref name="allowedMethods"/>.
|
||||
/// </summary>
|
||||
/// <param name="allowedMethods">The allowed HTTP methods.</param>
|
||||
|
|
@ -41,16 +41,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -64,6 +54,12 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
switch (routeDirection)
|
||||
{
|
||||
case RouteDirection.IncomingRequest:
|
||||
// Only required for constraining incoming requests
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
return AllowedMethods.Contains(httpContext.Request.Method, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
case RouteDirection.UrlGeneration:
|
||||
|
|
@ -80,8 +76,7 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
// 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))
|
||||
if (!values.TryGetValue(routeKey, out var obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -40,17 +30,15 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var 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 int.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out _);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -77,16 +77,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -97,8 +87,7 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var value) && value != null)
|
||||
{
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
var length = valueString.Length;
|
||||
|
|
|
|||
|
|
@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -40,17 +30,15 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var 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 long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out _);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -40,16 +40,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -60,8 +50,7 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var value) && value != null)
|
||||
{
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return valueString.Length <= MaxLength;
|
||||
|
|
|
|||
|
|
@ -34,16 +34,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -54,12 +44,10 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var value) && value != null)
|
||||
{
|
||||
long longValue;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue))
|
||||
if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
|
||||
{
|
||||
return longValue <= Max;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,16 +40,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -60,8 +50,7 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var value) && value != null)
|
||||
{
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return valueString.Length >= MinLength;
|
||||
|
|
|
|||
|
|
@ -34,16 +34,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -54,12 +44,10 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var value) && value != null)
|
||||
{
|
||||
long longValue;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue))
|
||||
if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
|
||||
{
|
||||
return longValue >= Min;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
internal class NullRouteConstraint : IRouteConstraint
|
||||
{
|
||||
public static readonly NullRouteConstraint Instance = new NullRouteConstraint();
|
||||
|
||||
private NullRouteConstraint()
|
||||
{
|
||||
}
|
||||
|
||||
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,16 +30,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -50,8 +40,7 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value))
|
||||
if (values.TryGetValue(routeKey, out var value))
|
||||
{
|
||||
return InnerConstraint.Match(httpContext,
|
||||
route,
|
||||
|
|
|
|||
|
|
@ -48,16 +48,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -68,12 +58,10 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var value) && value != null)
|
||||
{
|
||||
long longValue;
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (Int64.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue))
|
||||
if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
|
||||
{
|
||||
return longValue >= Min && longValue <= Max;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,16 +44,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -64,9 +54,7 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object routeValue;
|
||||
|
||||
if (values.TryGetValue(routeKey, out routeValue)
|
||||
if (values.TryGetValue(routeKey, out var routeValue)
|
||||
&& routeValue != null)
|
||||
{
|
||||
var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
|
||||
|
|
|
|||
|
|
@ -24,16 +24,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
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));
|
||||
|
|
@ -44,8 +34,7 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value) && value != null)
|
||||
if (values.TryGetValue(routeKey, out var 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);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Microsoft.AspNetCore.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a route parameter to contain only a specified strign.
|
||||
/// Constrains a route parameter to contain only a specified string.
|
||||
/// </summary>
|
||||
public class StringRouteConstraint : IRouteConstraint
|
||||
{
|
||||
|
|
@ -31,16 +31,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
/// <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));
|
||||
|
|
@ -51,9 +41,7 @@ namespace Microsoft.AspNetCore.Routing.Constraints
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
object routeValue;
|
||||
|
||||
if (values.TryGetValue(routeKey, out routeValue)
|
||||
if (values.TryGetValue(routeKey, out var routeValue)
|
||||
&& routeValue != null)
|
||||
{
|
||||
var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
internal class DataSourceDependentCache<T> where T : class
|
||||
{
|
||||
private readonly EndpointDataSource _dataSource;
|
||||
private readonly Func<IReadOnlyList<Endpoint>, T> _initializeCore;
|
||||
private readonly Func<T> _initializer;
|
||||
private readonly Action<object> _initializerWithState;
|
||||
|
||||
private object _lock;
|
||||
private bool _initialized;
|
||||
private T _value;
|
||||
|
||||
public DataSourceDependentCache(EndpointDataSource dataSource, Func<IReadOnlyList<Endpoint>, T> initialize)
|
||||
{
|
||||
if (dataSource == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataSource));
|
||||
}
|
||||
|
||||
_dataSource = dataSource;
|
||||
_initializeCore = initialize;
|
||||
|
||||
_initializer = Initialize;
|
||||
_initializerWithState = (state) => Initialize();
|
||||
_lock = new object();
|
||||
}
|
||||
|
||||
// Note that we don't lock here, and think about that in the context of a 'push'. So when data gets 'pushed'
|
||||
// we start computing a new state, but we're still able to perform operations on the old state until we've
|
||||
// processed the update.
|
||||
public T Value => _value;
|
||||
|
||||
public T EnsureInitialized()
|
||||
{
|
||||
return LazyInitializer.EnsureInitialized<T>(ref _value, ref _initialized, ref _lock, _initializer);
|
||||
}
|
||||
|
||||
private T Initialize()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var changeToken = _dataSource.GetChangeToken();
|
||||
_value = _initializeCore(_dataSource.Endpoints);
|
||||
|
||||
changeToken.RegisterChangeCallback(_initializerWithState, null);
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata that defines data tokens for an <see cref="Endpoint"/>. This metadata
|
||||
/// type provides data tokens value for <see cref="RouteData.DataTokens"/> associated
|
||||
/// with an endpoint.
|
||||
/// </summary>
|
||||
public sealed class DataTokensMetadata : IDataTokensMetadata
|
||||
{
|
||||
public DataTokensMetadata(IReadOnlyDictionary<string, object> dataTokens)
|
||||
{
|
||||
if (dataTokens == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataTokens));
|
||||
}
|
||||
|
||||
DataTokens = dataTokens;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the data tokens.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object> DataTokens { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a collection of <see cref="Endpoint"/> instances.
|
||||
/// </summary>
|
||||
public sealed class DefaultEndpointDataSource : EndpointDataSource
|
||||
{
|
||||
private readonly IReadOnlyList<Endpoint> _endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultEndpointDataSource" /> class.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="Endpoint"/> instances that the data source will return.</param>
|
||||
public DefaultEndpointDataSource(params Endpoint[] endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
_endpoints = (Endpoint[])endpoints.Clone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultEndpointDataSource" /> class.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="Endpoint"/> instances that the data source will return.</param>
|
||||
public DefaultEndpointDataSource(IEnumerable<Endpoint> endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
_endpoints = new List<Endpoint>(endpoints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
|
||||
/// instances.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IChangeToken"/>.</returns>
|
||||
public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a read-only collection of <see cref="Endpoint"/> instances.
|
||||
/// </summary>
|
||||
public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
|
|
@ -18,6 +16,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
public class DefaultInlineConstraintResolver : IInlineConstraintResolver
|
||||
{
|
||||
private readonly IDictionary<string, Type> _inlineConstraintMap;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultInlineConstraintResolver"/> class.
|
||||
|
|
@ -25,11 +24,23 @@ namespace Microsoft.AspNetCore.Routing
|
|||
/// <param name="routeOptions">
|
||||
/// Accessor for <see cref="RouteOptions"/> containing the constraints of interest.
|
||||
/// </param>
|
||||
[Obsolete("This constructor is obsolete. Use DefaultInlineConstraintResolver.ctor(IOptions<RouteOptions>, IServiceProvider) instead.")]
|
||||
public DefaultInlineConstraintResolver(IOptions<RouteOptions> routeOptions)
|
||||
{
|
||||
_inlineConstraintMap = routeOptions.Value.ConstraintMap;
|
||||
}
|
||||
|
||||
public DefaultInlineConstraintResolver(IOptions<RouteOptions> routeOptions, IServiceProvider serviceProvider)
|
||||
{
|
||||
if (serviceProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceProvider));
|
||||
}
|
||||
|
||||
_inlineConstraintMap = routeOptions.Value.ConstraintMap;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <example>
|
||||
/// A typical constraint looks like the following
|
||||
|
|
@ -45,112 +56,12 @@ namespace Microsoft.AspNetCore.Routing
|
|||
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;
|
||||
// This will return null if the text resolves to a non-IRouteConstraint
|
||||
return ParameterPolicyActivator.ResolveParameterPolicy<IRouteConstraint>(
|
||||
_inlineConstraintMap,
|
||||
_serviceProvider,
|
||||
inlineConstraint,
|
||||
out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue