зеркало из https://github.com/aspnet/Routing.git
Родитель
34c60bc14d
Коммит
93d20ec78c
|
@ -2,8 +2,7 @@
|
|||
"adx-nonshipping": {
|
||||
"rules": [],
|
||||
"packages": {
|
||||
"Microsoft.AspNetCore.Routing.DecisionTree.Sources": {},
|
||||
"Microsoft.AspNetCore.Routing.UrlMatchingTree.Sources": {}
|
||||
"Microsoft.AspNetCore.Routing.DecisionTree.Sources": {}
|
||||
}
|
||||
},
|
||||
"Default": {
|
||||
|
|
105
Routing.sln
105
Routing.sln
|
@ -45,20 +45,6 @@ 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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Dispatcher", "src\Microsoft.AspNetCore.Dispatcher\Microsoft.AspNetCore.Dispatcher.csproj", "{3FEBCDA2-0381-47B8-A400-4A998D62F86F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Dispatcher.Abstractions", "src\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj", "{3153A4B2-BF6B-44EB-8113-F425F07F86E6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Dispatcher.Test", "test\Microsoft.AspNetCore.Dispatcher.Test\Microsoft.AspNetCore.Dispatcher.Test.csproj", "{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Dispatcher.Abstractions.Test", "test\Microsoft.AspNetCore.Dispatcher.Abstractions.Test\Microsoft.AspNetCore.Dispatcher.Abstractions.Test.csproj", "{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispatcherSample", "samples\DispatcherSample\DispatcherSample.csproj", "{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Dispatcher.Performance", "benchmarks\Microsoft.AspNetCore.Dispatcher.Performance\Microsoft.AspNetCore.Dispatcher.Performance.csproj", "{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Dispatcher.FunctionalTest", "test\Microsoft.AspNetCore.Dispatcher.FunctionalTest\Microsoft.AspNetCore.Dispatcher.FunctionalTest.csproj", "{32107601-C9BE-467B-894C-C9F2E35F03E4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -159,90 +145,6 @@ 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
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92}.Release|x86.Build.0 = Release|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85}.Release|x86.Build.0 = Release|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -256,13 +158,6 @@ Global
|
|||
{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}
|
||||
{3FEBCDA2-0381-47B8-A400-4A998D62F86F} = {0E966C37-7334-4D96-AAF6-9F49FBD166E3}
|
||||
{3153A4B2-BF6B-44EB-8113-F425F07F86E6} = {0E966C37-7334-4D96-AAF6-9F49FBD166E3}
|
||||
{DB2ABDCA-639B-4E0D-B64F-5F6A98A9EC92} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
|
||||
{14ACBCB4-3B99-425F-A5E2-07E228DEBF63} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
|
||||
{6EBC8AE2-CFF7-46E1-8427-9111FD4F3E85} = {C3ADD55B-B9C7-4061-8AD4-6A70D1AE3B2E}
|
||||
{30AF355D-E3AB-4FF5-8A59-A253AFEBA26A} = {D5F39F59-5725-4127-82E7-67028D006185}
|
||||
{32107601-C9BE-467B-894C-C9F2E35F03E4} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {36C8D815-B7F1-479D-894B-E606FB8DECDA}
|
||||
|
|
|
@ -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.Dispatcher.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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Performance
|
||||
{
|
||||
public class DispatcherBenchmark
|
||||
{
|
||||
private const int NumberOfRequestTypes = 3;
|
||||
private const int Iterations = 100;
|
||||
|
||||
private readonly IMatcher _treeMatcher;
|
||||
private readonly RequestEntry[] _requests;
|
||||
|
||||
public DispatcherBenchmark()
|
||||
{
|
||||
var dataSource = new DefaultDispatcherDataSource()
|
||||
{
|
||||
Endpoints =
|
||||
{
|
||||
new RoutePatternEndpoint("api/Widgets", Benchmark_Delegate),
|
||||
new RoutePatternEndpoint("api/Widgets/{id}", Benchmark_Delegate),
|
||||
new RoutePatternEndpoint("api/Widgets/search/{term}", Benchmark_Delegate),
|
||||
new RoutePatternEndpoint("admin/users/{id}", Benchmark_Delegate),
|
||||
new RoutePatternEndpoint("admin/users/{id}/manage", Benchmark_Delegate),
|
||||
},
|
||||
};
|
||||
|
||||
var factory = new TreeMatcherFactory();
|
||||
_treeMatcher = factory.CreateMatcher(dataSource, new List<EndpointSelector>());
|
||||
|
||||
_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 MatcherContext(_requests[j].HttpContext);
|
||||
|
||||
await _treeMatcher.MatchAsync(context);
|
||||
|
||||
Verify(context, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Verify(MatcherContext context, int i)
|
||||
{
|
||||
if (_requests[i].IsMatch)
|
||||
{
|
||||
if (context.Endpoint == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed {i}");
|
||||
}
|
||||
|
||||
var values = _requests[i].Values;
|
||||
if (values.Count != context.Values.Count)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed {i}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (context.Endpoint != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed {i}");
|
||||
}
|
||||
|
||||
if (context.Values.Count != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed {i}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct RequestEntry
|
||||
{
|
||||
public HttpContext HttpContext;
|
||||
public bool IsMatch;
|
||||
public RouteValueDictionary Values;
|
||||
}
|
||||
|
||||
private static Task Benchmark_Delegate(HttpContext httpContext)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Dispatcher\Microsoft.AspNetCore.Dispatcher.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Performance
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,16 +2,20 @@
|
|||
// 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.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Performance
|
||||
{
|
||||
|
@ -26,11 +30,12 @@ namespace Microsoft.AspNetCore.Routing.Performance
|
|||
public RoutingBenchmark()
|
||||
{
|
||||
var handler = new RouteHandler((next) => Task.FromResult<object>(null));
|
||||
|
||||
|
||||
var treeBuilder = new TreeRouteBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
new RoutePatternBinderFactory(UrlEncoder.Default, new DefaultObjectPoolProvider()),
|
||||
new DefaultInlineConstraintResolver(Options.Create(new RouteOptions())));
|
||||
UrlEncoder.Default,
|
||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy(UrlEncoder.Default)),
|
||||
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);
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;netcoreapp2.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net461</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public interface IAuthorizationPolicyMetadata
|
||||
{
|
||||
string Name { get; }
|
||||
}
|
||||
|
||||
public class AuthorizationPolicyMetadata : IAuthorizationPolicyMetadata
|
||||
{
|
||||
public AuthorizationPolicyMetadata(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public interface ICorsPolicyMetadata
|
||||
{
|
||||
string Name { get; }
|
||||
}
|
||||
|
||||
public class CorsPolicyMetadata : ICorsPolicyMetadata
|
||||
{
|
||||
public CorsPolicyMetadata(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
|
@ -1,24 +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.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseIISIntegration()
|
||||
.UseKestrel()
|
||||
.UseStartup<Startup>()
|
||||
.ConfigureLogging((c, b) => b.AddProvider(new ConsoleLoggerProvider((category, level) => true, includeScopes: false)))
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,140 +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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDispatcher();
|
||||
|
||||
// This is a temporary layering issue, don't worry about :)
|
||||
services.AddRouting();
|
||||
services.AddSingleton<IDefaultMatcherFactory, TreeMatcherFactory>();
|
||||
|
||||
// Imagine this was done by MVC or another framework.
|
||||
services.AddSingleton<DispatcherDataSource>(ConfigureDispatcher());
|
||||
services.AddSingleton<EndpointSelector, HttpMethodEndpointSelector>();
|
||||
|
||||
}
|
||||
|
||||
public DefaultDispatcherDataSource ConfigureDispatcher()
|
||||
{
|
||||
return new DefaultDispatcherDataSource()
|
||||
{
|
||||
Addresses =
|
||||
{
|
||||
new RoutePatternAddress("{id?}", new { controller = "Home", action = "Index", }, "Home:Index()"),
|
||||
new RoutePatternAddress("Home/About/{id?}", new { controller = "Home", action = "About", }, "Home:About()"),
|
||||
new RoutePatternAddress("Admin/Index/{id?}", new { controller = "Admin", action = "Index", }, "Admin:Index()"),
|
||||
new RoutePatternAddress("Admin/Users/{id?}", new { controller = "Admin", action = "Users", }, "Admin:GetUsers()/Admin:EditUsers()"),
|
||||
},
|
||||
Endpoints =
|
||||
{
|
||||
new RoutePatternEndpoint("{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
||||
new RoutePatternEndpoint("Home/{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
||||
new RoutePatternEndpoint("Home/Index/{id?}", new { controller = "Home", action = "Index", }, Home_Index, "Home:Index()"),
|
||||
new RoutePatternEndpoint("Home/About/{id?}", new { controller = "Home", action = "About", }, Home_About, "Home:About()"),
|
||||
new RoutePatternEndpoint("Admin/Index/{id?}", new { controller = "Admin", action = "Index", }, Admin_Index, "Admin:Index()"),
|
||||
new RoutePatternEndpoint("Admin/Users/{id?}", new { controller = "Admin", action = "Users", }, "GET", Admin_GetUsers, "Admin:GetUsers()", new AuthorizationPolicyMetadata("Admin")),
|
||||
new RoutePatternEndpoint("Admin/Users/{id?}", new { controller = "Admin", action = "Users", }, "POST", Admin_EditUsers, "Admin:EditUsers()", new AuthorizationPolicyMetadata("Admin")),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
|
||||
{
|
||||
app.UseDispatcher();
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
logger.LogInformation("Executing fake CORS middleware");
|
||||
|
||||
var feature = context.Features.Get<IDispatcherFeature>();
|
||||
var policy = feature.Endpoint?.Metadata.GetMetadata<ICorsPolicyMetadata>();
|
||||
logger.LogInformation("using CORS policy {PolicyName}", policy?.Name ?? "default");
|
||||
|
||||
await next.Invoke();
|
||||
});
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
logger.LogInformation("Executing fake AuthZ middleware");
|
||||
|
||||
var feature = context.Features.Get<IDispatcherFeature>();
|
||||
var policy = feature.Endpoint?.Metadata.GetMetadata<IAuthorizationPolicyMetadata>();
|
||||
if (policy != null)
|
||||
{
|
||||
logger.LogInformation("using Auth policy {PolicyName}", policy.Name);
|
||||
}
|
||||
|
||||
await next.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public static Task Home_Index(HttpContext httpContext)
|
||||
{
|
||||
var templateFactory = httpContext.RequestServices.GetRequiredService<TemplateFactory>();
|
||||
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<h1>Some links you can visit</h1>" +
|
||||
$"<p><a href=\"{templateFactory.GetTemplate(new { controller = "Home", action = "Index", }).GetUrl(httpContext)}\">Home:Index()</a></p>" +
|
||||
$"<p><a href=\"{templateFactory.GetTemplate(new { controller = "Home", action = "About", }).GetUrl(httpContext)}\">Home:About()</a></p>" +
|
||||
$"<p><a href=\"{templateFactory.GetTemplate(new { controller = "Admin", action = "Index", }).GetUrl(httpContext)}\">Admin:Index()</a></p>" +
|
||||
$"<p><a href=\"{templateFactory.GetTemplate(new { controller = "Admin", action = "Users", }).GetUrl(httpContext)}\">Admin:GetUsers()/Admin:EditUsers()</a></p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
|
||||
public static Task Home_About(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<p>This is a dispatcher sample.</p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
|
||||
public static Task Admin_Index(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<p>This is the admin page.</p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
|
||||
public static Task Admin_GetUsers(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<p>Users: rynowak, jbagga</p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
|
||||
public static Task Admin_EditUsers(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Response.WriteAsync(
|
||||
$"<html>" +
|
||||
$"<body>" +
|
||||
$"<p>blerp</p>" +
|
||||
$"</body>" +
|
||||
$"</html>");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Microsoft.AspNetCore.Routing.DecisionTree.Sources</id>
|
||||
<version>2.1.0-preview1-t000</version>
|
||||
<authors>sharedsources</authors>
|
||||
<owners>sharedsources</owners>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Microsoft.AspNetCore.Routing.DecisionTree.Sources</description>
|
||||
<repository type="git" url="https://github.com/aspnet/Routing" />
|
||||
<dependencies>
|
||||
<group targetFramework=".NETStandard1.0" />
|
||||
</dependencies>
|
||||
<contentFiles>
|
||||
<files include="cs/netstandard1.0/DecisionCriterion.cs" buildAction="Compile" />
|
||||
<files include="cs/netstandard1.0/DecisionCriterionValue.cs" buildAction="Compile" />
|
||||
<files include="cs/netstandard1.0/DecisionCriterionValueEqualityComparer.cs" buildAction="Compile" />
|
||||
<files include="cs/netstandard1.0/DecisionTreeBuilder.cs" buildAction="Compile" />
|
||||
<files include="cs/netstandard1.0/DecisionTreeNode.cs" buildAction="Compile" />
|
||||
<files include="cs/netstandard1.0/IClassifier.cs" buildAction="Compile" />
|
||||
<files include="cs/netstandard1.0/ItemDescriptor.cs" buildAction="Compile" />
|
||||
</contentFiles>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="D:\k\Routing\shared\Microsoft.AspNetCore.Routing.DecisionTree.Sources\DecisionCriterion.cs" target="contentFiles\cs\netstandard1.0\DecisionCriterion.cs" />
|
||||
<file src="D:\k\Routing\shared\Microsoft.AspNetCore.Routing.DecisionTree.Sources\DecisionCriterionValue.cs" target="contentFiles\cs\netstandard1.0\DecisionCriterionValue.cs" />
|
||||
<file src="D:\k\Routing\shared\Microsoft.AspNetCore.Routing.DecisionTree.Sources\DecisionCriterionValueEqualityComparer.cs" target="contentFiles\cs\netstandard1.0\DecisionCriterionValueEqualityComparer.cs" />
|
||||
<file src="D:\k\Routing\shared\Microsoft.AspNetCore.Routing.DecisionTree.Sources\DecisionTreeBuilder.cs" target="contentFiles\cs\netstandard1.0\DecisionTreeBuilder.cs" />
|
||||
<file src="D:\k\Routing\shared\Microsoft.AspNetCore.Routing.DecisionTree.Sources\DecisionTreeNode.cs" target="contentFiles\cs\netstandard1.0\DecisionTreeNode.cs" />
|
||||
<file src="D:\k\Routing\shared\Microsoft.AspNetCore.Routing.DecisionTree.Sources\IClassifier.cs" target="contentFiles\cs\netstandard1.0\IClassifier.cs" />
|
||||
<file src="D:\k\Routing\shared\Microsoft.AspNetCore.Routing.DecisionTree.Sources\ItemDescriptor.cs" target="contentFiles\cs\netstandard1.0\ItemDescriptor.cs" />
|
||||
</files>
|
||||
</package>
|
|
@ -1,52 +0,0 @@
|
|||
{
|
||||
"version": 3,
|
||||
"targets": {
|
||||
".NETStandard,Version=v1.0": {}
|
||||
},
|
||||
"libraries": {},
|
||||
"projectFileDependencyGroups": {
|
||||
".NETStandard,Version=v1.0": []
|
||||
},
|
||||
"packageFolders": {
|
||||
"C:\\Users\\rynowak\\.nuget\\packages\\": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "2.1.0-preview1-t000",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\rynowak\\.dotnet\\buildtools\\korebuild\\2.1.0-preview1-15509\\modules\\sharedsources\\sharedsources.csproj",
|
||||
"projectName": "Microsoft.AspNetCore.Routing.DecisionTree.Sources",
|
||||
"projectPath": "C:\\Users\\rynowak\\.dotnet\\buildtools\\korebuild\\2.1.0-preview1-15509\\modules\\sharedsources\\sharedsources.csproj",
|
||||
"packagesPath": "C:\\Users\\rynowak\\.nuget\\packages\\",
|
||||
"outputPath": "D:\\k\\Routing/shared/Microsoft.AspNetCore.Routing.DecisionTree.Sourcesobj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\rynowak\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"netstandard1.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"C:\\Users\\rynowak\\.dotnet\\NuGetFallbackFolder": {},
|
||||
"C:\\Users\\rynowak\\.dotnet\\x64\\sdk\\NuGetFallbackFolder": {},
|
||||
"C:\\Users\\rynowak\\private_nuget": {},
|
||||
"https://www.nuget.org/api/v2/": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandard1.0": {
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandard1.0": {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">D:\k\Routing\shared\Microsoft.AspNetCore.Routing.DecisionTree.Sourcesobj\project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\rynowak\.nuget\packages\</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">4.4.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -1,119 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Dispatcher.Internal;
|
||||
#if ROUTING
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Tree
|
||||
#elif DISPATCHER
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
{
|
||||
internal struct TreeEnumerator : IEnumerator<UrlMatchingNode>
|
||||
{
|
||||
private readonly Stack<UrlMatchingNode> _stack;
|
||||
private readonly PathTokenizer _tokenizer;
|
||||
|
||||
public TreeEnumerator(UrlMatchingNode root, PathTokenizer tokenizer)
|
||||
{
|
||||
_stack = new Stack<UrlMatchingNode>();
|
||||
_tokenizer = tokenizer;
|
||||
Current = null;
|
||||
|
||||
_stack.Push(root);
|
||||
}
|
||||
|
||||
public UrlMatchingNode Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_stack == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (_stack.Count > 0)
|
||||
{
|
||||
var next = _stack.Pop();
|
||||
|
||||
// In case of wild card segment, the request path segment length can be greater
|
||||
// Example:
|
||||
// Template: a/{*path}
|
||||
// Request Url: a/b/c/d
|
||||
if (next.IsCatchAll && next.Matches.Count > 0)
|
||||
{
|
||||
Current = next;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Next template has the same length as the url we are trying to match
|
||||
// The only possible matching segments are either our current matches or
|
||||
// any catch-all segment after this segment in which the catch all is empty.
|
||||
else if (next.Depth >= _tokenizer.Count)
|
||||
{
|
||||
if (next.Matches.Count > 0)
|
||||
{
|
||||
Current = next;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We can stop looking as any other child node from this node will be
|
||||
// either a literal, a constrained parameter or a parameter.
|
||||
// (Catch alls and constrained catch alls will show up as candidate matches).
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (next.CatchAlls != null)
|
||||
{
|
||||
_stack.Push(next.CatchAlls);
|
||||
}
|
||||
|
||||
if (next.ConstrainedCatchAlls != null)
|
||||
{
|
||||
_stack.Push(next.ConstrainedCatchAlls);
|
||||
}
|
||||
|
||||
if (next.Parameters != null)
|
||||
{
|
||||
_stack.Push(next.Parameters);
|
||||
}
|
||||
|
||||
if (next.ConstrainedParameters != null)
|
||||
{
|
||||
_stack.Push(next.ConstrainedParameters);
|
||||
}
|
||||
|
||||
if (next.Literals.Count > 0)
|
||||
{
|
||||
Debug.Assert(next.Depth < _tokenizer.Count);
|
||||
if (next.Literals.TryGetValue(_tokenizer[next.Depth].Value, out var node))
|
||||
{
|
||||
_stack.Push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_stack.Clear();
|
||||
Current = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||
#if ROUTING
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Tree
|
||||
#elif DISPATCHER
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
{
|
||||
#if ROUTING
|
||||
public
|
||||
#elif DISPATCHER
|
||||
internal
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
class UrlMatchingTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="UrlMatchingTree"/>.
|
||||
/// </summary>
|
||||
/// <param name="order">The order associated with routes in this <see cref="UrlMatchingTree"/>.</param>
|
||||
public UrlMatchingTree(int order)
|
||||
{
|
||||
Order = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the order of the routes associated with this <see cref="UrlMatchingTree"/>.
|
||||
/// </summary>
|
||||
public int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root of the <see cref="UrlMatchingTree"/>.
|
||||
/// </summary>
|
||||
public UrlMatchingNode Root { get; } = new UrlMatchingNode(0);
|
||||
|
||||
internal static void AddEntryToTree(UrlMatchingTree tree, InboundRouteEntry entry)
|
||||
{
|
||||
// The url matching tree represents all the routes asociated with a given
|
||||
// order. Each node in the tree represents all the different categories
|
||||
// a segment can have for which there is a defined inbound route entry.
|
||||
// Each node contains a set of Matches that indicate all the routes for which
|
||||
// a URL is a potential match. This list contains the routes with the same
|
||||
// number of segments and the routes with the same number of segments plus an
|
||||
// additional catch all parameter (as it can be empty).
|
||||
// For example, for a set of routes like:
|
||||
// 'Customer/Index/{id}'
|
||||
// '{Controller}/{Action}/{*parameters}'
|
||||
//
|
||||
// The route tree will look like:
|
||||
// Root ->
|
||||
// Literals: Customer ->
|
||||
// Literals: Index ->
|
||||
// Parameters: {id}
|
||||
// Matches: 'Customer/Index/{id}'
|
||||
// Parameters: {Controller} ->
|
||||
// Parameters: {Action} ->
|
||||
// Matches: '{Controller}/{Action}/{*parameters}'
|
||||
// CatchAlls: {*parameters}
|
||||
// Matches: '{Controller}/{Action}/{*parameters}'
|
||||
//
|
||||
// When the tree router tries to match a route, it iterates the list of url matching trees
|
||||
// in ascending order. For each tree it traverses each node starting from the root in the
|
||||
// following order: Literals, constrained parameters, parameters, constrained catch all routes, catch alls.
|
||||
// When it gets to a node of the same length as the route its trying to match, it simply looks at the list of
|
||||
// candidates (which is in precence order) and tries to match the url against it.
|
||||
|
||||
var current = tree.Root;
|
||||
#if ROUTING
|
||||
var routePattern = entry.RouteTemplate.ToRoutePattern();
|
||||
var matcher = new TemplateMatcher(entry.RouteTemplate, entry.Defaults);
|
||||
#elif DISPATCHER
|
||||
var routePattern = entry.RoutePattern;
|
||||
var matcher = new RoutePatternMatcher(routePattern, entry.Defaults);
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
|
||||
for (var i = 0; i < routePattern.PathSegments.Count; i++)
|
||||
{
|
||||
var segment = routePattern.PathSegments[i];
|
||||
if (!segment.IsSimple)
|
||||
{
|
||||
// Treat complex segments as a constrained parameter
|
||||
if (current.ConstrainedParameters == null)
|
||||
{
|
||||
current.ConstrainedParameters = new UrlMatchingNode(i + 1);
|
||||
}
|
||||
|
||||
current = current.ConstrainedParameters;
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.Assert(segment.Parts.Count == 1);
|
||||
var part = segment.Parts[0];
|
||||
if (part.IsLiteral)
|
||||
{
|
||||
var literal = (RoutePatternLiteral)part;
|
||||
if (!current.Literals.TryGetValue(literal.Content, out var next))
|
||||
{
|
||||
next = new UrlMatchingNode(i + 1);
|
||||
current.Literals.Add(literal.Content, next);
|
||||
}
|
||||
|
||||
current = next;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We accept templates that have intermediate optional values, but we ignore
|
||||
// those values for route matching. For that reason, we need to add the entry
|
||||
// to the list of matches, only if the remaining segments are optional. For example:
|
||||
// /{controller}/{action=Index}/{id} will be equivalent to /{controller}/{action}/{id}
|
||||
// for the purposes of route matching.
|
||||
if (part.IsParameter &&
|
||||
RemainingSegmentsAreOptional(routePattern.PathSegments, i))
|
||||
{
|
||||
#if ROUTING
|
||||
current.Matches.Add(new InboundMatch() { Entry = entry, TemplateMatcher = matcher });
|
||||
#elif DISPATCHER
|
||||
current.Matches.Add(new InboundMatch() { Entry = entry, RoutePatternMatcher = matcher });
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
}
|
||||
|
||||
var parameter = (RoutePatternParameter)part;
|
||||
if (parameter != null && parameter.Constraints.Any() && !parameter.IsCatchAll)
|
||||
{
|
||||
if (current.ConstrainedParameters == null)
|
||||
{
|
||||
current.ConstrainedParameters = new UrlMatchingNode(i + 1);
|
||||
}
|
||||
|
||||
current = current.ConstrainedParameters;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parameter != null && !parameter.IsCatchAll)
|
||||
{
|
||||
if (current.Parameters == null)
|
||||
{
|
||||
current.Parameters = new UrlMatchingNode(i + 1);
|
||||
}
|
||||
|
||||
current = current.Parameters;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parameter != null && parameter.Constraints.Any() && parameter.IsCatchAll)
|
||||
{
|
||||
if (current.ConstrainedCatchAlls == null)
|
||||
{
|
||||
current.ConstrainedCatchAlls = new UrlMatchingNode(i + 1) { IsCatchAll = true };
|
||||
}
|
||||
|
||||
current = current.ConstrainedCatchAlls;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parameter != null && parameter.IsCatchAll)
|
||||
{
|
||||
if (current.CatchAlls == null)
|
||||
{
|
||||
current.CatchAlls = new UrlMatchingNode(i + 1) { IsCatchAll = true };
|
||||
}
|
||||
|
||||
current = current.CatchAlls;
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.Fail("We shouldn't get here.");
|
||||
}
|
||||
|
||||
#if ROUTING
|
||||
current.Matches.Add(new InboundMatch() { Entry = entry, TemplateMatcher = matcher });
|
||||
current.Matches.Sort((x, y) =>
|
||||
{
|
||||
var result = x.Entry.Precedence.CompareTo(y.Entry.Precedence);
|
||||
return result == 0 ? x.Entry.RouteTemplate.TemplateText.CompareTo(y.Entry.RouteTemplate.TemplateText) : result;
|
||||
});
|
||||
#elif DISPATCHER
|
||||
current.Matches.Add(new InboundMatch() { Entry = entry, RoutePatternMatcher = matcher });
|
||||
current.Matches.Sort((x, y) =>
|
||||
{
|
||||
var result = x.Entry.Precedence.CompareTo(y.Entry.Precedence);
|
||||
return result == 0 ? x.Entry.RoutePattern.RawText.CompareTo(y.Entry.RoutePattern.RawText) : result;
|
||||
});
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
private static bool RemainingSegmentsAreOptional(IReadOnlyList<RoutePatternPathSegment> segments, int currentParameterIndex)
|
||||
{
|
||||
for (var i = currentParameterIndex; i < segments.Count; i++)
|
||||
{
|
||||
if (!segments[i].IsSimple)
|
||||
{
|
||||
// /{complex}-{segment}
|
||||
return false;
|
||||
}
|
||||
|
||||
var part = segments[i].Parts[0];
|
||||
if (!part.IsParameter)
|
||||
{
|
||||
// /literal
|
||||
return false;
|
||||
}
|
||||
|
||||
var parameter = (RoutePatternParameter)part;
|
||||
var isOptionalCatchAllOrHasDefaultValue = parameter.IsOptional ||
|
||||
parameter.IsCatchAll ||
|
||||
parameter.DefaultValue != null;
|
||||
|
||||
if (!isOptionalCatchAllOrHasDefaultValue)
|
||||
{
|
||||
// /{parameter}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +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.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
[DebuggerDisplay("{DisplayName,nq}")]
|
||||
public abstract class Address
|
||||
{
|
||||
public abstract string DisplayName { get; }
|
||||
|
||||
public abstract MetadataCollection Metadata { get; }
|
||||
}
|
||||
}
|
|
@ -1,790 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Dispatcher.Abstractions;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IDictionary{String, Object}"/> type for dispatcher values.
|
||||
/// </summary>
|
||||
public class DispatcherValueCollection : IDictionary<string, object>, IReadOnlyDictionary<string, object>
|
||||
{
|
||||
internal Storage _storage;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty <see cref="DispatcherValueCollection"/>.
|
||||
/// </summary>
|
||||
public DispatcherValueCollection()
|
||||
{
|
||||
_storage = EmptyStorage.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="DispatcherValueCollection"/> initialized with the specified <paramref name="values"/>.
|
||||
/// </summary>
|
||||
/// <param name="values">An object to initialize the dictionary. The value can be of type
|
||||
/// <see cref="IDictionary{TKey, TValue}"/> or <see cref="IReadOnlyDictionary{TKey, TValue}"/>
|
||||
/// or an object with public properties as key-value pairs.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// If the value is a dictionary or other <see cref="IEnumerable{T}"/> of <see cref="KeyValuePair{String, Object}"/>,
|
||||
/// then its entries are copied. Otherwise the object is interpreted as a set of key-value pairs where the
|
||||
/// property names are keys, and property values are the values, and copied into the dictionary.
|
||||
/// Only public instance non-index properties are considered.
|
||||
/// </remarks>
|
||||
public DispatcherValueCollection(object values)
|
||||
{
|
||||
var dictionary = values as DispatcherValueCollection;
|
||||
if (dictionary != null)
|
||||
{
|
||||
var listStorage = dictionary._storage as ListStorage;
|
||||
if (listStorage != null)
|
||||
{
|
||||
_storage = new ListStorage(listStorage);
|
||||
return;
|
||||
}
|
||||
|
||||
var propertyStorage = dictionary._storage as PropertyStorage;
|
||||
if (propertyStorage != null)
|
||||
{
|
||||
// PropertyStorage is immutable so we can just copy it.
|
||||
_storage = dictionary._storage;
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get here, it's an EmptyStorage.
|
||||
_storage = EmptyStorage.Instance;
|
||||
return;
|
||||
}
|
||||
|
||||
var keyValueEnumerable = values as IEnumerable<KeyValuePair<string, object>>;
|
||||
if (keyValueEnumerable != null)
|
||||
{
|
||||
var listStorage = new ListStorage();
|
||||
_storage = listStorage;
|
||||
foreach (var kvp in keyValueEnumerable)
|
||||
{
|
||||
if (listStorage.ContainsKey(kvp.Key))
|
||||
{
|
||||
var message = Resources.FormatDispatcherValueCollection_DuplicateKey(kvp.Key, nameof(DispatcherValueCollection));
|
||||
throw new ArgumentException(message, nameof(values));
|
||||
}
|
||||
|
||||
listStorage.Add(kvp);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var stringValueEnumerable = values as IEnumerable<KeyValuePair<string, string>>;
|
||||
if (stringValueEnumerable != null)
|
||||
{
|
||||
var listStorage = new ListStorage();
|
||||
_storage = listStorage;
|
||||
foreach (var kvp in stringValueEnumerable)
|
||||
{
|
||||
if (listStorage.ContainsKey(kvp.Key))
|
||||
{
|
||||
var message = Resources.FormatDispatcherValueCollection_DuplicateKey(kvp.Key, nameof(DispatcherValueCollection));
|
||||
throw new ArgumentException(message, nameof(values));
|
||||
}
|
||||
|
||||
listStorage.Add(new KeyValuePair<string, object>(kvp.Key, kvp.Value));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (values != null)
|
||||
{
|
||||
_storage = new PropertyStorage(values);
|
||||
return;
|
||||
}
|
||||
|
||||
_storage = EmptyStorage.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
object value;
|
||||
TryGetValue(key, out value);
|
||||
return value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (!_storage.TrySetValue(key, value))
|
||||
{
|
||||
Upgrade();
|
||||
_storage.TrySetValue(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the comparer for this dictionary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will always be a reference to <see cref="StringComparer.OrdinalIgnoreCase"/>
|
||||
/// </remarks>
|
||||
public IEqualityComparer<string> Comparer => StringComparer.OrdinalIgnoreCase;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _storage.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
var keys = new string[list.Count];
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
{
|
||||
keys[i] = list[i].Key;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<string> IReadOnlyDictionary<string, object>.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<object> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
var values = new object[list.Count];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
values[i] = list[i].Value;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<object> IReadOnlyDictionary<string, object>.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return Values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(string key, object value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var message = Resources.FormatDispatcherValueCollection_DuplicateKey(key, nameof(DispatcherValueCollection));
|
||||
throw new ArgumentException(message, nameof(key));
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(new KeyValuePair<string, object>(key, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
if (_storage.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
|
||||
{
|
||||
if (_storage.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return EqualityComparer<object>.Default.Equals(list[i].Value, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _storage.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void ICollection<KeyValuePair<string, object>>.CopyTo(
|
||||
KeyValuePair<string, object>[] array,
|
||||
int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
if (_storage.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
|
||||
{
|
||||
if (_storage.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase) &&
|
||||
EqualityComparer<object>.Default.Equals(list[i].Value, item.Value))
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (_storage.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Upgrade();
|
||||
|
||||
var list = (ListStorage)_storage;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetValue(string key, out object value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _storage.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
private void Upgrade()
|
||||
{
|
||||
_storage.Upgrade(ref _storage);
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
|
||||
{
|
||||
private readonly Storage _storage;
|
||||
private int _index;
|
||||
|
||||
public Enumerator(DispatcherValueCollection collection)
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
_storage = collection._storage;
|
||||
|
||||
Current = default(KeyValuePair<string, object>);
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
public KeyValuePair<string, object> Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++_index < _storage.Count)
|
||||
{
|
||||
Current = _storage[_index];
|
||||
return true;
|
||||
}
|
||||
|
||||
Current = default(KeyValuePair<string, object>);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Current = default(KeyValuePair<string, object>);
|
||||
_index = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Storage and its subclasses are internal for testing.
|
||||
internal abstract class Storage
|
||||
{
|
||||
public abstract int Count { get; }
|
||||
|
||||
public abstract KeyValuePair<string, object> this[int index] { get; set; }
|
||||
|
||||
public abstract void Upgrade(ref Storage storage);
|
||||
|
||||
public abstract bool TryGetValue(string key, out object value);
|
||||
|
||||
public abstract bool ContainsKey(string key);
|
||||
|
||||
public abstract bool TrySetValue(string key, object value);
|
||||
}
|
||||
|
||||
internal class ListStorage : Storage
|
||||
{
|
||||
private KeyValuePair<string, object>[] _items;
|
||||
private int _count;
|
||||
|
||||
private static readonly KeyValuePair<string, object>[] _emptyArray = new KeyValuePair<string, object>[0];
|
||||
|
||||
public ListStorage()
|
||||
{
|
||||
_items = _emptyArray;
|
||||
}
|
||||
|
||||
public ListStorage(int capacity)
|
||||
{
|
||||
if (capacity == 0)
|
||||
{
|
||||
_items = _emptyArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
_items = new KeyValuePair<string, object>[capacity];
|
||||
}
|
||||
}
|
||||
|
||||
public ListStorage(ListStorage other)
|
||||
{
|
||||
if (other.Count == 0)
|
||||
{
|
||||
_items = _emptyArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
_items = new KeyValuePair<string, object>[other.Count];
|
||||
for (var i = 0; i < other.Count; i++)
|
||||
{
|
||||
this.Add(other[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Capacity => _items.Length;
|
||||
|
||||
public override int Count => _count;
|
||||
|
||||
public override KeyValuePair<string, object> this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= _count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
return _items[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (index < 0 || index >= _count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
_items[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
if (_count == _items.Length)
|
||||
{
|
||||
EnsureCapacity(_count + 1);
|
||||
}
|
||||
|
||||
_items[_count++] = item;
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
_count--;
|
||||
|
||||
for (var i = index; i < _count; i++)
|
||||
{
|
||||
_items[i] = _items[i + 1];
|
||||
}
|
||||
|
||||
_items[_count] = default(KeyValuePair<string, object>);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (var i = 0; i < _count; i++)
|
||||
{
|
||||
_items[i] = default(KeyValuePair<string, object>);
|
||||
}
|
||||
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
for (var i = 0; i < _count; i++)
|
||||
{
|
||||
array[arrayIndex++] = _items[i];
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ContainsKey(string key)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var kvp = _items[i];
|
||||
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TrySetValue(string key, object value)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var kvp = _items[i];
|
||||
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_items[i] = new KeyValuePair<string, object>(key, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Add(new KeyValuePair<string, object>(key, value));
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryGetValue(string key, out object value)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var kvp = _items[i];
|
||||
if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = kvp.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Upgrade(ref Storage storage)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int min)
|
||||
{
|
||||
var newLength = _items.Length == 0 ? 4 : _items.Length * 2;
|
||||
var newItems = new KeyValuePair<string, object>[newLength];
|
||||
for (var i = 0; i < _count; i++)
|
||||
{
|
||||
newItems[i] = _items[i];
|
||||
}
|
||||
|
||||
_items = newItems;
|
||||
}
|
||||
}
|
||||
|
||||
internal class PropertyStorage : Storage
|
||||
{
|
||||
private static readonly PropertyCache _propertyCache = new PropertyCache();
|
||||
|
||||
internal readonly object _value;
|
||||
internal readonly PropertyHelper[] _properties;
|
||||
|
||||
public PropertyStorage(object value)
|
||||
{
|
||||
Debug.Assert(value != null);
|
||||
_value = value;
|
||||
|
||||
// Cache the properties so we can know if we've already validated them for duplicates.
|
||||
var type = _value.GetType();
|
||||
if (!_propertyCache.TryGetValue(type, out _properties))
|
||||
{
|
||||
_properties = PropertyHelper.GetVisibleProperties(type);
|
||||
ValidatePropertyNames(type, _properties);
|
||||
_propertyCache.TryAdd(type, _properties);
|
||||
}
|
||||
}
|
||||
|
||||
public PropertyStorage(PropertyStorage propertyStorage)
|
||||
{
|
||||
_value = propertyStorage._value;
|
||||
_properties = propertyStorage._properties;
|
||||
}
|
||||
|
||||
public override int Count => _properties.Length;
|
||||
|
||||
public override KeyValuePair<string, object> this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var property = _properties[index];
|
||||
return new KeyValuePair<string, object>(property.Name, property.GetValue(_value));
|
||||
}
|
||||
set
|
||||
{
|
||||
// PropertyStorage never sets a value.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool TryGetValue(string key, out object value)
|
||||
{
|
||||
for (var i = 0; i < _properties.Length; i++)
|
||||
{
|
||||
var property = _properties[i];
|
||||
if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = property.GetValue(_value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool ContainsKey(string key)
|
||||
{
|
||||
for (var i = 0; i < _properties.Length; i++)
|
||||
{
|
||||
var property = _properties[i];
|
||||
if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TrySetValue(string key, object value)
|
||||
{
|
||||
// PropertyStorage never sets a value.
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Upgrade(ref Storage storage)
|
||||
{
|
||||
storage = new ListStorage(Count);
|
||||
for (var i = 0; i < _properties.Length; i++)
|
||||
{
|
||||
var property = _properties[i];
|
||||
storage.TrySetValue(property.Name, property.GetValue(_value));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidatePropertyNames(Type type, PropertyHelper[] properties)
|
||||
{
|
||||
var names = new Dictionary<string, PropertyHelper>(StringComparer.OrdinalIgnoreCase);
|
||||
for (var i = 0; i < properties.Length; i++)
|
||||
{
|
||||
var property = properties[i];
|
||||
|
||||
PropertyHelper duplicate;
|
||||
if (names.TryGetValue(property.Name, out duplicate))
|
||||
{
|
||||
var message = Resources.FormatDispatcherValueCollection_DuplicatePropertyName(
|
||||
type.FullName,
|
||||
property.Name,
|
||||
duplicate.Name,
|
||||
nameof(DispatcherValueCollection));
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
names.Add(property.Name, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class EmptyStorage : Storage
|
||||
{
|
||||
public static readonly EmptyStorage Instance = new EmptyStorage();
|
||||
|
||||
private EmptyStorage()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Count => 0;
|
||||
|
||||
public override KeyValuePair<string, object> this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ContainsKey(string key)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryGetValue(string key, out object value)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TrySetValue(string key, object value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Upgrade(ref Storage storage)
|
||||
{
|
||||
storage = new ListStorage();
|
||||
}
|
||||
}
|
||||
|
||||
private class PropertyCache : ConcurrentDictionary<Type, PropertyHelper[]>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +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.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
[DebuggerDisplay("{GetType().Name} - '{DisplayName,nq}'")]
|
||||
public abstract class Endpoint
|
||||
{
|
||||
public abstract string DisplayName { get; }
|
||||
|
||||
public abstract MetadataCollection Metadata { get; }
|
||||
}
|
||||
}
|
|
@ -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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IDispatcherFeature
|
||||
{
|
||||
Endpoint Endpoint { get; set; }
|
||||
|
||||
Func<RequestDelegate, RequestDelegate> Handler { get; set; }
|
||||
|
||||
DispatcherValueCollection Values { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class MetadataCollection : IReadOnlyList<object>
|
||||
{
|
||||
private readonly object[] _items;
|
||||
|
||||
public MetadataCollection()
|
||||
{
|
||||
_items = Array.Empty<object>();
|
||||
}
|
||||
|
||||
public MetadataCollection(IEnumerable<object> items)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
}
|
||||
|
||||
_items = items.ToArray();
|
||||
}
|
||||
|
||||
public object this[int index] => _items[index];
|
||||
|
||||
public int Count => _items.Length;
|
||||
|
||||
public T GetMetadata<T>() where T : class
|
||||
{
|
||||
for (var i = _items.Length -1; i >= 0; i--)
|
||||
{
|
||||
var item = _items[i] as T;
|
||||
if (item !=null)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetOrderedMetadata<T>() where T : class
|
||||
{
|
||||
for (var i = 0; i < _items.Length; i++)
|
||||
{
|
||||
var item = _items[i] as T;
|
||||
if (item != null)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator() => new Enumerator(this);
|
||||
|
||||
IEnumerator<object> IEnumerable<object>.GetEnumerator() => GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public struct Enumerator : IEnumerator<object>
|
||||
{
|
||||
private object[] _items;
|
||||
private int _index;
|
||||
private object _current;
|
||||
|
||||
internal Enumerator(MetadataCollection collection)
|
||||
{
|
||||
_items = collection._items;
|
||||
_index = 0;
|
||||
_current = null;
|
||||
}
|
||||
|
||||
public object Current => _current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index < _items.Length)
|
||||
{
|
||||
_current = _items[_index++];
|
||||
return true;
|
||||
}
|
||||
|
||||
_current = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = 0;
|
||||
_current = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core abstractions for dispatching requests to endpoints and use addresses to generate URLs.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;routing;dispatcher</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsPropertyHelperSourcesPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,6 +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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Dispatcher.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
|
@ -1,58 +0,0 @@
|
|||
// <auto-generated />
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Abstractions
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNetCore.Dispatcher.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// An element with the key '{0}' already exists in the {1}.
|
||||
/// </summary>
|
||||
internal static string DispatcherValueCollection_DuplicateKey
|
||||
{
|
||||
get => GetString("DispatcherValueCollection_DuplicateKey");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An element with the key '{0}' already exists in the {1}.
|
||||
/// </summary>
|
||||
internal static string FormatDispatcherValueCollection_DuplicateKey(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherValueCollection_DuplicateKey"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.
|
||||
/// </summary>
|
||||
internal static string DispatcherValueCollection_DuplicatePropertyName
|
||||
{
|
||||
get => GetString("DispatcherValueCollection_DuplicatePropertyName");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.
|
||||
/// </summary>
|
||||
internal static string FormatDispatcherValueCollection_DuplicatePropertyName(object p0, object p1, object p2, object p3)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherValueCollection_DuplicatePropertyName"), p0, p1, p2, p3);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
||||
System.Diagnostics.Debug.Assert(value != null);
|
||||
|
||||
if (formatterNames != null)
|
||||
{
|
||||
for (var i = 0; i < formatterNames.Length; i++)
|
||||
{
|
||||
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="DispatcherValueCollection_DuplicateKey" xml:space="preserve">
|
||||
<value>An element with the key '{0}' already exists in the {1}.</value>
|
||||
</data>
|
||||
<data name="DispatcherValueCollection_DuplicatePropertyName" xml:space="preserve">
|
||||
<value>The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1,27 +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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public abstract class Template
|
||||
{
|
||||
public virtual string GetUrl()
|
||||
{
|
||||
return GetUrl(null, new DispatcherValueCollection());
|
||||
}
|
||||
|
||||
public virtual string GetUrl(HttpContext httpContext)
|
||||
{
|
||||
return GetUrl(httpContext, new DispatcherValueCollection());
|
||||
}
|
||||
|
||||
public virtual string GetUrl(DispatcherValueCollection values)
|
||||
{
|
||||
return GetUrl(null, values);
|
||||
}
|
||||
|
||||
public abstract string GetUrl(HttpContext httpContext, DispatcherValueCollection values);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public abstract class TemplateFactory
|
||||
{
|
||||
public Template GetTemplate(object values)
|
||||
{
|
||||
return GetTemplateFromKey(new DispatcherValueCollection(values));
|
||||
}
|
||||
|
||||
public abstract Template GetTemplateFromKey<TKey>(TKey key) where TKey : class;
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
{
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public abstract class AddressTable
|
||||
{
|
||||
public abstract IReadOnlyList<IReadOnlyList<Address>> AddressGroups { get; }
|
||||
}
|
||||
}
|
|
@ -1,25 +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.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception which indicates multiple matches in endpoint selection.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AmbiguousEndpointException : Exception
|
||||
{
|
||||
public AmbiguousEndpointException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
protected AmbiguousEndpointException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public static class DispatcherApplicationBuilderExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseDispatcher(this IApplicationBuilder builder)
|
||||
{
|
||||
builder.Properties.Add("Dispatcher", true);
|
||||
return builder.UseMiddleware<DispatcherMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class CompositeDispatcherDataSource : DispatcherDataSource
|
||||
{
|
||||
private readonly DispatcherDataSource[] _dataSources;
|
||||
|
||||
public CompositeDispatcherDataSource(IEnumerable<DispatcherDataSource> dataSources)
|
||||
{
|
||||
if (dataSources == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataSources));
|
||||
}
|
||||
|
||||
_dataSources = dataSources.ToArray();
|
||||
|
||||
var changeTokens = new IChangeToken[_dataSources.Length];
|
||||
for (var i = 0; i < _dataSources.Length; i++)
|
||||
{
|
||||
changeTokens[i] = _dataSources[i].ChangeToken;
|
||||
}
|
||||
|
||||
ChangeToken = new CompositeChangeToken(changeTokens);
|
||||
}
|
||||
|
||||
public override IChangeToken ChangeToken { get; }
|
||||
|
||||
protected override IReadOnlyList<Address> GetAddresses()
|
||||
{
|
||||
var addresses = new List<Address>();
|
||||
for (var i = 0; i < _dataSources.Length; i++)
|
||||
{
|
||||
addresses.AddRange(((IAddressCollectionProvider)_dataSources[i]).Addresses);
|
||||
}
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<Endpoint> GetEndpoints()
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
for (var i = 0; i < _dataSources.Length; i++)
|
||||
{
|
||||
endpoints.AddRange(((IEndpointCollectionProvider)_dataSources[i]).Endpoints);
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class CompositeHandlerFactory : IHandlerFactory
|
||||
{
|
||||
private readonly IHandlerFactory[] _factories;
|
||||
|
||||
public CompositeHandlerFactory(IEnumerable<IHandlerFactory> factories)
|
||||
{
|
||||
if (factories == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factories));
|
||||
}
|
||||
|
||||
_factories = factories.ToArray();
|
||||
}
|
||||
|
||||
public Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint)
|
||||
{
|
||||
if (endpoint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoint));
|
||||
}
|
||||
|
||||
for (var i = 0; i < _factories.Length; i++)
|
||||
{
|
||||
var handler = _factories[i].CreateHandler(endpoint);
|
||||
if (handler != null)
|
||||
{
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the purpose for invoking an <see cref="IDispatcherValueConstraint"/>.
|
||||
/// </summary>
|
||||
public enum ConstraintPurpose
|
||||
{
|
||||
/// <summary>
|
||||
/// A request URL is being processed by the dispatcher.
|
||||
/// </summary>
|
||||
IncomingRequest,
|
||||
|
||||
/// <summary>
|
||||
/// A URL is being created by a template.
|
||||
/// </summary>
|
||||
TemplateExecution,
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a dispatcher value parameter to contain only lowercase or uppercase letters A through Z in the English alphabet.
|
||||
/// </summary>
|
||||
public class AlphaDispatcherValueConstraint : RegexDispatcherValueConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AlphaDispatcherValueConstraint" /> class.
|
||||
/// </summary>
|
||||
public AlphaDispatcherValueConstraint() : base(@"^[a-z]*$")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a dispatcher value by several child constraints.
|
||||
/// </summary>
|
||||
public class CompositeDispatcherValueConstraint : IDispatcherValueConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CompositeDispatcherValueConstraint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="constraints">The child constraints that must match for this constraint to match.</param>
|
||||
public CompositeDispatcherValueConstraint(IEnumerable<IDispatcherValueConstraint> constraints)
|
||||
{
|
||||
if (constraints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraints));
|
||||
}
|
||||
|
||||
Constraints = constraints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child constraints that must match for this constraint to match.
|
||||
/// </summary>
|
||||
public IEnumerable<IDispatcherValueConstraint> Constraints { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(DispatcherValueConstraintContext constraintContext)
|
||||
{
|
||||
if (constraintContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraintContext));
|
||||
}
|
||||
|
||||
foreach (var constraint in Constraints)
|
||||
{
|
||||
if (!constraint.Match(constraintContext))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The default implementation of <see cref="IConstraintFactory"/>. Resolves constraints by parsing
|
||||
/// a constraint key and constraint arguments, using a map to resolve the constraint type, and calling an
|
||||
/// appropriate constructor for the constraint type.
|
||||
/// </summary>
|
||||
public class DefaultConstraintFactory : IConstraintFactory
|
||||
{
|
||||
private readonly IDictionary<string, Type> _constraintMap;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultConstraintFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dispatcherOptions">
|
||||
/// Accessor for <see cref="DispatcherOptions"/> containing the constraints of interest.
|
||||
/// </param>
|
||||
public DefaultConstraintFactory(IOptions<DispatcherOptions> dispatcherOptions)
|
||||
{
|
||||
_constraintMap = dispatcherOptions.Value.ConstraintMap;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <example>
|
||||
/// A typical constraint looks like the following
|
||||
/// "exampleConstraint(arg1, arg2, 12)".
|
||||
/// Here if the type registered for exampleConstraint has a single constructor with one argument,
|
||||
/// The entire string "arg1, arg2, 12" will be treated as a single argument.
|
||||
/// In all other cases arguments are split at comma.
|
||||
/// </example>
|
||||
public virtual IDispatcherValueConstraint ResolveConstraint(string constraint)
|
||||
{
|
||||
if (constraint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraint));
|
||||
}
|
||||
|
||||
string constraintKey;
|
||||
string argumentString;
|
||||
var indexOfFirstOpenParens = constraint.IndexOf('(');
|
||||
if (indexOfFirstOpenParens >= 0 && constraint.EndsWith(")", StringComparison.Ordinal))
|
||||
{
|
||||
constraintKey = constraint.Substring(0, indexOfFirstOpenParens);
|
||||
argumentString = constraint.Substring(indexOfFirstOpenParens + 1,
|
||||
constraint.Length - indexOfFirstOpenParens - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
constraintKey = constraint;
|
||||
argumentString = null;
|
||||
}
|
||||
|
||||
if (!_constraintMap.TryGetValue(constraintKey, out var constraintType))
|
||||
{
|
||||
// Cannot resolve the constraint key
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!typeof(IDispatcherValueConstraint).GetTypeInfo().IsAssignableFrom(constraintType.GetTypeInfo()))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatDefaultConstraintResolver_TypeNotConstraint(
|
||||
constraintType, constraintKey, typeof(IDispatcherValueConstraint).Name));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return CreateConstraint(constraintType, argumentString);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"An error occurred while trying to create an instance of route constraint '{constraintType.FullName}'.",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static IDispatcherValueConstraint CreateConstraint(Type constraintType, string argumentString)
|
||||
{
|
||||
// No arguments - call the default constructor
|
||||
if (argumentString == null)
|
||||
{
|
||||
return (IDispatcherValueConstraint)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 RegexDispatcherValueConstraint 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 InvalidOperationException(
|
||||
Resources.FormatDefaultConstraintResolver_CouldNotFindCtor(
|
||||
constraintTypeInfo.Name, arguments.Length));
|
||||
}
|
||||
else if (constructorMatches == 1)
|
||||
{
|
||||
activationConstructor = matchingConstructors[0];
|
||||
parameters = ConvertArguments(activationConstructor.GetParameters(), arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatDefaultConstraintResolver_AmbiguousCtors(
|
||||
constraintTypeInfo.Name, arguments.Length));
|
||||
}
|
||||
}
|
||||
|
||||
return (IDispatcherValueConstraint)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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// A builder for producing a mapping of keys to <see cref="IDispatcherValueConstraint"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="DispatcherValueConstraintBuilder"/> allows iterative building a set of dispatcher value constraints, and will
|
||||
/// merge multiple entries for the same key.
|
||||
/// </remarks>
|
||||
public class DispatcherValueConstraintBuilder
|
||||
{
|
||||
private readonly IConstraintFactory _constraintFactory;
|
||||
private readonly string _rawText;
|
||||
private readonly Dictionary<string, List<IDispatcherValueConstraint>> _constraints;
|
||||
private readonly HashSet<string> _optionalParameters;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DispatcherValueConstraintBuilder"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="constraintFactory">The <see cref="IConstraintFactory"/>.</param>
|
||||
/// <param name="rawText">The display name (for use in error messages).</param>
|
||||
public DispatcherValueConstraintBuilder(
|
||||
IConstraintFactory constraintFactory,
|
||||
string rawText)
|
||||
{
|
||||
if (constraintFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraintFactory));
|
||||
}
|
||||
|
||||
if (rawText == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(rawText));
|
||||
}
|
||||
|
||||
_constraintFactory = constraintFactory;
|
||||
_rawText = rawText;
|
||||
|
||||
_constraints = new Dictionary<string, List<IDispatcherValueConstraint>>(StringComparer.OrdinalIgnoreCase);
|
||||
_optionalParameters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a mapping of constraints.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IDictionary{String, IDispatcherValueConstraint}"/> of the constraints.</returns>
|
||||
public IDictionary<string, IDispatcherValueConstraint> Build()
|
||||
{
|
||||
var constraints = new Dictionary<string, IDispatcherValueConstraint>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kvp in _constraints)
|
||||
{
|
||||
IDispatcherValueConstraint constraint;
|
||||
if (kvp.Value.Count == 1)
|
||||
{
|
||||
constraint = kvp.Value[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
constraint = new CompositeDispatcherValueConstraint(kvp.Value.ToArray());
|
||||
}
|
||||
|
||||
if (_optionalParameters.Contains(kvp.Key))
|
||||
{
|
||||
var optionalConstraint = new OptionalDispatcherValueConstraint(constraint);
|
||||
constraints.Add(kvp.Key, optionalConstraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
constraints.Add(kvp.Key, constraint);
|
||||
}
|
||||
}
|
||||
|
||||
return constraints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a constraint instance for the given key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">
|
||||
/// The constraint instance. Must either be a string or an instance of <see cref="IDispatcherValueConstraint"/>.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// If the <paramref name="value"/> is a string, it will be converted to a <see cref="RegexDispatcherValueConstraint"/>.
|
||||
///
|
||||
/// For example, the string <code>Product[0-9]+</code> will be converted to the regular expression
|
||||
/// <code>^(Product[0-9]+)</code>. See <see cref="System.Text.RegularExpressions.Regex"/> for more details.
|
||||
/// </remarks>
|
||||
public void AddConstraint(string key, object value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
var constraint = value as IDispatcherValueConstraint;
|
||||
if (constraint == null)
|
||||
{
|
||||
var regexPattern = value as string;
|
||||
if (regexPattern == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatDispatcherValueConstraintBuilder_ValidationMustBeStringOrCustomConstraint(
|
||||
key,
|
||||
value,
|
||||
_rawText,
|
||||
typeof(IDispatcherValueConstraint)));
|
||||
}
|
||||
|
||||
var constraintsRegEx = "^(" + regexPattern + ")$";
|
||||
constraint = new RegexDispatcherValueConstraint(constraintsRegEx);
|
||||
}
|
||||
|
||||
Add(key, constraint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a constraint for the given key, resolved by the <see cref="IConstraintFactory"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="constraintText">The text to be resolved by <see cref="IConstraintFactory"/>.</param>
|
||||
/// <remarks>
|
||||
/// The <see cref="IConstraintFactory"/> can create <see cref="IDispatcherValueConstraint"/> instances
|
||||
/// based on <paramref name="constraintText"/>. See <see cref="DispatcherOptions.ConstraintMap"/> to register
|
||||
/// custom constraint types.
|
||||
/// </remarks>
|
||||
public void AddResolvedConstraint(string key, string constraintText)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (constraintText == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraintText));
|
||||
}
|
||||
|
||||
var constraint = _constraintFactory.ResolveConstraint(constraintText);
|
||||
if (constraint == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatDispatcherValueConstraintBuilder_CouldNotResolveConstraint(
|
||||
key,
|
||||
constraintText,
|
||||
_rawText,
|
||||
_constraintFactory.GetType().Name));
|
||||
}
|
||||
|
||||
Add(key, constraint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the given key as optional.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
public void SetOptional(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
_optionalParameters.Add(key);
|
||||
}
|
||||
|
||||
private void Add(string key, IDispatcherValueConstraint constraint)
|
||||
{
|
||||
if (!_constraints.TryGetValue(key, out var list))
|
||||
{
|
||||
list = new List<IDispatcherValueConstraint>();
|
||||
_constraints.Add(key, list);
|
||||
}
|
||||
|
||||
list.Add(constraint);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,109 +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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// A context object for <see cref="IDispatcherValueConstraint"/>.
|
||||
/// </summary>
|
||||
public class DispatcherValueConstraintContext
|
||||
{
|
||||
private HttpContext _httpContext;
|
||||
private DispatcherValueCollection _values;
|
||||
private ConstraintPurpose _purpose;
|
||||
private string _key;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DispatcherValueConstraintContext"/>.
|
||||
/// </summary>
|
||||
public DispatcherValueConstraintContext()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DispatcherValueConstraintContext"/> for the current request.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="values">The <see cref="DispatcherValueCollection"/> for the current operation.</param>
|
||||
/// <param name="purpose">The purpose for invoking the constraint.</param>
|
||||
public DispatcherValueConstraintContext(HttpContext httpContext, DispatcherValueCollection values, ConstraintPurpose purpose)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
HttpContext = httpContext;
|
||||
Values = values;
|
||||
Purpose = purpose;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Http.HttpContext"/> associated with the current request.
|
||||
/// </summary>
|
||||
public HttpContext HttpContext
|
||||
{
|
||||
get => _httpContext;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_httpContext = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key associated with the current <see cref="IDispatcherValueConstraint"/>.
|
||||
/// </summary>
|
||||
public string Key
|
||||
{
|
||||
get => _key;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_key = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the purpose of executing the <see cref="IDispatcherValueConstraint"/>.
|
||||
/// </summary>
|
||||
public ConstraintPurpose Purpose
|
||||
{
|
||||
get => _purpose;
|
||||
set => _purpose = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="DispatcherValueCollection"/> associated with the current operation.
|
||||
/// </summary>
|
||||
public DispatcherValueCollection Values
|
||||
{
|
||||
get => _values;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_values = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an abstraction for resolving constraints as instances of <see cref="IDispatcherValueConstraint"/>.
|
||||
/// </summary>
|
||||
public interface IConstraintFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the constraint.
|
||||
/// </summary>
|
||||
/// <param name="constraint">The constraint to resolve.</param>
|
||||
/// <returns>The <see cref="IDispatcherValueConstraint"/> the constraint was resolved to.</returns>
|
||||
IDispatcherValueConstraint ResolveConstraint(string constraint);
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract that a class must implement in order to check whether a URL parameter
|
||||
/// value is valid for a constraint.
|
||||
/// </summary>
|
||||
public interface IDispatcherValueConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the current operation should succeed or fail, typically by validating one or
|
||||
/// more values in <see cref="DispatcherValueConstraintContext.Values"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="DispatcherValueConstraintContext"/> associated with the current operation.</param>
|
||||
/// <returns><c>true</c> if the current operation should proceed; otherwise <c>false</c>.</returns>
|
||||
bool Match(DispatcherValueConstraintContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +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.Globalization;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrains a dispatcher value parameter to represent only 32-bit integer values.
|
||||
/// </summary>
|
||||
public class IntDispatcherValueConstraint : IDispatcherValueConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(DispatcherValueConstraintContext constraintContext)
|
||||
{
|
||||
if (constraintContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraintContext));
|
||||
}
|
||||
|
||||
if (constraintContext.Values.TryGetValue(constraintContext.Key, out var value) && value != null)
|
||||
{
|
||||
if (value is int)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
return int.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a constraint on an optional parameter. If the parameter is present, then it is constrained by InnerConstraint.
|
||||
/// </summary>
|
||||
public class OptionalDispatcherValueConstraint : IDispatcherValueConstraint
|
||||
{
|
||||
public OptionalDispatcherValueConstraint(IDispatcherValueConstraint innerConstraint)
|
||||
{
|
||||
if (innerConstraint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(innerConstraint));
|
||||
}
|
||||
|
||||
InnerConstraint = innerConstraint;
|
||||
}
|
||||
|
||||
public IDispatcherValueConstraint InnerConstraint { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(DispatcherValueConstraintContext constraintContext)
|
||||
{
|
||||
if (constraintContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraintContext));
|
||||
}
|
||||
|
||||
if (constraintContext.Values.TryGetValue(constraintContext.Key, out var routeValue)
|
||||
&& routeValue != null)
|
||||
{
|
||||
return InnerConstraint.Match(constraintContext);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +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.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RegexDispatcherValueConstraint : IDispatcherValueConstraint
|
||||
{
|
||||
private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
public RegexDispatcherValueConstraint(Regex regex)
|
||||
{
|
||||
if (regex == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(regex));
|
||||
}
|
||||
|
||||
Constraint = regex;
|
||||
}
|
||||
|
||||
public RegexDispatcherValueConstraint(string regexPattern)
|
||||
{
|
||||
if (regexPattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(regexPattern));
|
||||
}
|
||||
|
||||
Constraint = new Regex(
|
||||
regexPattern,
|
||||
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
|
||||
RegexMatchTimeout);
|
||||
}
|
||||
|
||||
public Regex Constraint { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(DispatcherValueConstraintContext constraintContext)
|
||||
{
|
||||
if (constraintContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraintContext));
|
||||
}
|
||||
|
||||
if (constraintContext.Values.TryGetValue(constraintContext.Key, out var routeValue)
|
||||
&& routeValue != null)
|
||||
{
|
||||
var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
|
||||
|
||||
return Constraint.IsMatch(parameterValueString);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a regex constraint.
|
||||
/// </summary>
|
||||
public class RegexStringDispatcherValueConstraint : RegexDispatcherValueConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegexStringDispatcherValueConstraint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="regexPattern">The regular expression pattern to match.</param>
|
||||
public RegexStringDispatcherValueConstraint(string regexPattern)
|
||||
: base(regexPattern)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DefaultAddressTable : AddressTable
|
||||
{
|
||||
private readonly DispatcherOptions _options;
|
||||
private readonly List<Address>[] _groups;
|
||||
|
||||
public DefaultAddressTable(IOptions<DispatcherOptions> options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
|
||||
_groups = new List<Address>[options.Value.Matchers.Count];
|
||||
for (var i = 0; i < options.Value.Matchers.Count; i++)
|
||||
{
|
||||
_groups[i] = new List<Address>(options.Value.Matchers[i].AddressProvider?.Addresses ?? Array.Empty<Address>());
|
||||
}
|
||||
}
|
||||
|
||||
public override IReadOnlyList<IReadOnlyList<Address>> AddressGroups => _groups;
|
||||
}
|
||||
}
|
|
@ -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 System.Collections.Generic;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DefaultDispatcherDataSource : DispatcherDataSource
|
||||
{
|
||||
private readonly List<Address> _addresses;
|
||||
private readonly List<Endpoint> _endpoints;
|
||||
|
||||
public DefaultDispatcherDataSource()
|
||||
{
|
||||
_addresses = new List<Address>();
|
||||
_endpoints = new List<Endpoint>();
|
||||
}
|
||||
|
||||
public override IChangeToken ChangeToken { get; } = NullChangeToken.Singleton;
|
||||
|
||||
public IList<Address> Addresses => _addresses;
|
||||
|
||||
public IList<Endpoint> Endpoints => _endpoints;
|
||||
|
||||
protected override IReadOnlyList<Address> GetAddresses() => _addresses;
|
||||
|
||||
protected override IReadOnlyList<Endpoint> GetEndpoints() => _endpoints;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DefaultTemplateFactory : TemplateFactory
|
||||
{
|
||||
private readonly ITemplateFactoryComponent[] _components;
|
||||
|
||||
public DefaultTemplateFactory(IEnumerable<ITemplateFactoryComponent> components)
|
||||
{
|
||||
if (components == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(components));
|
||||
}
|
||||
|
||||
_components = components.ToArray();
|
||||
}
|
||||
|
||||
public override Template GetTemplateFromKey<TKey>(TKey key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
for (var i = 0; i < _components.Length; i++)
|
||||
{
|
||||
var component = _components[i] as TemplateFactory<TKey>;
|
||||
if (component == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var template = component.GetTemplate(key);
|
||||
if (template != null)
|
||||
{
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
internal class DefaultDispatcherConfigureOptions : IConfigureOptions<DispatcherOptions>
|
||||
{
|
||||
private readonly IEnumerable<DispatcherDataSource> _dataSources;
|
||||
private readonly IDefaultMatcherFactory _dispatcherFactory;
|
||||
private readonly IEnumerable<EndpointSelector> _endpointSelectors;
|
||||
private readonly IEnumerable<IHandlerFactory> _handlerFactories;
|
||||
|
||||
public DefaultDispatcherConfigureOptions(
|
||||
IDefaultMatcherFactory dispatcherFactory,
|
||||
IEnumerable<DispatcherDataSource> dataSources,
|
||||
IEnumerable<EndpointSelector> endpointSelectors,
|
||||
IEnumerable<IHandlerFactory> handlerFactories)
|
||||
{
|
||||
_dispatcherFactory = dispatcherFactory;
|
||||
_dataSources = dataSources;
|
||||
_endpointSelectors = endpointSelectors;
|
||||
_handlerFactories = handlerFactories;
|
||||
}
|
||||
|
||||
public void Configure(DispatcherOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
var matcher = _dispatcherFactory.CreateMatcher(new CompositeDispatcherDataSource(_dataSources), _endpointSelectors);
|
||||
|
||||
options.Matchers.Add(new MatcherEntry()
|
||||
{
|
||||
Matcher = matcher,
|
||||
AddressProvider = matcher as IAddressCollectionProvider,
|
||||
EndpointProvider = matcher as IEndpointCollectionProvider,
|
||||
HandlerFactory = new CompositeHandlerFactory(_handlerFactories),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +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 Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class DispatcherServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddDispatcher(this IServiceCollection services)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
// Adds the EndpointMiddleware at the end of the pipeline if the DispatcherMiddleware is in use.
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IStartupFilter, DispatcherEndpointStartupFilter>());
|
||||
|
||||
// Adds a default dispatcher which will collect all data sources and endpoint selectors from DI.
|
||||
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<DispatcherOptions>, DefaultDispatcherConfigureOptions>());
|
||||
|
||||
//
|
||||
// Addresses + Templates
|
||||
//
|
||||
services.TryAddSingleton<AddressTable, DefaultAddressTable>();
|
||||
services.TryAddSingleton<TemplateFactory, DefaultTemplateFactory>();
|
||||
services.TryAddSingleton<ITemplateFactoryComponent, RoutePatternTemplateFactory>();
|
||||
services.TryAddSingleton<RoutePatternAddressSelector>();
|
||||
|
||||
//
|
||||
// Misc Infrastructure
|
||||
//
|
||||
services.TryAddSingleton<RoutePatternBinderFactory>();
|
||||
services.TryAddSingleton<IConstraintFactory, DefaultConstraintFactory>();
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHandlerFactory, RoutePatternEndpointHandlerFactory>());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public abstract class DispatcherDataSource : IAddressCollectionProvider, IEndpointCollectionProvider
|
||||
{
|
||||
public abstract IChangeToken ChangeToken { get; }
|
||||
|
||||
protected abstract IReadOnlyList<Address> GetAddresses();
|
||||
|
||||
protected abstract IReadOnlyList<Endpoint> GetEndpoints();
|
||||
|
||||
IReadOnlyList<Address> IAddressCollectionProvider.Addresses => GetAddresses();
|
||||
|
||||
IReadOnlyList<Endpoint> IEndpointCollectionProvider.Endpoints => GetEndpoints();
|
||||
}
|
||||
}
|
|
@ -1,24 +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 Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherEndpointStartupFilter : IStartupFilter
|
||||
{
|
||||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
|
||||
{
|
||||
return builder =>
|
||||
{
|
||||
next(builder);
|
||||
if (builder.Properties.ContainsKey("Dispatcher"))
|
||||
{
|
||||
builder.UseMiddleware<EndpointMiddleware>();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherFeature : IDispatcherFeature
|
||||
{
|
||||
public Endpoint Endpoint { get; set; }
|
||||
|
||||
public Func<RequestDelegate, RequestDelegate> Handler { get; set; }
|
||||
|
||||
public DispatcherValueCollection Values { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,83 +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.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherMiddleware
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly DispatcherOptions _options;
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public DispatcherMiddleware(IOptions<DispatcherOptions> options, ILogger<DispatcherMiddleware> logger, RequestDelegate next)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
var feature = new DispatcherFeature();
|
||||
httpContext.Features.Set<IDispatcherFeature>(feature);
|
||||
|
||||
var context = new MatcherContext(httpContext);
|
||||
foreach (var entry in _options.Matchers)
|
||||
{
|
||||
await entry.Matcher.MatchAsync(context);
|
||||
|
||||
if (context.ShortCircuit != null)
|
||||
{
|
||||
feature.Endpoint = context.Endpoint;
|
||||
feature.Values = context.Values;
|
||||
|
||||
await context.ShortCircuit(httpContext);
|
||||
|
||||
_logger.RequestShortCircuitedDispatcherMiddleware(context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.Endpoint != null)
|
||||
{
|
||||
_logger.EndpointMatchedDispatcherMiddleware(context.Endpoint);
|
||||
feature.Endpoint = context.Endpoint;
|
||||
feature.Values = context.Values;
|
||||
|
||||
feature.Handler = entry.HandlerFactory.CreateHandler(feature.Endpoint);
|
||||
if (feature.Handler == null)
|
||||
{
|
||||
_logger.HandlerNotCreated(entry);
|
||||
throw new InvalidOperationException("Couldn't create a handler, that's bad.");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
_logger.NoEndpointsMatchedMatcher(entry.Matcher);
|
||||
}
|
||||
|
||||
await _next(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherOptions
|
||||
{
|
||||
public MatcherCollection Matchers { get; } = new MatcherCollection();
|
||||
|
||||
private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
|
||||
|
||||
public IDictionary<string, Type> ConstraintMap
|
||||
{
|
||||
get
|
||||
{
|
||||
return _constraintTypeMap;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ConstraintMap));
|
||||
}
|
||||
|
||||
_constraintTypeMap = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static IDictionary<string, Type> GetDefaultConstraintMap()
|
||||
{
|
||||
return new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// Type-specific constraints
|
||||
{ "int", typeof(IntDispatcherValueConstraint) },
|
||||
|
||||
//// Regex-based constraints
|
||||
{ "alpha", typeof(AlphaDispatcherValueConstraint) },
|
||||
{ "regex", typeof(RegexStringDispatcherValueConstraint) },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class EndpointMiddleware
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly DispatcherOptions _options;
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public EndpointMiddleware(IOptions<DispatcherOptions> options, ILogger<DispatcherMiddleware> logger, RequestDelegate next)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
if (feature.Handler != null)
|
||||
{
|
||||
_logger.ExecutingEndpoint(feature.Endpoint);
|
||||
|
||||
try
|
||||
{
|
||||
await feature.Handler(_next)(httpContext);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.ExecutedEndpoint(feature.Endpoint);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await _next(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class EndpointOrderMetadata : IEndpointOrderMetadata
|
||||
{
|
||||
public EndpointOrderMetadata(int order)
|
||||
{
|
||||
Order = order;
|
||||
}
|
||||
|
||||
public int Order { get; }
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public abstract class EndpointSelector
|
||||
{
|
||||
public abstract Task SelectAsync(EndpointSelectorContext context);
|
||||
|
||||
public virtual void Initialize(IEndpointCollectionProvider endpointProvider)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public sealed class EndpointSelectorContext
|
||||
{
|
||||
private int _index;
|
||||
|
||||
public EndpointSelectorContext(HttpContext httpContext, DispatcherValueCollection values, IList<Endpoint> endpoints, IList<EndpointSelector> selectors)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (selectors == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selectors));
|
||||
}
|
||||
|
||||
HttpContext = httpContext;
|
||||
Values = values;
|
||||
Endpoints = endpoints;
|
||||
Selectors = selectors;
|
||||
}
|
||||
|
||||
public IList<Endpoint> Endpoints { get; }
|
||||
|
||||
public HttpContext HttpContext { get; }
|
||||
|
||||
public IList<EndpointSelector> Selectors { get; }
|
||||
|
||||
public RequestDelegate ShortCircuit { get; set; }
|
||||
|
||||
public DispatcherValueCollection Values { get; }
|
||||
|
||||
public Task InvokeNextAsync()
|
||||
{
|
||||
if (_index >= Selectors.Count)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var selector = Selectors[_index++];
|
||||
return selector.SelectAsync(this);
|
||||
}
|
||||
|
||||
public Snapshot CreateSnapshot()
|
||||
{
|
||||
return new Snapshot(_index, Endpoints);
|
||||
}
|
||||
|
||||
public void RestoreSnapshot(Snapshot snapshot)
|
||||
{
|
||||
snapshot.Apply(this);
|
||||
}
|
||||
|
||||
public struct Snapshot
|
||||
{
|
||||
private readonly int _index;
|
||||
private readonly Endpoint[] _endpoints;
|
||||
|
||||
internal Snapshot(int index, IList<Endpoint> endpoints)
|
||||
{
|
||||
_index = index;
|
||||
_endpoints = endpoints.ToArray();
|
||||
}
|
||||
|
||||
internal void Apply(EndpointSelectorContext context)
|
||||
{
|
||||
context._index = _index;
|
||||
|
||||
context.Endpoints.Clear();
|
||||
for (var i = 0; i < _endpoints.Length; i++)
|
||||
{
|
||||
context.Endpoints.Add(_endpoints[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple implementation of <see cref="IHandlerFactory"/> that adapts a particular endpoint
|
||||
/// type to a handler.
|
||||
/// </summary>
|
||||
public sealed class HandlerFactory<TEndpoint> : IHandlerFactory
|
||||
{
|
||||
private readonly Func<TEndpoint, Func<RequestDelegate, RequestDelegate>> _adapter;
|
||||
|
||||
public HandlerFactory(Func<TEndpoint, Func<RequestDelegate, RequestDelegate>> adapter)
|
||||
{
|
||||
if (adapter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(adapter));
|
||||
}
|
||||
|
||||
_adapter = adapter;
|
||||
}
|
||||
|
||||
public Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint)
|
||||
{
|
||||
if (endpoint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoint));
|
||||
}
|
||||
|
||||
if (endpoint is TEndpoint myTypeOfEndpoint)
|
||||
{
|
||||
return _adapter(myTypeOfEndpoint);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class HttpMethodEndpointSelector : EndpointSelector
|
||||
{
|
||||
private object _lock;
|
||||
private bool _servicesInitialized;
|
||||
|
||||
public HttpMethodEndpointSelector()
|
||||
{
|
||||
_lock = new object();
|
||||
}
|
||||
|
||||
protected ILogger Logger { get; private set; }
|
||||
|
||||
public override async Task SelectAsync(EndpointSelectorContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
EnsureServicesInitialized(context);
|
||||
|
||||
var snapshot = context.CreateSnapshot();
|
||||
|
||||
var fallbackEndpoints = new List<Endpoint>();
|
||||
for (var i = context.Endpoints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var endpoint = context.Endpoints[i] as IRoutePatternEndpoint;
|
||||
if (endpoint == null || endpoint.HttpMethod == null)
|
||||
{
|
||||
// No metadata.
|
||||
Logger.NoHttpMethodFound(context.Endpoints[i]);
|
||||
|
||||
fallbackEndpoints.Add(context.Endpoints[i]);
|
||||
context.Endpoints.RemoveAt(i);
|
||||
}
|
||||
else if (string.Equals(endpoint.HttpMethod, context.HttpContext.Request.Method, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// The request method matches the endpoint's HTTP method.
|
||||
Logger.RequestMethodMatchedEndpointMethod(endpoint.HttpMethod, context.Endpoints[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a match.
|
||||
Logger.RequestMethodDidNotMatchEndpointMethod(context.HttpContext.Request.Method, endpoint.HttpMethod, context.Endpoints[i]);
|
||||
context.Endpoints.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Now the list of endpoints only contains those that have an HTTP method preference AND match the current
|
||||
// request.
|
||||
await context.InvokeNextAsync();
|
||||
|
||||
if (context.Endpoints.Count == 0)
|
||||
{
|
||||
Logger.NoEndpointMatchedRequestMethod(context.HttpContext.Request.Method);
|
||||
|
||||
// Nothing matched, do the fallback.
|
||||
context.RestoreSnapshot(snapshot);
|
||||
|
||||
context.Endpoints.Clear();
|
||||
|
||||
for (var i = 0; i < fallbackEndpoints.Count; i++)
|
||||
{
|
||||
context.Endpoints.Add(fallbackEndpoints[i]);
|
||||
}
|
||||
|
||||
await context.InvokeNextAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected void EnsureServicesInitialized(EndpointSelectorContext context)
|
||||
{
|
||||
if (Volatile.Read(ref _servicesInitialized))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureServicesInitializedSlow(context);
|
||||
}
|
||||
|
||||
private void EnsureServicesInitializedSlow(EndpointSelectorContext context)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!Volatile.Read(ref _servicesInitialized))
|
||||
{
|
||||
var services = context.HttpContext.RequestServices;
|
||||
Logger = services.GetRequiredService<ILoggerFactory>().CreateLogger(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IAddressCollectionProvider
|
||||
{
|
||||
IReadOnlyList<Address> Addresses { get; }
|
||||
|
||||
IChangeToken ChangeToken { get; }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IDefaultMatcherFactory
|
||||
{
|
||||
IMatcher CreateMatcher(DispatcherDataSource dataSource, IEnumerable<EndpointSelector> endpointSelectors);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IEndpointCollectionProvider
|
||||
{
|
||||
IReadOnlyList<Endpoint> Endpoints { get; }
|
||||
|
||||
IChangeToken ChangeToken { get; }
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IEndpointOrderMetadata
|
||||
{
|
||||
int Order { get; }
|
||||
}
|
||||
}
|
|
@ -1,27 +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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// An interface for components that can create a middleware-like delegate from an <see cref="Endpoint"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Implementations registered in the application services using the service type <see cref="IHandlerFactory"/>
|
||||
/// will be automatically added to set of handler factories used by the default dispatcher.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IHandlerFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a middleware-like delegate for the provided <see cref="Endpoint"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The <see cref="Endpoint"/> that will execute for the current request.</param>
|
||||
/// <returns>An <see cref="Func{RequestDelegate, RequestDelegate}"/> or <c>null</c>.</returns>
|
||||
Func<RequestDelegate, RequestDelegate> CreateHandler(Endpoint endpoint);
|
||||
}
|
||||
}
|
|
@ -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 System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for components that can select an <see cref="Endpoint"/> given the current request, as part
|
||||
/// of the execution of <see cref="DispatcherMiddleware"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="IMatcher"/> implementations can also optionally implement the <see cref="IEndpointCollectionProvider"/>
|
||||
/// and <see cref="IAddressCollectionProvider"/> interfaces to provide addition information.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Use <see cref="DispatcherOptions"/> to register instances of <see cref="IMatcher"/> that will be used by the
|
||||
/// <see cref="DispatcherMiddleware"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to asynchronously select an <see cref="Endpoint"/> for the current request.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="MatcherContext"/> associated with the current request.</param>
|
||||
/// <returns>A <see cref="Task"/> which represents the asynchronous completion of the operation.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// An implementation should use data from the current request (<see cref="MatcherContext.HttpContext"/>) to select the
|
||||
/// <see cref="Endpoint"/> and set <see cref="MatcherContext.Endpoint"/> and optionally <see cref="MatcherContext.Values"/>
|
||||
/// to indicate a successful result.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the matcher encounters an immediate failure condition, the implementation should set
|
||||
/// <see cref="MatcherContext.ShortCircuit"/> to a <see cref="RequestDelegate"/> that will respond to the current request.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
Task MatchAsync(MatcherContext context);
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IRoutePatternAddress
|
||||
{
|
||||
string Pattern { get; }
|
||||
|
||||
DispatcherValueCollection Defaults { get; }
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface IRoutePatternEndpoint
|
||||
{
|
||||
string HttpMethod { get; }
|
||||
|
||||
string Pattern { get; }
|
||||
|
||||
DispatcherValueCollection Values { get; }
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public interface ITemplateFactoryComponent
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,197 +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 Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
internal static class LoggerExtensions
|
||||
{
|
||||
// MatcherBase
|
||||
private static readonly Action<ILogger, string, Exception> _ambiguousEndpoints = LoggerMessage.Define<string>(
|
||||
LogLevel.Error,
|
||||
new EventId(0, "AmbiguousEndpoints"),
|
||||
"Request matched multiple endpoints resulting in ambiguity. Matching endpoints: {AmbiguousEndpoints}");
|
||||
|
||||
private static readonly Action<ILogger, PathString, Exception> _noEndpointsMatched = LoggerMessage.Define<PathString>(
|
||||
LogLevel.Debug,
|
||||
new EventId(1, "NoEndpointsMatched"),
|
||||
"No endpoints matched the current request path '{PathString}'.");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _requestShortCircuitedMatcherBase = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(2, "RequestShortCircuited_MatcherBase"),
|
||||
"The current request '{RequestPath}' was short circuited.");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _endpointMatchedMatcherBase = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(3, "EndpointMatched_MatcherBase"),
|
||||
"Request matched endpoint '{endpointName}'.");
|
||||
|
||||
// DispatcherMiddleware
|
||||
private static readonly Action<ILogger, Type, Exception> _handlerNotCreated = LoggerMessage.Define<Type>(
|
||||
LogLevel.Error,
|
||||
new EventId(0, "HandlerNotCreated"),
|
||||
"A handler could not be created for '{MatcherType}'.");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _requestShortCircuitedDispatcherMiddleware = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(1, "RequestShortCircuited_DispatcherMiddleware"),
|
||||
"The current request '{RequestPath}' was short circuited.");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _endpointMatchedDispatcherMiddleware = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(2, "EndpointMatched_DispatcherMiddleware"),
|
||||
"Request matched endpoint '{endpointName}'.");
|
||||
|
||||
private static readonly Action<ILogger, IMatcher, Exception> _noEndpointsMatchedMatcher = LoggerMessage.Define<IMatcher>(
|
||||
LogLevel.Debug,
|
||||
new EventId(3, "NoEndpointsMatchedMatcher"),
|
||||
"No endpoints matched matcher '{Matcher}'.");
|
||||
|
||||
//EndpointMiddleware
|
||||
private static readonly Action<ILogger, string, Exception> _executingEndpoint = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(0, "ExecutingEndpoint"),
|
||||
"Executing endpoint '{EndpointName}'.");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _executedEndpoint = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(1, "ExecutedEndpoint"),
|
||||
"Executed endpoint '{EndpointName}'.");
|
||||
|
||||
// HttpMethodEndpointSelector
|
||||
private static readonly Action<ILogger, string, Exception> _noHttpMethodFound = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(0, "NoHttpMethodFound"),
|
||||
"No HTTP method specified for endpoint '{EndpointName}'.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception> _requestMethodMatchedEndpointMethod = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Information,
|
||||
new EventId(1, "RequestMethodMatchedEndpointMethod"),
|
||||
"Request method matched HTTP method '{Method}' for endpoint '{EndpointName}'.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, string, Exception> _requestMethodDidNotMatchEndpointMethod = LoggerMessage.Define<string, string, string>(
|
||||
LogLevel.Information,
|
||||
new EventId(2, "RequestMethodDidNotMatchEndpointMethod"),
|
||||
"Request method '{RequestMethod}' did not match HTTP method '{EndpointMethod}' for endpoint '{EndpointName}'.");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _noEndpointMatchedRequestMethod = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(3, "NoEndpointMatchedRequestMethod"),
|
||||
"No endpoint matched request method '{Method}'.");
|
||||
|
||||
// TreeMatcher
|
||||
private static readonly Action<ILogger, string, Exception> _requestShortCircuited = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(3, "RequestShortCircuited"),
|
||||
"The current request '{RequestPath}' was short circuited.");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _matchedRoute = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
"Request successfully matched the route pattern '{RoutePattern}'.");
|
||||
|
||||
private static readonly Action<ILogger, object, string, IDispatcherValueConstraint, Exception> _routeValueDoesNotMatchConstraint = LoggerMessage.Define<object, string, IDispatcherValueConstraint>(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
"Route value '{RouteValue}' with key '{RouteKey}' did not match the constraint '{RouteConstraint}'.");
|
||||
|
||||
public static void RouteValueDoesNotMatchConstraint(
|
||||
this ILogger logger,
|
||||
object routeValue,
|
||||
string routeKey,
|
||||
IDispatcherValueConstraint routeConstraint)
|
||||
{
|
||||
_routeValueDoesNotMatchConstraint(logger, routeValue, routeKey, routeConstraint, null);
|
||||
}
|
||||
|
||||
public static void RequestShortCircuited(this ILogger logger, MatcherContext matcherContext)
|
||||
{
|
||||
var requestPath = matcherContext.HttpContext.Request.Path;
|
||||
_requestShortCircuited(logger, requestPath, null);
|
||||
}
|
||||
|
||||
public static void MatchedRoute(
|
||||
this ILogger logger,
|
||||
string routePattern)
|
||||
{
|
||||
_matchedRoute(logger, routePattern, null);
|
||||
}
|
||||
|
||||
public static void AmbiguousEndpoints(this ILogger logger, string ambiguousEndpoints)
|
||||
{
|
||||
_ambiguousEndpoints(logger, ambiguousEndpoints, null);
|
||||
}
|
||||
|
||||
public static void EndpointMatchedMatcherBase(this ILogger logger, Endpoint endpoint)
|
||||
{
|
||||
_endpointMatchedMatcherBase(logger, endpoint.DisplayName ?? "Unnamed endpoint", null);
|
||||
}
|
||||
|
||||
public static void NoEndpointsMatched(this ILogger logger, PathString pathString)
|
||||
{
|
||||
_noEndpointsMatched(logger, pathString, null);
|
||||
}
|
||||
|
||||
public static void RequestShortCircuitedMatcherBase(this ILogger logger, MatcherContext matcherContext)
|
||||
{
|
||||
var requestPath = matcherContext.HttpContext.Request.Path;
|
||||
_requestShortCircuitedMatcherBase(logger, requestPath, null);
|
||||
}
|
||||
|
||||
public static void EndpointMatchedDispatcherMiddleware(this ILogger logger, Endpoint endpoint)
|
||||
{
|
||||
_endpointMatchedDispatcherMiddleware(logger, endpoint.DisplayName ?? "Unnamed endpoint", null);
|
||||
}
|
||||
|
||||
public static void RequestShortCircuitedDispatcherMiddleware(this ILogger logger, MatcherContext matcherContext)
|
||||
{
|
||||
var requestPath = matcherContext.HttpContext.Request.Path;
|
||||
_requestShortCircuitedDispatcherMiddleware(logger, requestPath, null);
|
||||
}
|
||||
|
||||
public static void HandlerNotCreated(this ILogger logger, MatcherEntry matcher)
|
||||
{
|
||||
var matcherType = matcher.GetType();
|
||||
_handlerNotCreated(logger, matcherType, null);
|
||||
}
|
||||
|
||||
public static void NoEndpointsMatchedMatcher(this ILogger logger, IMatcher matcher)
|
||||
{
|
||||
_noEndpointsMatchedMatcher(logger, matcher, null);
|
||||
}
|
||||
|
||||
public static void ExecutingEndpoint(this ILogger logger, Endpoint endpoint)
|
||||
{
|
||||
_executingEndpoint(logger, endpoint.DisplayName ?? "Unnamed endpoint", null);
|
||||
}
|
||||
|
||||
public static void ExecutedEndpoint(this ILogger logger, Endpoint endpoint)
|
||||
{
|
||||
_executedEndpoint(logger, endpoint.DisplayName ?? "Unnamed endpoint", null);
|
||||
}
|
||||
|
||||
public static void NoHttpMethodFound(this ILogger logger, Endpoint endpoint)
|
||||
{
|
||||
_noHttpMethodFound(logger, endpoint.DisplayName ?? "Unnamed endpoint", null);
|
||||
}
|
||||
|
||||
public static void RequestMethodMatchedEndpointMethod(this ILogger logger, string httpMethod, Endpoint endpoint)
|
||||
{
|
||||
_requestMethodMatchedEndpointMethod(logger, httpMethod, endpoint.DisplayName ?? "Unnamed endpoint", null);
|
||||
}
|
||||
|
||||
public static void RequestMethodDidNotMatchEndpointMethod(this ILogger logger, string requestMethod, string endpointMethod, Endpoint endpoint)
|
||||
{
|
||||
_requestMethodDidNotMatchEndpointMethod(logger, requestMethod, endpointMethod, endpoint.DisplayName ?? "Unnamed endpoint", null);
|
||||
}
|
||||
|
||||
public static void NoEndpointMatchedRequestMethod(this ILogger logger, string requestMethod)
|
||||
{
|
||||
_noEndpointMatchedRequestMethod(logger, requestMethod, null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public abstract class MatcherBase : IMatcher, IAddressCollectionProvider, IEndpointCollectionProvider
|
||||
{
|
||||
private List<Address> _addresses;
|
||||
private List<Endpoint> _endpoints;
|
||||
private List<EndpointSelector> _endpointSelectors;
|
||||
|
||||
private object _lock;
|
||||
private bool _servicesInitialized;
|
||||
private bool _selectorsInitialized;
|
||||
private readonly Func<object> _selectorInitializer;
|
||||
|
||||
public MatcherBase()
|
||||
{
|
||||
_lock = new object();
|
||||
_selectorInitializer = InitializeSelectors;
|
||||
}
|
||||
|
||||
protected ILogger Logger { get; private set; }
|
||||
|
||||
public virtual IList<Address> Addresses
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_addresses == null)
|
||||
{
|
||||
_addresses = new List<Address>();
|
||||
}
|
||||
|
||||
return _addresses;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual DispatcherDataSource DataSource { get; set; }
|
||||
|
||||
public virtual IList<Endpoint> Endpoints
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_endpoints == null)
|
||||
{
|
||||
_endpoints = new List<Endpoint>();
|
||||
}
|
||||
|
||||
return _endpoints;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IList<EndpointSelector> Selectors
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_endpointSelectors == null)
|
||||
{
|
||||
_endpointSelectors = new List<EndpointSelector>();
|
||||
}
|
||||
|
||||
return _endpointSelectors;
|
||||
}
|
||||
}
|
||||
|
||||
public IChangeToken ChangeToken => DataSource?.ChangeToken ?? NullChangeToken.Singleton;
|
||||
|
||||
IReadOnlyList<Address> IAddressCollectionProvider.Addresses => GetAddresses();
|
||||
|
||||
IReadOnlyList<Endpoint> IEndpointCollectionProvider.Endpoints => GetEndpoints();
|
||||
|
||||
protected virtual IReadOnlyList<Address> GetAddresses()
|
||||
{
|
||||
return ((IAddressCollectionProvider)DataSource)?.Addresses ?? _addresses ?? (IReadOnlyList<Address>)Array.Empty<Address>();
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<Endpoint> GetEndpoints()
|
||||
{
|
||||
return ((IEndpointCollectionProvider)DataSource)?.Endpoints ?? _endpoints ?? (IReadOnlyList<Endpoint>)Array.Empty<Endpoint>();
|
||||
}
|
||||
|
||||
public virtual async Task MatchAsync(MatcherContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
EnsureServicesInitialized(context);
|
||||
|
||||
context.Values = await MatchRequestAsync(context.HttpContext);
|
||||
if (context.Values != null)
|
||||
{
|
||||
await SelectEndpointAsync(context, GetEndpoints());
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Task<DispatcherValueCollection> MatchRequestAsync(HttpContext httpContext)
|
||||
{
|
||||
// By default don't apply any criteria or provide any values.
|
||||
return Task.FromResult(new DispatcherValueCollection());
|
||||
}
|
||||
|
||||
protected virtual void InitializeServices(IServiceProvider services)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual async Task SelectEndpointAsync(MatcherContext context, IEnumerable<Endpoint> endpoints)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
EnsureSelectorsInitialized();
|
||||
|
||||
var selectorContext = new EndpointSelectorContext(context.HttpContext, context.Values, endpoints.ToList(), Selectors.ToList());
|
||||
await selectorContext.InvokeNextAsync();
|
||||
|
||||
if (selectorContext.ShortCircuit != null)
|
||||
{
|
||||
context.ShortCircuit = selectorContext.ShortCircuit;
|
||||
Logger.RequestShortCircuitedMatcherBase(context);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (selectorContext.Endpoints.Count)
|
||||
{
|
||||
case 0:
|
||||
Logger.NoEndpointsMatched(context.HttpContext.Request.Path);
|
||||
return;
|
||||
|
||||
case 1:
|
||||
context.Endpoint = selectorContext.Endpoints[0];
|
||||
|
||||
Logger.EndpointMatchedMatcherBase(context.Endpoint);
|
||||
return;
|
||||
|
||||
default:
|
||||
var endpointNames = string.Join(
|
||||
Environment.NewLine,
|
||||
selectorContext.Endpoints.Select(a => a.DisplayName));
|
||||
|
||||
Logger.AmbiguousEndpoints(endpointNames);
|
||||
|
||||
var message = Resources.FormatAmbiguousEndpoints(
|
||||
Environment.NewLine,
|
||||
endpointNames);
|
||||
|
||||
throw new AmbiguousEndpointException(message);
|
||||
}
|
||||
}
|
||||
|
||||
protected void EnsureSelectorsInitialized()
|
||||
{
|
||||
object _ = null;
|
||||
LazyInitializer.EnsureInitialized(ref _, ref _selectorsInitialized, ref _lock, _selectorInitializer);
|
||||
}
|
||||
|
||||
private object InitializeSelectors()
|
||||
{
|
||||
foreach (var selector in Selectors)
|
||||
{
|
||||
selector.Initialize(this);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void EnsureServicesInitialized(MatcherContext context)
|
||||
{
|
||||
if (Volatile.Read(ref _servicesInitialized))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureServicesInitializedSlow(context);
|
||||
}
|
||||
|
||||
private void EnsureServicesInitializedSlow(MatcherContext context)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!Volatile.Read(ref _servicesInitialized))
|
||||
{
|
||||
var services = context.HttpContext.RequestServices;
|
||||
Logger = services.GetRequiredService<ILoggerFactory>().CreateLogger(GetType());
|
||||
InitializeServices(services);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class MatcherCollection : Collection<MatcherEntry>
|
||||
{
|
||||
public void Add(IMatcher matcher, IHandlerFactory handerFactory)
|
||||
{
|
||||
if (matcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(matcher));
|
||||
}
|
||||
|
||||
Add(new MatcherEntry()
|
||||
{
|
||||
Matcher = matcher,
|
||||
AddressProvider = matcher as IAddressCollectionProvider,
|
||||
EndpointProvider = matcher as IEndpointCollectionProvider,
|
||||
|
||||
HandlerFactory = handerFactory,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// A context object for <see cref="IMatcher.MatchAsync(MatcherContext)"/>.
|
||||
/// </summary>
|
||||
public class MatcherContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MatcherContext"/> for the current request.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
|
||||
public MatcherContext(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
HttpContext = httpContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Http.HttpContext"/> associated with the current request.
|
||||
/// </summary>
|
||||
public HttpContext HttpContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Endpoint"/> selected by the matcher.
|
||||
/// </summary>
|
||||
public Endpoint Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a short-circuit delegate provided by the matcher.
|
||||
/// </summary>
|
||||
public RequestDelegate ShortCircuit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a <see cref="DispatcherValueCollection"/> provided by the matcher.
|
||||
/// </summary>
|
||||
public DispatcherValueCollection Values { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class MatcherEntry
|
||||
{
|
||||
public IHandlerFactory HandlerFactory { get; set; }
|
||||
|
||||
public IMatcher Matcher { get; set; }
|
||||
|
||||
public IAddressCollectionProvider AddressProvider { get; set; }
|
||||
|
||||
public IEndpointCollectionProvider EndpointProvider { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core middleware for dispatching requests to endpoints and use addresses to generate URLs.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<DefineConstants>$(DefineConstants);DISPATCHER</DefineConstants>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;routing;dispatcher</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\shared\Microsoft.AspNetCore.Routing.UrlMatchingTree.Sources\**\*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.HashCodeCombiner.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="$(MicrosoftExtensionsObjectPoolPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,64 +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.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
/// <summary>
|
||||
/// The parsed representation of a constraint in a <see cref="RoutePattern"/> parameter.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public sealed class ConstraintReference
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ConstraintReference"/>.
|
||||
/// </summary>
|
||||
/// <param name="content">The constraint identifier.</param>
|
||||
/// <remarks>A new <see cref="ConstraintReference"/>.</remarks>
|
||||
public static ConstraintReference Create(string content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
return new ConstraintReference(null, content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ConstraintReference"/>.
|
||||
/// </summary>
|
||||
/// <param name="rawText">The raw text of the constraint identifier.</param>
|
||||
/// <param name="content">The constraint identifier.</param>
|
||||
/// <remarks>A new <see cref="ConstraintReference"/>.</remarks>
|
||||
public static ConstraintReference CreateFromText(string rawText, string content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
return new ConstraintReference(rawText, content);
|
||||
}
|
||||
|
||||
private ConstraintReference(string rawText, string content)
|
||||
{
|
||||
RawText = rawText;
|
||||
Content = content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the constraint text.
|
||||
/// </summary>
|
||||
public string Content { get; }
|
||||
|
||||
public string RawText { get; }
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return RawText ?? Content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,232 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
public static class InlineRouteParameterParser
|
||||
{
|
||||
public static RoutePatternParameter ParseRouteParameter(string text, string parameter)
|
||||
{
|
||||
if (parameter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parameter));
|
||||
}
|
||||
|
||||
if (parameter.Length == 0)
|
||||
{
|
||||
return new RoutePatternParameter(null, string.Empty, null, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||
}
|
||||
|
||||
var startIndex = 0;
|
||||
var endIndex = parameter.Length - 1;
|
||||
|
||||
var parameterKind = RoutePatternParameterKind.Standard;
|
||||
if (parameter[0] == '*')
|
||||
{
|
||||
parameterKind = RoutePatternParameterKind.CatchAll;
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (parameter[endIndex] == '?')
|
||||
{
|
||||
parameterKind = RoutePatternParameterKind.Optional;
|
||||
endIndex--;
|
||||
}
|
||||
|
||||
var currentIndex = startIndex;
|
||||
|
||||
// Parse parameter name
|
||||
var parameterName = string.Empty;
|
||||
|
||||
while (currentIndex <= endIndex)
|
||||
{
|
||||
var currentChar = parameter[currentIndex];
|
||||
|
||||
if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
|
||||
{
|
||||
// Parameter names are allowed to start with delimiters used to denote constraints or default values.
|
||||
// i.e. "=foo" or ":bar" would be treated as parameter names rather than default value or constraint
|
||||
// specifications.
|
||||
parameterName = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||
|
||||
// Roll the index back and move to the constraint parsing stage.
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
else if (currentIndex == endIndex)
|
||||
{
|
||||
parameterName = parameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
var parseResults = ParseConstraints(parameter, currentIndex, endIndex);
|
||||
currentIndex = parseResults.CurrentIndex;
|
||||
|
||||
string defaultValue = null;
|
||||
if (currentIndex <= endIndex &&
|
||||
parameter[currentIndex] == '=')
|
||||
{
|
||||
defaultValue = parameter.Substring(currentIndex + 1, endIndex - currentIndex);
|
||||
}
|
||||
|
||||
return new RoutePatternParameter(text, parameterName, defaultValue, parameterKind, parseResults.Constraints.ToArray());
|
||||
}
|
||||
|
||||
private static ConstraintParseResults ParseConstraints(
|
||||
string parameter,
|
||||
int currentIndex,
|
||||
int endIndex)
|
||||
{
|
||||
var constraints = new List<ConstraintReference>();
|
||||
var state = ParseState.Start;
|
||||
var startIndex = currentIndex;
|
||||
do
|
||||
{
|
||||
var currentChar = currentIndex > endIndex ? null : (char?)parameter[currentIndex];
|
||||
switch (state)
|
||||
{
|
||||
case ParseState.Start:
|
||||
switch (currentChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
break;
|
||||
case ':':
|
||||
state = ParseState.ParsingName;
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '(':
|
||||
state = ParseState.InsideParenthesis;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ParseState.InsideParenthesis:
|
||||
switch (currentChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
var constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||
break;
|
||||
case ')':
|
||||
// Only consume a ')' token if
|
||||
// (a) it is the last token
|
||||
// (b) the next character is the start of the new constraint ':'
|
||||
// (c) the next character is the start of the default value.
|
||||
|
||||
var nextChar = currentIndex + 1 > endIndex ? null : (char?)parameter[currentIndex + 1];
|
||||
switch (nextChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
constraintText = parameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||
break;
|
||||
case ':':
|
||||
state = ParseState.Start;
|
||||
constraintText = parameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
constraintText = parameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
case '=':
|
||||
// In the original implementation, the Regex would've backtracked if it encountered an
|
||||
// unbalanced opening bracket followed by (not necessarily immediatiely) a delimiter.
|
||||
// Simply verifying that the parantheses will eventually be closed should suffice to
|
||||
// determine if the terminator needs to be consumed as part of the current constraint
|
||||
// specification.
|
||||
var indexOfClosingParantheses = parameter.IndexOf(')', currentIndex + 1);
|
||||
if (indexOfClosingParantheses == -1)
|
||||
{
|
||||
constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||
|
||||
if (currentChar == ':')
|
||||
{
|
||||
state = ParseState.ParsingName;
|
||||
startIndex = currentIndex + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = ParseState.End;
|
||||
currentIndex--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentIndex = indexOfClosingParantheses;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ParseState.ParsingName:
|
||||
switch (currentChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
var constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||
break;
|
||||
case ':':
|
||||
constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '(':
|
||||
state = ParseState.InsideParenthesis;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
|
||||
} while (state != ParseState.End);
|
||||
|
||||
return new ConstraintParseResults
|
||||
{
|
||||
CurrentIndex = currentIndex,
|
||||
Constraints = constraints
|
||||
};
|
||||
}
|
||||
|
||||
private enum ParseState
|
||||
{
|
||||
Start,
|
||||
ParsingName,
|
||||
InsideParenthesis,
|
||||
End
|
||||
}
|
||||
|
||||
private struct ConstraintParseResults
|
||||
{
|
||||
public int CurrentIndex;
|
||||
|
||||
public IEnumerable<ConstraintReference> Constraints;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public sealed class RoutePattern
|
||||
{
|
||||
private const string SeparatorString = "/";
|
||||
|
||||
internal RoutePattern(
|
||||
string rawText,
|
||||
RoutePatternParameter[] parameters,
|
||||
RoutePatternPathSegment[] pathSegments)
|
||||
{
|
||||
Debug.Assert(parameters != null);
|
||||
Debug.Assert(pathSegments != null);
|
||||
|
||||
RawText = rawText;
|
||||
Parameters = parameters;
|
||||
PathSegments = pathSegments;
|
||||
}
|
||||
|
||||
public string RawText { get; }
|
||||
|
||||
public IReadOnlyList<RoutePatternParameter> Parameters { get; }
|
||||
|
||||
public IReadOnlyList<RoutePatternPathSegment> PathSegments { get; }
|
||||
|
||||
public static RoutePattern Parse(string pattern)
|
||||
{
|
||||
try
|
||||
{
|
||||
return RoutePatternParser.Parse(pattern);
|
||||
}
|
||||
catch (RoutePatternException ex)
|
||||
{
|
||||
throw new ArgumentException(ex.Message, nameof(pattern), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter matching the given name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the parameter to match.</param>
|
||||
/// <returns>The matching parameter or <c>null</c> if no parameter matches the given name.</returns>
|
||||
public RoutePatternParameter GetParameter(string name)
|
||||
{
|
||||
for (var i = 0; i < Parameters.Count; i++)
|
||||
{
|
||||
var parameter = Parameters[i];
|
||||
if (string.Equals(parameter.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
public sealed class RoutePatternBuilder
|
||||
{
|
||||
private RoutePatternBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
public IList<RoutePatternPathSegment> PathSegments { get; } = new List<RoutePatternPathSegment>();
|
||||
|
||||
public string RawText { get; set; }
|
||||
|
||||
public RoutePatternBuilder AddPathSegment(params RoutePatternPart[] parts)
|
||||
{
|
||||
if (parts == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parts));
|
||||
}
|
||||
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.RoutePatternBuilder_CollectionCannotBeEmpty, nameof(parts));
|
||||
}
|
||||
|
||||
return AddPathSegmentFromText(null, parts);
|
||||
}
|
||||
|
||||
public RoutePatternBuilder AddPathSegmentFromText(string text, params RoutePatternPart[] parts)
|
||||
{
|
||||
if (parts == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parts));
|
||||
}
|
||||
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.RoutePatternBuilder_CollectionCannotBeEmpty, nameof(parts));
|
||||
}
|
||||
|
||||
var segment = new RoutePatternPathSegment(text, parts.ToArray());
|
||||
PathSegments.Add(segment);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public RoutePattern Build()
|
||||
{
|
||||
var parameters = new List<RoutePatternParameter>();
|
||||
for (var i = 0; i < PathSegments.Count; i++)
|
||||
{
|
||||
var segment = PathSegments[i];
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
if (segment.Parts[j] is RoutePatternParameter parameter)
|
||||
{
|
||||
parameters.Add(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RoutePattern(RawText, parameters.ToArray(), PathSegments.ToArray());
|
||||
}
|
||||
|
||||
public static RoutePatternBuilder Create(string text)
|
||||
{
|
||||
return new RoutePatternBuilder() { RawText = text, };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
public class RoutePatternException : Exception
|
||||
{
|
||||
public RoutePatternException(string pattern, string message)
|
||||
: base(message)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public string Pattern { get; }
|
||||
}
|
||||
}
|
|
@ -1,32 +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.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public sealed class RoutePatternLiteral : RoutePatternPart
|
||||
{
|
||||
internal RoutePatternLiteral(string rawText, string content)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(content));
|
||||
|
||||
RawText = rawText;
|
||||
Content = content;
|
||||
|
||||
PartKind = RoutePatternPartKind.Literal;
|
||||
}
|
||||
|
||||
public string Content { get; }
|
||||
|
||||
public override RoutePatternPartKind PartKind { get; }
|
||||
|
||||
public override string RawText { get; }
|
||||
|
||||
internal override string DebuggerToString()
|
||||
{
|
||||
return RawText;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,511 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Dispatcher.Internal;
|
||||
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RoutePatternMatcher
|
||||
{
|
||||
private const string SeparatorString = "/";
|
||||
private const char SeparatorChar = '/';
|
||||
|
||||
// Perf: This is a cache to avoid looking things up in 'Defaults' each request.
|
||||
private readonly bool[] _hasDefaultValue;
|
||||
private readonly object[] _defaultValues;
|
||||
|
||||
private static readonly char[] Delimiters = new char[] { SeparatorChar };
|
||||
|
||||
public RoutePatternMatcher(
|
||||
RoutePattern routePattern,
|
||||
DispatcherValueCollection defaults)
|
||||
{
|
||||
if (routePattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routePattern));
|
||||
}
|
||||
|
||||
RoutePattern = routePattern;
|
||||
Defaults = defaults ?? new DispatcherValueCollection();
|
||||
|
||||
// Perf: cache the default value for each parameter (other than complex segments).
|
||||
_hasDefaultValue = new bool[RoutePattern.PathSegments.Count];
|
||||
_defaultValues = new object[RoutePattern.PathSegments.Count];
|
||||
|
||||
for (var i = 0; i < RoutePattern.PathSegments.Count; i++)
|
||||
{
|
||||
var segment = RoutePattern.PathSegments[i];
|
||||
if (!segment.IsSimple)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var part = segment.Parts[0];
|
||||
if (!part.IsParameter)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameter = (RoutePatternParameter)part;
|
||||
if (Defaults.TryGetValue(parameter.Name, out var value))
|
||||
{
|
||||
_hasDefaultValue[i] = true;
|
||||
_defaultValues[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DispatcherValueCollection Defaults { get; }
|
||||
|
||||
public RoutePattern RoutePattern { get; }
|
||||
|
||||
public bool TryMatch(PathString path, DispatcherValueCollection values)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
var pathTokenizer = new PathTokenizer(path);
|
||||
|
||||
// Perf: We do a traversal of the request-segments + route-segments twice.
|
||||
//
|
||||
// For most segment-types, we only really need to any work on one of the two passes.
|
||||
//
|
||||
// On the first pass, we're just looking to see if there's anything that would disqualify us from matching.
|
||||
// The most common case would be a literal segment that doesn't match.
|
||||
//
|
||||
// On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values'
|
||||
// and start capturing strings.
|
||||
foreach (var stringSegment in pathTokenizer)
|
||||
{
|
||||
if (stringSegment.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var pathSegment = i >= RoutePattern.PathSegments.Count ? null : RoutePattern.PathSegments[i];
|
||||
if (pathSegment == null && stringSegment.Length > 0)
|
||||
{
|
||||
// If pathSegment is null, then we're out of route segments. All we can match is the empty
|
||||
// string.
|
||||
return false;
|
||||
}
|
||||
else if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameter parameter && parameter.IsCatchAll)
|
||||
{
|
||||
// Nothing to validate for a catch-all - it can match any string, including the empty string.
|
||||
//
|
||||
// Also, a catch-all has to be the last part, so we're done.
|
||||
break;
|
||||
}
|
||||
if (!TryMatchLiterals(i++, stringSegment, pathSegment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < RoutePattern.PathSegments.Count; i++)
|
||||
{
|
||||
// We've matched the request path so far, but still have remaining route segments. These need
|
||||
// to be all single-part parameter segments with default values or else they won't match.
|
||||
var pathSegment = RoutePattern.PathSegments[i];
|
||||
Debug.Assert(pathSegment != null);
|
||||
|
||||
if (!pathSegment.IsSimple)
|
||||
{
|
||||
// If the segment is a complex segment, it MUST contain literals, and we've parsed the full
|
||||
// path so far, so it can't match.
|
||||
return false;
|
||||
}
|
||||
|
||||
var part = pathSegment.Parts[0];
|
||||
if (part.IsLiteral || part.IsSeparator)
|
||||
{
|
||||
// If the segment is a simple literal - which need the URL to provide a value, so we don't match.
|
||||
return false;
|
||||
}
|
||||
|
||||
var parameter = (RoutePatternParameter)part;
|
||||
if (parameter.IsCatchAll)
|
||||
{
|
||||
// Nothing to validate for a catch-all - it can match any string, including the empty string.
|
||||
//
|
||||
// Also, a catch-all has to be the last part, so we're done.
|
||||
break;
|
||||
}
|
||||
|
||||
// If we get here, this is a simple segment with a parameter. We need it to be optional, or for the
|
||||
// defaults to have a value.
|
||||
if (!_hasDefaultValue[i] && !parameter.IsOptional)
|
||||
{
|
||||
// There's no default for this (non-optional) parameter so it can't match.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we've very likely got a match, so start capturing values for real.
|
||||
i = 0;
|
||||
foreach (var requestSegment in pathTokenizer)
|
||||
{
|
||||
var pathSegment = RoutePattern.PathSegments[i++];
|
||||
if (SavePathSegmentsAsValues(i, values, requestSegment, pathSegment))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!pathSegment.IsSimple)
|
||||
{
|
||||
if (!MatchComplexSegment(pathSegment, requestSegment.ToString(), Defaults, values))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < RoutePattern.PathSegments.Count; i++)
|
||||
{
|
||||
// We've matched the request path so far, but still have remaining route segments. We already know these
|
||||
// are simple parameters that either have a default, or don't need to produce a value.
|
||||
var pathSegment = RoutePattern.PathSegments[i];
|
||||
Debug.Assert(pathSegment != null);
|
||||
Debug.Assert(pathSegment.IsSimple);
|
||||
|
||||
var part = pathSegment.Parts[0];
|
||||
Debug.Assert(part.IsParameter);
|
||||
|
||||
// It's ok for a catch-all to produce a null value
|
||||
if (part is RoutePatternParameter parameter && (parameter.IsCatchAll || _hasDefaultValue[i]))
|
||||
{
|
||||
// Don't replace an existing value with a null.
|
||||
var defaultValue = _defaultValues[i];
|
||||
if (defaultValue != null || !values.ContainsKey(parameter.Name))
|
||||
{
|
||||
values[parameter.Name] = defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all remaining default values to the route data
|
||||
foreach (var kvp in Defaults)
|
||||
{
|
||||
if (!values.ContainsKey(kvp.Key))
|
||||
{
|
||||
values.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryMatchLiterals(int index, StringSegment stringSegment, RoutePatternPathSegment pathSegment)
|
||||
{
|
||||
if (pathSegment.IsSimple && !pathSegment.Parts[0].IsParameter)
|
||||
{
|
||||
// This is a literal segment, so we need to match the text, or the route isn't a match.
|
||||
if (pathSegment.Parts[0].IsLiteral)
|
||||
{
|
||||
var part = (RoutePatternLiteral)pathSegment.Parts[0];
|
||||
|
||||
if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var part = (RoutePatternSeparator)pathSegment.Parts[0];
|
||||
|
||||
if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pathSegment.IsSimple && pathSegment.Parts[0].IsParameter)
|
||||
{
|
||||
// For a parameter, validate that it's a has some length, or we have a default, or it's optional.
|
||||
var part = (RoutePatternParameter)pathSegment.Parts[0];
|
||||
if (stringSegment.Length == 0 &&
|
||||
!_hasDefaultValue[index] &&
|
||||
!part.IsOptional)
|
||||
{
|
||||
// There's no value for this parameter, the route can't match.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(!pathSegment.IsSimple);
|
||||
// Don't attempt to validate a complex segment at this point other than being non-emtpy,
|
||||
// do it in the second pass.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SavePathSegmentsAsValues(int index, DispatcherValueCollection values, StringSegment requestSegment, RoutePatternPathSegment pathSegment)
|
||||
{
|
||||
if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameter parameter && parameter.IsCatchAll)
|
||||
{
|
||||
// A catch-all captures til the end of the string.
|
||||
var captured = requestSegment.Buffer.Substring(requestSegment.Offset);
|
||||
if (captured.Length > 0)
|
||||
{
|
||||
values[parameter.Name] = captured;
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
|
||||
values[parameter.Name] = _defaultValues[index];
|
||||
}
|
||||
|
||||
// A catch-all has to be the last part, so we're done.
|
||||
return true;
|
||||
}
|
||||
else if (pathSegment.IsSimple && pathSegment.Parts[0].IsParameter)
|
||||
{
|
||||
// A simple parameter captures the whole segment, or a default value if nothing was
|
||||
// provided.
|
||||
parameter = (RoutePatternParameter)pathSegment.Parts[0];
|
||||
if (requestSegment.Length > 0)
|
||||
{
|
||||
values[parameter.Name] = requestSegment.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_hasDefaultValue[index])
|
||||
{
|
||||
values[parameter.Name] = _defaultValues[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool MatchComplexSegment(
|
||||
RoutePatternPathSegment routeSegment,
|
||||
string requestSegment,
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
DispatcherValueCollection values)
|
||||
{
|
||||
var indexOfLastSegment = routeSegment.Parts.Count - 1;
|
||||
|
||||
// We match the request to the template starting at the rightmost parameter
|
||||
// If the last segment of template is optional, then request can match the
|
||||
// template with or without the last parameter. So we start with regular matching,
|
||||
// but if it doesn't match, we start with next to last parameter. Example:
|
||||
// Template: {p1}/{p2}.{p3?}. If the request is one/two.three it will match right away
|
||||
// giving p3 value of three. But if the request is one/two, we start matching from the
|
||||
// rightmost giving p3 the value of two, then we end up not matching the segment.
|
||||
// In this case we start again from p2 to match the request and we succeed giving
|
||||
// the value two to p2
|
||||
if (routeSegment.Parts[indexOfLastSegment] is RoutePatternParameter parameter && parameter.IsOptional &&
|
||||
routeSegment.Parts[indexOfLastSegment - 1].IsSeparator)
|
||||
{
|
||||
if (MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var separator = (RoutePatternSeparator)routeSegment.Parts[indexOfLastSegment - 1];
|
||||
if (requestSegment.EndsWith(
|
||||
separator.Content,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
return MatchComplexSegmentCore(
|
||||
routeSegment,
|
||||
requestSegment,
|
||||
Defaults,
|
||||
values,
|
||||
indexOfLastSegment - 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment);
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchComplexSegmentCore(
|
||||
RoutePatternPathSegment routeSegment,
|
||||
string requestSegment,
|
||||
IReadOnlyDictionary<string, object> defaults,
|
||||
DispatcherValueCollection values,
|
||||
int indexOfLastSegmentUsed)
|
||||
{
|
||||
Debug.Assert(routeSegment != null);
|
||||
Debug.Assert(routeSegment.Parts.Count > 1);
|
||||
|
||||
// Find last literal segment and get its last index in the string
|
||||
var lastIndex = requestSegment.Length;
|
||||
|
||||
RoutePatternParameter parameterNeedsValue = null; // Keeps track of a parameter segment that is pending a value
|
||||
RoutePatternPart lastLiteral = null; // Keeps track of the left-most literal we've encountered
|
||||
|
||||
var outValues = new DispatcherValueCollection();
|
||||
|
||||
while (indexOfLastSegmentUsed >= 0)
|
||||
{
|
||||
var newLastIndex = lastIndex;
|
||||
|
||||
var part = routeSegment.Parts[indexOfLastSegmentUsed];
|
||||
if (part.IsParameter)
|
||||
{
|
||||
// Hold on to the parameter so that we can fill it in when we locate the next literal
|
||||
parameterNeedsValue = (RoutePatternParameter)part;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(part.IsLiteral || part.IsSeparator);
|
||||
lastLiteral = part;
|
||||
|
||||
var startIndex = lastIndex - 1;
|
||||
// If we have a pending parameter subsegment, we must leave at least one character for that
|
||||
if (parameterNeedsValue != null)
|
||||
{
|
||||
startIndex--;
|
||||
}
|
||||
|
||||
if (startIndex < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int indexOfLiteral;
|
||||
if (part.IsLiteral)
|
||||
{
|
||||
var literal = (RoutePatternLiteral)part;
|
||||
indexOfLiteral = requestSegment.LastIndexOf(
|
||||
literal.Content,
|
||||
startIndex,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
var literal = (RoutePatternSeparator)part;
|
||||
indexOfLiteral = requestSegment.LastIndexOf(
|
||||
literal.Content,
|
||||
startIndex,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (indexOfLiteral == -1)
|
||||
{
|
||||
// If we couldn't find this literal index, this segment cannot match
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the first subsegment is a literal, it must match at the right-most extent of the request URI.
|
||||
// Without this check if your route had "/Foo/" we'd match the request URI "/somethingFoo/".
|
||||
// This check is related to the check we do at the very end of this function.
|
||||
if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1))
|
||||
{
|
||||
if (part is RoutePatternLiteral literal && ((indexOfLiteral + literal.Content.Length) != requestSegment.Length))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (part is RoutePatternSeparator separator && ((indexOfLiteral + separator.Content.Length) != requestSegment.Length))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
newLastIndex = indexOfLiteral;
|
||||
}
|
||||
|
||||
if ((parameterNeedsValue != null) &&
|
||||
(((lastLiteral != null) && !part.IsParameter) || (indexOfLastSegmentUsed == 0)))
|
||||
{
|
||||
// If we have a pending parameter that needs a value, grab that value
|
||||
|
||||
int parameterStartIndex;
|
||||
int parameterTextLength;
|
||||
|
||||
if (lastLiteral == null)
|
||||
{
|
||||
if (indexOfLastSegmentUsed == 0)
|
||||
{
|
||||
parameterStartIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameterStartIndex = newLastIndex;
|
||||
Debug.Assert(false, "indexOfLastSegementUsed should always be 0 from the check above");
|
||||
}
|
||||
parameterTextLength = lastIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're getting a value for a parameter that is somewhere in the middle of the segment
|
||||
if ((indexOfLastSegmentUsed == 0) && (part.IsParameter))
|
||||
{
|
||||
parameterStartIndex = 0;
|
||||
parameterTextLength = lastIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastLiteral.IsLiteral)
|
||||
{
|
||||
var literal = (RoutePatternLiteral)lastLiteral;
|
||||
parameterStartIndex = newLastIndex + literal.Content.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
var separator = (RoutePatternSeparator)lastLiteral;
|
||||
parameterStartIndex = newLastIndex + separator.Content.Length;
|
||||
}
|
||||
parameterTextLength = lastIndex - parameterStartIndex;
|
||||
}
|
||||
}
|
||||
|
||||
var parameterValueString = requestSegment.Substring(parameterStartIndex, parameterTextLength);
|
||||
|
||||
if (string.IsNullOrEmpty(parameterValueString))
|
||||
{
|
||||
// If we're here that means we have a segment that contains multiple sub-segments.
|
||||
// For these segments all parameters must have non-empty values. If the parameter
|
||||
// has an empty value it's not a match.
|
||||
return false;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there's a value in the segment for this parameter, use the subsegment value
|
||||
outValues.Add(parameterNeedsValue.Name, parameterValueString);
|
||||
}
|
||||
|
||||
parameterNeedsValue = null;
|
||||
lastLiteral = null;
|
||||
}
|
||||
|
||||
lastIndex = newLastIndex;
|
||||
indexOfLastSegmentUsed--;
|
||||
}
|
||||
|
||||
// If the last subsegment is a parameter, it's OK that we didn't parse all the way to the left extent of
|
||||
// the string since the parameter will have consumed all the remaining text anyway. If the last subsegment
|
||||
// is a literal then we *must* have consumed the entire text in that literal. Otherwise we end up matching
|
||||
// the route "Foo" to the request URI "somethingFoo". Thus we have to check that we parsed the *entire*
|
||||
// request URI in order for it to be a match.
|
||||
// This check is related to the check we do earlier in this function for LiteralSubsegments.
|
||||
if (lastIndex == 0 || routeSegment.Parts[0].IsParameter)
|
||||
{
|
||||
foreach (var item in outValues)
|
||||
{
|
||||
values.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public class RoutePatternParameter : RoutePatternPart
|
||||
{
|
||||
internal RoutePatternParameter(
|
||||
string rawText,
|
||||
string name,
|
||||
object defaultValue,
|
||||
RoutePatternParameterKind parameterKind,
|
||||
ConstraintReference[] constraints)
|
||||
{
|
||||
// See #475 - this code should have some asserts, but it can't because of the design of InlineRouteParameterParser.
|
||||
|
||||
RawText = rawText;
|
||||
Name = name;
|
||||
DefaultValue = defaultValue;
|
||||
ParameterKind = parameterKind;
|
||||
Constraints = constraints;
|
||||
|
||||
PartKind = RoutePatternPartKind.Parameter;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ConstraintReference> Constraints { get; }
|
||||
|
||||
public object DefaultValue { get; }
|
||||
|
||||
public bool IsCatchAll => ParameterKind == RoutePatternParameterKind.CatchAll;
|
||||
|
||||
public bool IsOptional => ParameterKind == RoutePatternParameterKind.Optional;
|
||||
|
||||
public RoutePatternParameterKind ParameterKind { get; }
|
||||
|
||||
public override RoutePatternPartKind PartKind { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public override string RawText { get; }
|
||||
|
||||
internal override string DebuggerToString()
|
||||
{
|
||||
return RawText ?? "{" + (IsCatchAll ? "*" : string.Empty) + Name + (IsOptional ? "?" : string.Empty) + "}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
public enum RoutePatternParameterKind
|
||||
{
|
||||
Standard,
|
||||
Optional,
|
||||
CatchAll,
|
||||
}
|
||||
}
|
|
@ -1,579 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
internal static class RoutePatternParser
|
||||
{
|
||||
private const char Separator = '/';
|
||||
private const char OpenBrace = '{';
|
||||
private const char CloseBrace = '}';
|
||||
private const char EqualsSign = '=';
|
||||
private const char QuestionMark = '?';
|
||||
private const char Asterisk = '*';
|
||||
private const string PeriodString = ".";
|
||||
|
||||
internal static readonly char[] InvalidParameterNameChars = new char[]
|
||||
{
|
||||
Separator,
|
||||
OpenBrace,
|
||||
CloseBrace,
|
||||
QuestionMark,
|
||||
Asterisk
|
||||
};
|
||||
|
||||
public static RoutePattern Parse(string pattern)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
var trimmedPattern = TrimPrefix(pattern);
|
||||
|
||||
var context = new TemplateParserContext(trimmedPattern);
|
||||
var segments = new List<RoutePatternPathSegment>();
|
||||
|
||||
while (context.MoveNext())
|
||||
{
|
||||
var i = context.Index;
|
||||
|
||||
if (context.Current == Separator)
|
||||
{
|
||||
// If we get here is means that there's a consecutive '/' character.
|
||||
// Templates don't start with a '/' and parsing a segment consumes the separator.
|
||||
throw new RoutePatternException(pattern, Resources.TemplateRoute_CannotHaveConsecutiveSeparators);
|
||||
}
|
||||
|
||||
if (!ParseSegment(context, segments))
|
||||
{
|
||||
throw new RoutePatternException(pattern, context.Error);
|
||||
}
|
||||
|
||||
// A successful parse should always result in us being at the end or at a separator.
|
||||
Debug.Assert(context.AtEnd() || context.Current == Separator);
|
||||
|
||||
if (context.Index <= i)
|
||||
{
|
||||
throw new InvalidProgramException("Infinite loop in the parser. This is a bug.");
|
||||
}
|
||||
}
|
||||
|
||||
if (IsAllValid(context, segments))
|
||||
{
|
||||
var builder = RoutePatternBuilder.Create(pattern);
|
||||
for (var i = 0; i < segments.Count; i++)
|
||||
{
|
||||
builder.PathSegments.Add(segments[i]);
|
||||
}
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RoutePatternException(pattern, context.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseSegment(TemplateParserContext context, List<RoutePatternPathSegment> segments)
|
||||
{
|
||||
Debug.Assert(context != null);
|
||||
Debug.Assert(segments != null);
|
||||
|
||||
var parts = new List<RoutePatternPart>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var i = context.Index;
|
||||
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
if (!context.MoveNext())
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
// This is an 'escaped' brace in a literal, like "{{foo"
|
||||
context.Back();
|
||||
if (!ParseLiteral(context, parts))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a parameter
|
||||
context.Back();
|
||||
if (!ParseParameter(context, parts))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ParseLiteral(context, parts))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.Current == Separator || context.AtEnd())
|
||||
{
|
||||
// We've reached the end of the segment
|
||||
break;
|
||||
}
|
||||
|
||||
if (context.Index <= i)
|
||||
{
|
||||
throw new InvalidProgramException("Infinite loop in the parser. This is a bug.");
|
||||
}
|
||||
}
|
||||
|
||||
if (IsSegmentValid(context, parts))
|
||||
{
|
||||
segments.Add(new RoutePatternPathSegment(null, parts.ToArray()));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseParameter(TemplateParserContext context, List<RoutePatternPart> parts)
|
||||
{
|
||||
Debug.Assert(context.Current == OpenBrace);
|
||||
context.Mark();
|
||||
|
||||
context.MoveNext();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
// This is an open brace inside of a parameter, it has to be escaped
|
||||
if (context.MoveNext())
|
||||
{
|
||||
if (context.Current != OpenBrace)
|
||||
{
|
||||
// If we see something like "{p1:regex(^\d{3", we will come here.
|
||||
context.Error = Resources.TemplateRoute_UnescapedBrace;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
// Example: "{p1:regex(^\d{"
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (context.Current == CloseBrace)
|
||||
{
|
||||
// When we encounter Closed brace here, it either means end of the parameter or it is a closed
|
||||
// brace in the parameter, in that case it needs to be escaped.
|
||||
// Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter
|
||||
if (!context.MoveNext())
|
||||
{
|
||||
// This is the end of the string -and we have a valid parameter
|
||||
break;
|
||||
}
|
||||
|
||||
if (context.Current == CloseBrace)
|
||||
{
|
||||
// This is an 'escaped' brace in a parameter name
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the end of the parameter
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.MoveNext())
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var text = context.Capture();
|
||||
if (text == "{}")
|
||||
{
|
||||
context.Error = Resources.FormatTemplateRoute_InvalidParameterName(string.Empty);
|
||||
return false;
|
||||
}
|
||||
|
||||
var inside = text.Substring(1, text.Length - 2);
|
||||
var decoded = inside.Replace("}}", "}").Replace("{{", "{");
|
||||
|
||||
// At this point, we need to parse the raw name for inline constraint,
|
||||
// default values and optional parameters.
|
||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(text, decoded);
|
||||
|
||||
// See #475 - this is here because InlineRouteParameterParser can't return errors
|
||||
if (decoded.StartsWith("*") && decoded.EndsWith("?"))
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (templatePart.IsOptional && templatePart.DefaultValue != null)
|
||||
{
|
||||
// Cannot be optional and have a default value.
|
||||
// The only way to declare an optional parameter is to have a ? at the end,
|
||||
// hence we cannot have both default value and optional parameter within the template.
|
||||
// A workaround is to add it as a separate entry in the defaults argument.
|
||||
context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
var parameterName = templatePart.Name;
|
||||
if (IsValidParameterName(context, parameterName))
|
||||
{
|
||||
parts.Add(templatePart);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseLiteral(TemplateParserContext context, List<RoutePatternPart> parts)
|
||||
{
|
||||
context.Mark();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == Separator)
|
||||
{
|
||||
// End of the segment
|
||||
break;
|
||||
}
|
||||
else if (context.Current == OpenBrace)
|
||||
{
|
||||
if (!context.MoveNext())
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
// This is an 'escaped' brace in a literal, like "{{foo" - keep going.
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've just seen the start of a parameter, so back up.
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (context.Current == CloseBrace)
|
||||
{
|
||||
if (!context.MoveNext())
|
||||
{
|
||||
// This is a dangling close-brace, which is not allowed
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.Current == CloseBrace)
|
||||
{
|
||||
// This is an 'escaped' brace in a literal, like "{{foo" - keep going.
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is an unbalanced close-brace, which is not allowed
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.MoveNext())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var encoded = context.Capture();
|
||||
var decoded = encoded.Replace("}}", "}").Replace("{{", "{");
|
||||
if (IsValidLiteral(context, decoded))
|
||||
{
|
||||
parts.Add(RoutePatternPart.CreateLiteralFromText(encoded, decoded));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAllValid(TemplateParserContext context, List<RoutePatternPathSegment> segments)
|
||||
{
|
||||
// A catch-all parameter must be the last part of the last segment
|
||||
for (var i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var segment = segments[i];
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
if (part.IsParameter &&
|
||||
((RoutePatternParameter)part).IsCatchAll &&
|
||||
(i != segments.Count - 1 || j != segment.Parts.Count - 1))
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CatchAllMustBeLast;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsSegmentValid(TemplateParserContext context, List<RoutePatternPart> parts)
|
||||
{
|
||||
// If a segment has multiple parts, then it can't contain a catch all.
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
{
|
||||
var part = parts[i];
|
||||
if (part.IsParameter && ((RoutePatternParameter)part).IsCatchAll && parts.Count > 1)
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if a segment has multiple parts, then only the last one parameter can be optional
|
||||
// if it is following a optional seperator.
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
{
|
||||
var part = parts[i];
|
||||
|
||||
if (part.IsParameter && ((RoutePatternParameter)part).IsOptional && parts.Count > 1)
|
||||
{
|
||||
// This optional parameter is the last part in the segment
|
||||
if (i == parts.Count - 1)
|
||||
{
|
||||
var previousPart = parts[i - 1];
|
||||
|
||||
if (!previousPart.IsLiteral && !previousPart.IsSeparator)
|
||||
{
|
||||
// The optional parameter is preceded by something that is not a literal or separator
|
||||
// Example of error message:
|
||||
// "In the segment '{RouteValue}{param?}', the optional parameter 'param' is preceded
|
||||
// by an invalid segment '{RouteValue}'. Only a period (.) can precede an optional parameter.
|
||||
context.Error = string.Format(
|
||||
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
||||
RoutePatternPathSegment.DebuggerToString(parts),
|
||||
((RoutePatternParameter)part).Name,
|
||||
parts[i - 1].DebuggerToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (previousPart is RoutePatternLiteral literal && literal.Content != PeriodString)
|
||||
{
|
||||
// The optional parameter is preceded by a literal other than period.
|
||||
// Example of error message:
|
||||
// "In the segment '{RouteValue}-{param?}', the optional parameter 'param' is preceded
|
||||
// by an invalid segment '-'. Only a period (.) can precede an optional parameter.
|
||||
context.Error = string.Format(
|
||||
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
||||
RoutePatternPathSegment.DebuggerToString(parts),
|
||||
((RoutePatternParameter)part).Name,
|
||||
parts[i - 1].DebuggerToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
parts[i - 1] = RoutePatternPart.CreateSeparatorFromText(previousPart.RawText, ((RoutePatternLiteral)previousPart).Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This optional parameter is not the last one in the segment
|
||||
// Example:
|
||||
// An optional parameter must be at the end of the segment. In the segment '{RouteValue?})',
|
||||
// optional parameter 'RouteValue' is followed by ')'
|
||||
context.Error = string.Format(
|
||||
Resources.TemplateRoute_OptionalParameterHasTobeTheLast,
|
||||
RoutePatternPathSegment.DebuggerToString(parts),
|
||||
((RoutePatternParameter)part).Name,
|
||||
parts[i + 1].DebuggerToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A segment cannot contain two consecutive parameters
|
||||
var isLastSegmentParameter = false;
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
{
|
||||
var part = parts[i];
|
||||
if (part.IsParameter && isLastSegmentParameter)
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters;
|
||||
return false;
|
||||
}
|
||||
|
||||
isLastSegmentParameter = part.IsParameter;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidParameterName(TemplateParserContext context, string parameterName)
|
||||
{
|
||||
if (parameterName.Length == 0 || parameterName.IndexOfAny(InvalidParameterNameChars) >= 0)
|
||||
{
|
||||
context.Error = Resources.FormatTemplateRoute_InvalidParameterName(parameterName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!context.ParameterNames.Add(parameterName))
|
||||
{
|
||||
context.Error = Resources.FormatTemplateRoute_RepeatedParameter(parameterName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidLiteral(TemplateParserContext context, string literal)
|
||||
{
|
||||
Debug.Assert(context != null);
|
||||
Debug.Assert(literal != null);
|
||||
|
||||
if (literal.IndexOf(QuestionMark) != -1)
|
||||
{
|
||||
context.Error = Resources.FormatTemplateRoute_InvalidLiteral(literal);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string TrimPrefix(string routePattern)
|
||||
{
|
||||
if (routePattern.StartsWith("~/", StringComparison.Ordinal))
|
||||
{
|
||||
return routePattern.Substring(2);
|
||||
}
|
||||
else if (routePattern.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
return routePattern.Substring(1);
|
||||
}
|
||||
else if (routePattern.StartsWith("~", StringComparison.Ordinal))
|
||||
{
|
||||
throw new RoutePatternException(routePattern, Resources.TemplateRoute_InvalidRouteTemplate);
|
||||
}
|
||||
return routePattern;
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
private class TemplateParserContext
|
||||
{
|
||||
private readonly string _template;
|
||||
private int _index;
|
||||
private int? _mark;
|
||||
|
||||
private HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public TemplateParserContext(string template)
|
||||
{
|
||||
Debug.Assert(template != null);
|
||||
_template = template;
|
||||
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
public char Current
|
||||
{
|
||||
get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; }
|
||||
}
|
||||
|
||||
public int Index => _index;
|
||||
|
||||
public string Error
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public HashSet<string> ParameterNames
|
||||
{
|
||||
get { return _parameterNames; }
|
||||
}
|
||||
|
||||
public bool Back()
|
||||
{
|
||||
return --_index >= 0;
|
||||
}
|
||||
|
||||
public bool AtEnd()
|
||||
{
|
||||
return _index >= _template.Length;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
return ++_index < _template.Length;
|
||||
}
|
||||
|
||||
public void Mark()
|
||||
{
|
||||
Debug.Assert(_index >= 0);
|
||||
|
||||
// Index is always the index of the character *past* Current - we want to 'mark' Current.
|
||||
_mark = _index;
|
||||
}
|
||||
|
||||
public string Capture()
|
||||
{
|
||||
if (_mark.HasValue)
|
||||
{
|
||||
var value = _template.Substring(_mark.Value, _index - _mark.Value);
|
||||
_mark = null;
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
if (_index == -1)
|
||||
{
|
||||
return _template;
|
||||
}
|
||||
else if (_mark.HasValue)
|
||||
{
|
||||
return _template.Substring(0, _mark.Value) +
|
||||
"|" +
|
||||
_template.Substring(_mark.Value, _index - _mark.Value) +
|
||||
"|" +
|
||||
_template.Substring(_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _template.Substring(0, _index) + "|" + _template.Substring(_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,235 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
public abstract class RoutePatternPart
|
||||
{
|
||||
// This class is not an extensibility point. It is abstract so we can extend it
|
||||
// or add semantics later inside the library.
|
||||
internal RoutePatternPart()
|
||||
{
|
||||
}
|
||||
|
||||
public static RoutePatternLiteral CreateLiteral(string content)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
|
||||
}
|
||||
|
||||
if (content.IndexOf('?') >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidLiteral(content));
|
||||
}
|
||||
|
||||
return new RoutePatternLiteral(null, content);
|
||||
}
|
||||
|
||||
public static RoutePatternLiteral CreateLiteralFromText(string rawText, string content)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
|
||||
}
|
||||
|
||||
if (content.IndexOf('?') >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidLiteral(content));
|
||||
}
|
||||
|
||||
return new RoutePatternLiteral(rawText, content);
|
||||
}
|
||||
|
||||
public static RoutePatternSeparator CreateSeparator(string content)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
|
||||
}
|
||||
|
||||
return new RoutePatternSeparator(null, content);
|
||||
}
|
||||
|
||||
public static RoutePatternSeparator CreateSeparatorFromText(string rawText, string content)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
|
||||
}
|
||||
|
||||
return new RoutePatternSeparator(rawText, content);
|
||||
}
|
||||
|
||||
public static RoutePatternParameter CreateParameter(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||
}
|
||||
|
||||
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||
}
|
||||
|
||||
return CreateParameterFromText(null, name, null, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||
}
|
||||
|
||||
public static RoutePatternParameter CreateParameterFromText(string rawText, string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||
}
|
||||
|
||||
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||
}
|
||||
|
||||
return CreateParameterFromText(rawText, name, null, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||
}
|
||||
|
||||
public static RoutePatternParameter CreateParameter(string name, object defaultValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||
}
|
||||
|
||||
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||
}
|
||||
|
||||
return CreateParameterFromText(null, name, defaultValue, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||
}
|
||||
|
||||
public static RoutePatternParameter CreateParameterFromText(string rawText, string name, object defaultValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||
}
|
||||
|
||||
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||
}
|
||||
|
||||
return CreateParameterFromText(rawText, name, defaultValue, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||
}
|
||||
|
||||
public static RoutePatternParameter CreateParameter(
|
||||
string name,
|
||||
object defaultValue,
|
||||
RoutePatternParameterKind parameterKind)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||
}
|
||||
|
||||
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||
}
|
||||
|
||||
if (defaultValue != null && parameterKind == RoutePatternParameterKind.Optional)
|
||||
{
|
||||
throw new ArgumentNullException(Resources.TemplateRoute_OptionalCannotHaveDefaultValue, nameof(parameterKind));
|
||||
}
|
||||
|
||||
return CreateParameterFromText(null, name, defaultValue, parameterKind, Array.Empty<ConstraintReference>());
|
||||
}
|
||||
|
||||
public static RoutePatternParameter CreateParameterFromText(
|
||||
string rawText,
|
||||
string name,
|
||||
object defaultValue,
|
||||
RoutePatternParameterKind parameterKind)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||
}
|
||||
|
||||
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||
}
|
||||
|
||||
if (defaultValue != null && parameterKind == RoutePatternParameterKind.Optional)
|
||||
{
|
||||
throw new ArgumentNullException(Resources.TemplateRoute_OptionalCannotHaveDefaultValue, nameof(parameterKind));
|
||||
}
|
||||
|
||||
return CreateParameterFromText(rawText, name, defaultValue, parameterKind, Array.Empty<ConstraintReference>());
|
||||
}
|
||||
|
||||
public static RoutePatternParameter CreateParameter(
|
||||
string name,
|
||||
object defaultValue,
|
||||
RoutePatternParameterKind parameterKind,
|
||||
params ConstraintReference[] constraints)
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||
}
|
||||
|
||||
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||
}
|
||||
|
||||
if (defaultValue != null && parameterKind == RoutePatternParameterKind.Optional)
|
||||
{
|
||||
throw new ArgumentNullException(Resources.TemplateRoute_OptionalCannotHaveDefaultValue, nameof(parameterKind));
|
||||
}
|
||||
|
||||
return new RoutePatternParameter(null, name, defaultValue, parameterKind, constraints);
|
||||
}
|
||||
|
||||
public static RoutePatternParameter CreateParameterFromText(
|
||||
string rawText,
|
||||
string name,
|
||||
object defaultValue,
|
||||
RoutePatternParameterKind parameterKind,
|
||||
params ConstraintReference[] constraints)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||
}
|
||||
|
||||
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||
}
|
||||
|
||||
if (defaultValue != null && parameterKind == RoutePatternParameterKind.Optional)
|
||||
{
|
||||
throw new ArgumentNullException(Resources.TemplateRoute_OptionalCannotHaveDefaultValue, nameof(parameterKind));
|
||||
}
|
||||
|
||||
return new RoutePatternParameter(rawText, name, defaultValue, parameterKind, constraints);
|
||||
}
|
||||
|
||||
public abstract RoutePatternPartKind PartKind { get; }
|
||||
|
||||
public abstract string RawText { get; }
|
||||
|
||||
public bool IsLiteral => PartKind == RoutePatternPartKind.Literal;
|
||||
|
||||
public bool IsParameter => PartKind == RoutePatternPartKind.Parameter;
|
||||
|
||||
public bool IsSeparator => PartKind == RoutePatternPartKind.Separator;
|
||||
|
||||
internal abstract string DebuggerToString();
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
public enum RoutePatternPartKind
|
||||
{
|
||||
Literal,
|
||||
Parameter,
|
||||
Separator,
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public sealed class RoutePatternPathSegment
|
||||
{
|
||||
internal RoutePatternPathSegment(string rawText, RoutePatternPart[] parts)
|
||||
{
|
||||
RawText = rawText;
|
||||
Parts = parts;
|
||||
}
|
||||
|
||||
public bool IsSimple => Parts.Count == 1;
|
||||
|
||||
public IReadOnlyList<RoutePatternPart> Parts { get; }
|
||||
|
||||
public string RawText { get; set; }
|
||||
|
||||
internal string DebuggerToString()
|
||||
{
|
||||
return RawText ?? DebuggerToString(Parts);
|
||||
}
|
||||
|
||||
internal static string DebuggerToString(IReadOnlyList<RoutePatternPart> parts)
|
||||
{
|
||||
return string.Join(string.Empty, parts.Select(p => p.DebuggerToString()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +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.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public sealed class RoutePatternSeparator : RoutePatternPart
|
||||
{
|
||||
internal RoutePatternSeparator(string rawText, string content)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(content));
|
||||
|
||||
RawText = rawText;
|
||||
Content = content;
|
||||
|
||||
PartKind = RoutePatternPartKind.Separator;
|
||||
}
|
||||
|
||||
public string Content { get; }
|
||||
|
||||
public override RoutePatternPartKind PartKind { get; }
|
||||
|
||||
public override string RawText { get; }
|
||||
|
||||
internal override string DebuggerToString()
|
||||
{
|
||||
return RawText ?? Content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Dispatcher.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
|
@ -1,338 +0,0 @@
|
|||
// <auto-generated />
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNetCore.Dispatcher.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Multiple endpoints matched. The following endpoints matched the request:{0}{0}{1}
|
||||
/// </summary>
|
||||
internal static string AmbiguousEndpoints
|
||||
{
|
||||
get => GetString("AmbiguousEndpoints");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiple endpoints matched. The following endpoints matched the request:{0}{0}{1}
|
||||
/// </summary>
|
||||
internal static string FormatAmbiguousEndpoints(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("AmbiguousEndpoints"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// Value cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string Argument_NullOrEmpty
|
||||
{
|
||||
get => GetString("Argument_NullOrEmpty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string FormatArgument_NullOrEmpty()
|
||||
=> GetString("Argument_NullOrEmpty");
|
||||
|
||||
/// <summary>
|
||||
/// The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.
|
||||
/// </summary>
|
||||
internal static string DefaultConstraintResolver_AmbiguousCtors
|
||||
{
|
||||
get => GetString("DefaultConstraintResolver_AmbiguousCtors");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.
|
||||
/// </summary>
|
||||
internal static string FormatDefaultConstraintResolver_AmbiguousCtors(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DefaultConstraintResolver_AmbiguousCtors"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.
|
||||
/// </summary>
|
||||
internal static string DefaultConstraintResolver_CouldNotFindCtor
|
||||
{
|
||||
get => GetString("DefaultConstraintResolver_CouldNotFindCtor");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.
|
||||
/// </summary>
|
||||
internal static string FormatDefaultConstraintResolver_CouldNotFindCtor(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DefaultConstraintResolver_CouldNotFindCtor"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.
|
||||
/// </summary>
|
||||
internal static string DefaultConstraintResolver_TypeNotConstraint
|
||||
{
|
||||
get => GetString("DefaultConstraintResolver_TypeNotConstraint");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.
|
||||
/// </summary>
|
||||
internal static string FormatDefaultConstraintResolver_TypeNotConstraint(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DefaultConstraintResolver_TypeNotConstraint"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// </summary>
|
||||
internal static string DispatcherValueConstraintBuilder_CouldNotResolveConstraint
|
||||
{
|
||||
get => GetString("DispatcherValueConstraintBuilder_CouldNotResolveConstraint");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatDispatcherValueConstraintBuilder_CouldNotResolveConstraint(object p0, object p1, object p2, object p3)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherValueConstraintBuilder_CouldNotResolveConstraint"), p0, p1, p2, p3);
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string DispatcherValueConstraintBuilder_ValidationMustBeStringOrCustomConstraint
|
||||
{
|
||||
get => GetString("DispatcherValueConstraintBuilder_ValidationMustBeStringOrCustomConstraint");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatDispatcherValueConstraintBuilder_ValidationMustBeStringOrCustomConstraint(object p0, object p1, object p2, object p3)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherValueConstraintBuilder_ValidationMustBeStringOrCustomConstraint"), p0, p1, p2, p3);
|
||||
|
||||
/// <summary>
|
||||
/// The collection cannot be empty.
|
||||
/// </summary>
|
||||
internal static string RoutePatternBuilder_CollectionCannotBeEmpty
|
||||
{
|
||||
get => GetString("RoutePatternBuilder_CollectionCannotBeEmpty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The collection cannot be empty.
|
||||
/// </summary>
|
||||
internal static string FormatRoutePatternBuilder_CollectionCannotBeEmpty()
|
||||
=> GetString("RoutePatternBuilder_CollectionCannotBeEmpty");
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveCatchAllInMultiSegment
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveCatchAllInMultiSegment()
|
||||
=> GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
|
||||
|
||||
/// <summary>
|
||||
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveConsecutiveParameters
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveConsecutiveParameters");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveConsecutiveParameters()
|
||||
=> GetString("TemplateRoute_CannotHaveConsecutiveParameters");
|
||||
|
||||
/// <summary>
|
||||
/// The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveConsecutiveSeparators
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveConsecutiveSeparators");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveConsecutiveSeparators()
|
||||
=> GetString("TemplateRoute_CannotHaveConsecutiveSeparators");
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter cannot be marked optional.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CatchAllCannotBeOptional
|
||||
{
|
||||
get => GetString("TemplateRoute_CatchAllCannotBeOptional");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter cannot be marked optional.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CatchAllCannotBeOptional()
|
||||
=> GetString("TemplateRoute_CatchAllCannotBeOptional");
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter can only appear as the last segment of the route template.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CatchAllMustBeLast
|
||||
{
|
||||
get => GetString("TemplateRoute_CatchAllMustBeLast");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter can only appear as the last segment of the route template.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CatchAllMustBeLast()
|
||||
=> GetString("TemplateRoute_CatchAllMustBeLast");
|
||||
|
||||
/// <summary>
|
||||
/// The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidLiteral
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidLiteral");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidLiteral(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidLiteral"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidParameterName
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidParameterName");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidParameterName(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidParameterName"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The route template cannot start with a '~' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidRouteTemplate
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidRouteTemplate");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route template cannot start with a '~' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidRouteTemplate()
|
||||
=> GetString("TemplateRoute_InvalidRouteTemplate");
|
||||
|
||||
/// <summary>
|
||||
/// There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_MismatchedParameter
|
||||
{
|
||||
get => GetString("TemplateRoute_MismatchedParameter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_MismatchedParameter()
|
||||
=> GetString("TemplateRoute_MismatchedParameter");
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter cannot have default value.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalCannotHaveDefaultValue
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter cannot have default value.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalCannotHaveDefaultValue()
|
||||
=> GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
|
||||
|
||||
/// <summary>
|
||||
/// In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalParameterCanbBePrecededByPeriod
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalParameterCanbBePrecededByPeriod");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_OptionalParameterCanbBePrecededByPeriod"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalParameterHasTobeTheLast
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalParameterHasTobeTheLast");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalParameterHasTobeTheLast(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_OptionalParameterHasTobeTheLast"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' appears more than one time in the route template.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_RepeatedParameter
|
||||
{
|
||||
get => GetString("TemplateRoute_RepeatedParameter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' appears more than one time in the route template.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_RepeatedParameter(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_RepeatedParameter"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_UnescapedBrace
|
||||
{
|
||||
get => GetString("TemplateRoute_UnescapedBrace");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_UnescapedBrace()
|
||||
=> GetString("TemplateRoute_UnescapedBrace");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
||||
System.Diagnostics.Debug.Assert(value != null);
|
||||
|
||||
if (formatterNames != null)
|
||||
{
|
||||
for (var i = 0; i < formatterNames.Length; i++)
|
||||
{
|
||||
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AmbiguousEndpoints" xml:space="preserve">
|
||||
<value>Multiple endpoints matched. The following endpoints matched the request:{0}{0}{1}</value>
|
||||
<comment>0 is the newline - 1 is a newline separate list of action display names</comment>
|
||||
</data>
|
||||
<data name="Argument_NullOrEmpty" xml:space="preserve">
|
||||
<value>Value cannot be null or empty.</value>
|
||||
</data>
|
||||
<data name="DefaultConstraintResolver_AmbiguousCtors" xml:space="preserve">
|
||||
<value>The constructor to use for activating the constraint type '{0}' is ambiguous. Multiple constructors were found with the following number of parameters: {1}.</value>
|
||||
</data>
|
||||
<data name="DefaultConstraintResolver_CouldNotFindCtor" xml:space="preserve">
|
||||
<value>Could not find a constructor for constraint type '{0}' with the following number of parameters: {1}.</value>
|
||||
</data>
|
||||
<data name="DefaultConstraintResolver_TypeNotConstraint" xml:space="preserve">
|
||||
<value>The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.</value>
|
||||
</data>
|
||||
<data name="DispatcherValueConstraintBuilder_CouldNotResolveConstraint" xml:space="preserve">
|
||||
<value>The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.</value>
|
||||
</data>
|
||||
<data name="DispatcherValueConstraintBuilder_ValidationMustBeStringOrCustomConstraint" xml:space="preserve">
|
||||
<value>The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.</value>
|
||||
</data>
|
||||
<data name="RoutePatternBuilder_CollectionCannotBeEmpty" xml:space="preserve">
|
||||
<value>The collection cannot be empty.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveCatchAllInMultiSegment" xml:space="preserve">
|
||||
<value>A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveConsecutiveParameters" xml:space="preserve">
|
||||
<value>A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveConsecutiveSeparators" xml:space="preserve">
|
||||
<value>The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
|
||||
<value>A catch-all parameter cannot be marked optional.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CatchAllMustBeLast" xml:space="preserve">
|
||||
<value>A catch-all parameter can only appear as the last segment of the route template.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidLiteral" xml:space="preserve">
|
||||
<value>The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidParameterName" xml:space="preserve">
|
||||
<value>The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidRouteTemplate" xml:space="preserve">
|
||||
<value>The route template cannot start with a '~' character unless followed by a '/'.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_MismatchedParameter" xml:space="preserve">
|
||||
<value>There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalCannotHaveDefaultValue" xml:space="preserve">
|
||||
<value>An optional parameter cannot have default value.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalParameterCanbBePrecededByPeriod" xml:space="preserve">
|
||||
<value>In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalParameterHasTobeTheLast" xml:space="preserve">
|
||||
<value>An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_RepeatedParameter" xml:space="preserve">
|
||||
<value>The route parameter name '{0}' appears more than one time in the route template.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_UnescapedBrace" xml:space="preserve">
|
||||
<value>In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1,41 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RoutePatternAddress : Address, IRoutePatternAddress
|
||||
{
|
||||
public RoutePatternAddress(string pattern, object values, params object[] metadata)
|
||||
: this(pattern, values, null, metadata)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutePatternAddress(string pattern, object values, string displayName, params object[] metadata)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
Pattern = pattern;
|
||||
Defaults = new DispatcherValueCollection(values);
|
||||
DisplayName = displayName;
|
||||
Metadata = new MetadataCollection(metadata);
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
|
||||
public override MetadataCollection Metadata { get; }
|
||||
|
||||
public string Pattern { get; }
|
||||
|
||||
public DispatcherValueCollection Defaults { get; }
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
// This isn't a proposed design, just a placeholder to demonstrate that things are wired up correctly.
|
||||
public class RoutePatternAddressSelector
|
||||
{
|
||||
private readonly AddressTable _addressTable;
|
||||
|
||||
public RoutePatternAddressSelector(AddressTable addressTable)
|
||||
{
|
||||
if (addressTable == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(addressTable));
|
||||
}
|
||||
|
||||
_addressTable = addressTable;
|
||||
}
|
||||
|
||||
public Address SelectAddress(object values)
|
||||
{
|
||||
return SelectAddress(new DispatcherValueCollection(values));
|
||||
}
|
||||
|
||||
public Address SelectAddress(DispatcherValueCollection values)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
// Capture the current state so we don't see partial updates.
|
||||
var groups = _addressTable.AddressGroups;
|
||||
for (var i = 0; i < groups.Count; i++)
|
||||
{
|
||||
var matches = new List<Address>();
|
||||
var group = groups[i];
|
||||
|
||||
for (var j = 0; j < group.Count; j++)
|
||||
{
|
||||
var address = group[j] as IRoutePatternAddress;
|
||||
if (address == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsMatch(address, values))
|
||||
{
|
||||
matches.Add(group[j]);
|
||||
}
|
||||
}
|
||||
|
||||
switch (matches.Count)
|
||||
{
|
||||
case 0:
|
||||
// No match, keep going.
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return matches[0];
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Ambiguous bro!");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsMatch(IRoutePatternAddress address, DispatcherValueCollection values)
|
||||
{
|
||||
foreach (var kvp in address.Defaults)
|
||||
{
|
||||
values.TryGetValue(kvp.Key, out var value);
|
||||
|
||||
if (!string.Equals(Convert.ToString(kvp.Value) ?? string.Empty, Convert.ToString(value) ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,439 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RoutePatternBinder
|
||||
{
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
private readonly ObjectPool<UriBuildingContext> _pool;
|
||||
|
||||
private readonly DispatcherValueCollection _defaults;
|
||||
private readonly DispatcherValueCollection _filters;
|
||||
private readonly RoutePattern _pattern;
|
||||
|
||||
internal RoutePatternBinder(
|
||||
UrlEncoder urlEncoder,
|
||||
ObjectPool<UriBuildingContext> pool,
|
||||
RoutePattern pattern,
|
||||
DispatcherValueCollection defaults)
|
||||
{
|
||||
if (urlEncoder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(urlEncoder));
|
||||
}
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pool));
|
||||
}
|
||||
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
_urlEncoder = urlEncoder;
|
||||
_pool = pool;
|
||||
_pattern = pattern;
|
||||
_defaults = defaults;
|
||||
|
||||
// Any default that doesn't have a corresponding parameter is a 'filter' and if a value
|
||||
// is provided for that 'filter' it must match the value in defaults.
|
||||
_filters = new DispatcherValueCollection(_defaults);
|
||||
foreach (var parameter in _pattern.Parameters)
|
||||
{
|
||||
_filters.Remove(parameter.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: Get the list of values we're going to try to use to match and generate this URI
|
||||
public (DispatcherValueCollection acceptedValues, DispatcherValueCollection combinedValues) GetValues(DispatcherValueCollection ambientValues, DispatcherValueCollection values)
|
||||
{
|
||||
var context = new TemplateBindingContext(_defaults);
|
||||
|
||||
// Find out which entries in the URI are valid for the URI we want to generate.
|
||||
// If the URI had ordered parameters a="1", b="2", c="3" and the new values
|
||||
// specified that b="9", then we need to invalidate everything after it. The new
|
||||
// values should then be a="1", b="9", c=<no value>.
|
||||
//
|
||||
// We also handle the case where a parameter is optional but has no value - we shouldn't
|
||||
// accept additional parameters that appear *after* that parameter.
|
||||
for (var i = 0; i < _pattern.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = _pattern.Parameters[i];
|
||||
|
||||
// If it's a parameter subsegment, examine the current value to see if it matches the new value
|
||||
var parameterName = parameter.Name;
|
||||
|
||||
var hasNewParameterValue = values.TryGetValue(parameterName, out var newParameterValue);
|
||||
|
||||
object currentParameterValue = null;
|
||||
var hasCurrentParameterValue = ambientValues != null &&
|
||||
ambientValues.TryGetValue(parameterName, out currentParameterValue);
|
||||
|
||||
if (hasNewParameterValue && hasCurrentParameterValue)
|
||||
{
|
||||
if (!RoutePartsEqual(currentParameterValue, newParameterValue))
|
||||
{
|
||||
// Stop copying current values when we find one that doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasNewParameterValue &&
|
||||
!hasCurrentParameterValue &&
|
||||
_defaults?.ContainsKey(parameter.Name) != true)
|
||||
{
|
||||
// This is an unsatisfied parameter value and there are no defaults. We might still
|
||||
// be able to generate a URL but we should stop 'accepting' ambient values.
|
||||
//
|
||||
// This might be a case like:
|
||||
// template: a/{b?}/{c?}
|
||||
// ambient: { c = 17 }
|
||||
// values: { }
|
||||
//
|
||||
// We can still generate a URL from this ("/a") but we shouldn't accept 'c' because
|
||||
// we can't use it.
|
||||
//
|
||||
// In the example above we should fall into this block for 'b'.
|
||||
break;
|
||||
}
|
||||
|
||||
// If the parameter is a match, add it to the list of values we will use for URI generation
|
||||
if (hasNewParameterValue)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(newParameterValue))
|
||||
{
|
||||
context.Accept(parameterName, newParameterValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hasCurrentParameterValue)
|
||||
{
|
||||
context.Accept(parameterName, currentParameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all remaining new values to the list of values we will use for URI generation
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(kvp.Value))
|
||||
{
|
||||
context.Accept(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Accept all remaining default values if they match a required parameter
|
||||
for (var i = 0; i < _pattern.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = _pattern.Parameters[i];
|
||||
if (parameter.IsOptional || parameter.IsCatchAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context.NeedsValue(parameter.Name))
|
||||
{
|
||||
// Add the default value only if there isn't already a new value for it and
|
||||
// only if it actually has a default value, which we determine based on whether
|
||||
// the parameter value is required.
|
||||
context.AcceptDefault(parameter.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that all required parameters have a value.
|
||||
for (var i = 0; i < _pattern.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = _pattern.Parameters[i];
|
||||
if (parameter.IsOptional || parameter.IsCatchAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!context.AcceptedValues.ContainsKey(parameter.Name))
|
||||
{
|
||||
// We don't have a value for this parameter, so we can't generate a url.
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Any default values that don't appear as parameters are treated like filters. Any new values
|
||||
// provided must match these defaults.
|
||||
foreach (var filter in _filters)
|
||||
{
|
||||
var parameter = _pattern.GetParameter(filter.Key);
|
||||
if (parameter != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (values.TryGetValue(filter.Key, out var value))
|
||||
{
|
||||
if (!RoutePartsEqual(value, filter.Value))
|
||||
{
|
||||
// If there is a non-parameterized value in the route and there is a
|
||||
// new value for it and it doesn't match, this route won't match.
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any ambient values that don't match parameters - they need to be visible to constraints
|
||||
// but they will ignored by link generation.
|
||||
var combinedValues = new DispatcherValueCollection(context.AcceptedValues);
|
||||
if (ambientValues != null)
|
||||
{
|
||||
foreach (var kvp in ambientValues)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(kvp.Value))
|
||||
{
|
||||
var parameter = _pattern.GetParameter(kvp.Key);
|
||||
if (parameter == null && !context.AcceptedValues.ContainsKey(kvp.Key))
|
||||
{
|
||||
combinedValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (context.AcceptedValues, combinedValues);
|
||||
}
|
||||
|
||||
// Step 2: If the route is a match generate the appropriate URI
|
||||
public string BindValues(DispatcherValueCollection acceptedValues)
|
||||
{
|
||||
var context = _pool.Get();
|
||||
var result = BindValues(context, acceptedValues);
|
||||
_pool.Return(context);
|
||||
return result;
|
||||
}
|
||||
|
||||
private string BindValues(UriBuildingContext context, DispatcherValueCollection acceptedValues)
|
||||
{
|
||||
for (var i = 0; i < _pattern.PathSegments.Count; i++)
|
||||
{
|
||||
Debug.Assert(context.BufferState == SegmentState.Beginning);
|
||||
Debug.Assert(context.UriState == SegmentState.Beginning);
|
||||
|
||||
var segment = _pattern.PathSegments[i];
|
||||
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
|
||||
if (part.IsLiteral)
|
||||
{
|
||||
if (!context.Accept(_urlEncoder, ((RoutePatternLiteral)part).Content))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (part.IsSeparator)
|
||||
{
|
||||
if (!context.Accept(_urlEncoder, ((RoutePatternSeparator)part).Content))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (part.IsParameter && part is RoutePatternParameter parameter)
|
||||
{
|
||||
// If it's a parameter, get its value
|
||||
object value;
|
||||
var hasValue = acceptedValues.TryGetValue(parameter.Name, out value);
|
||||
if (hasValue)
|
||||
{
|
||||
acceptedValues.Remove(parameter.Name);
|
||||
}
|
||||
|
||||
var isSameAsDefault = false;
|
||||
object defaultValue;
|
||||
if (_defaults != null && _defaults.TryGetValue(parameter.Name, out defaultValue))
|
||||
{
|
||||
if (RoutePartsEqual(value, defaultValue))
|
||||
{
|
||||
isSameAsDefault = true;
|
||||
}
|
||||
}
|
||||
|
||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (isSameAsDefault)
|
||||
{
|
||||
// If the accepted value is the same as the default value buffer it since
|
||||
// we won't necessarily add it to the URI we generate.
|
||||
if (!context.Buffer(_urlEncoder, converted))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the value is not accepted, it is null or empty value in the
|
||||
// middle of the segment. We accept this if the parameter is an
|
||||
// optional parameter and it is preceded by an optional seperator.
|
||||
// I this case, we need to remove the optional seperator that we
|
||||
// have added to the URI
|
||||
// Example: template = {id}.{format?}. parameters: id=5
|
||||
// In this case after we have generated "5.", we wont find any value
|
||||
// for format, so we remove '.' and generate 5.
|
||||
if (!context.Accept(_urlEncoder, converted))
|
||||
{
|
||||
if (j != 0 && parameter.IsOptional && segment.Parts[j - 1].IsSeparator)
|
||||
{
|
||||
context.Remove(((RoutePatternSeparator)segment.Parts[j - 1]).Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.EndSegment();
|
||||
}
|
||||
|
||||
// Generate the query string from the remaining values
|
||||
var wroteFirst = false;
|
||||
foreach (var kvp in acceptedValues)
|
||||
{
|
||||
if (_defaults != null && _defaults.ContainsKey(kvp.Key))
|
||||
{
|
||||
// This value is a 'filter' we don't need to put it in the query string.
|
||||
continue;
|
||||
}
|
||||
|
||||
var values = kvp.Value as IEnumerable;
|
||||
if (values != null && !(values is string))
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
wroteFirst |= AddParameterToContext(context, kvp.Key, value, wroteFirst);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wroteFirst |= AddParameterToContext(context, kvp.Key, kvp.Value, wroteFirst);
|
||||
}
|
||||
}
|
||||
return context.ToString();
|
||||
}
|
||||
|
||||
private bool AddParameterToContext(UriBuildingContext context, string key, object value, bool wroteFirst)
|
||||
{
|
||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (!string.IsNullOrEmpty(converted))
|
||||
{
|
||||
context.Writer.Write(wroteFirst ? '&' : '?');
|
||||
_urlEncoder.Encode(context.Writer, key);
|
||||
context.Writer.Write('=');
|
||||
_urlEncoder.Encode(context.Writer, converted);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two objects for equality as parts of a case-insensitive path.
|
||||
/// </summary>
|
||||
/// <param name="a">An object to compare.</param>
|
||||
/// <param name="b">An object to compare.</param>
|
||||
/// <returns>True if the object are equal, otherwise false.</returns>
|
||||
public static bool RoutePartsEqual(object a, object b)
|
||||
{
|
||||
var sa = a as string;
|
||||
var sb = b as string;
|
||||
|
||||
if (sa != null && sb != null)
|
||||
{
|
||||
// For strings do a case-insensitive comparison
|
||||
return string.Equals(sa, sb, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a != null && b != null)
|
||||
{
|
||||
// Explicitly call .Equals() in case it is overridden in the type
|
||||
return a.Equals(b);
|
||||
}
|
||||
else
|
||||
{
|
||||
// At least one of them is null. Return true if they both are
|
||||
return a == b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsRoutePartNonEmpty(object routePart)
|
||||
{
|
||||
var routePartString = routePart as string;
|
||||
if (routePartString == null)
|
||||
{
|
||||
return routePart != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return routePartString.Length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
private struct TemplateBindingContext
|
||||
{
|
||||
private readonly DispatcherValueCollection _defaults;
|
||||
private readonly DispatcherValueCollection _acceptedValues;
|
||||
|
||||
public TemplateBindingContext(DispatcherValueCollection defaults)
|
||||
{
|
||||
_defaults = defaults;
|
||||
|
||||
_acceptedValues = new DispatcherValueCollection();
|
||||
}
|
||||
|
||||
public DispatcherValueCollection AcceptedValues
|
||||
{
|
||||
get { return _acceptedValues; }
|
||||
}
|
||||
|
||||
public void Accept(string key, object value)
|
||||
{
|
||||
if (!_acceptedValues.ContainsKey(key))
|
||||
{
|
||||
_acceptedValues.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void AcceptDefault(string key)
|
||||
{
|
||||
Debug.Assert(!_acceptedValues.ContainsKey(key));
|
||||
|
||||
object value;
|
||||
if (_defaults != null && _defaults.TryGetValue(key, out value))
|
||||
{
|
||||
_acceptedValues.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool NeedsValue(string key)
|
||||
{
|
||||
return !_acceptedValues.ContainsKey(key);
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format("{{Accepted: '{0}'}}", string.Join(", ", _acceptedValues.Keys));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,96 +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.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RoutePatternBinderFactory
|
||||
{
|
||||
private readonly UrlEncoder _encoder;
|
||||
private readonly ObjectPool<UriBuildingContext> _pool;
|
||||
|
||||
public RoutePatternBinderFactory(UrlEncoder encoder, ObjectPoolProvider objectPoolProvider)
|
||||
{
|
||||
if (encoder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoder));
|
||||
}
|
||||
|
||||
if (objectPoolProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectPoolProvider));
|
||||
}
|
||||
|
||||
_encoder = encoder;
|
||||
_pool = objectPoolProvider.Create(new UriBuilderContextPooledObjectPolicy());
|
||||
}
|
||||
|
||||
public RoutePatternBinder Create(string pattern)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
return Create(RoutePattern.Parse(pattern), new DispatcherValueCollection());
|
||||
}
|
||||
|
||||
public RoutePatternBinder Create(string pattern, DispatcherValueCollection defaults)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (defaults == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(defaults));
|
||||
}
|
||||
|
||||
return Create(RoutePattern.Parse(pattern), defaults);
|
||||
}
|
||||
|
||||
public RoutePatternBinder Create(RoutePattern pattern)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
return Create(pattern, new DispatcherValueCollection());
|
||||
}
|
||||
|
||||
public RoutePatternBinder Create(RoutePattern pattern, DispatcherValueCollection defaults)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (defaults == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(defaults));
|
||||
}
|
||||
|
||||
return new RoutePatternBinder(_encoder, _pool, pattern, defaults);
|
||||
}
|
||||
|
||||
private class UriBuilderContextPooledObjectPolicy : IPooledObjectPolicy<UriBuildingContext>
|
||||
{
|
||||
public UriBuildingContext Create()
|
||||
{
|
||||
return new UriBuildingContext();
|
||||
}
|
||||
|
||||
public bool Return(UriBuildingContext obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,125 +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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RoutePatternEndpoint : Endpoint, IRoutePatternEndpoint
|
||||
{
|
||||
public RoutePatternEndpoint(string pattern, RequestDelegate requestDelegate, params object[] metadata)
|
||||
: this(pattern, (object)null, (string)null, requestDelegate, null, metadata)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutePatternEndpoint(string pattern, Func<RequestDelegate, RequestDelegate> delegateFactory, params object[] metadata)
|
||||
: this(pattern, (object)null, (string)null, delegateFactory, null, metadata)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutePatternEndpoint(string pattern, object values, RequestDelegate requestDelegate, params object[] metadata)
|
||||
: this(pattern, values, null, requestDelegate, null, metadata)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutePatternEndpoint(string pattern, object values, Func<RequestDelegate, RequestDelegate> delegateFactory, params object[] metadata)
|
||||
: this(pattern, values, null, delegateFactory, null, metadata)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutePatternEndpoint(string pattern, object values, RequestDelegate requestDelegate, string displayName, params object[] metadata)
|
||||
: this(pattern, values, null, requestDelegate, displayName, metadata)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutePatternEndpoint(string pattern, object values, Func<RequestDelegate, RequestDelegate> delegateFactory, string displayName, params object[] metadata)
|
||||
: this(pattern, values, null, delegateFactory, displayName, metadata)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutePatternEndpoint(string pattern, object values, string httpMethod, RequestDelegate requestDelegate, params object[] metadata)
|
||||
: this(pattern, values, httpMethod, requestDelegate, null, metadata)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutePatternEndpoint(string pattern, object values, string httpMethod, Func<RequestDelegate, RequestDelegate> delegateFactory, params object[] metadata)
|
||||
: this(pattern, values, httpMethod, delegateFactory, null, metadata)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutePatternEndpoint(
|
||||
string pattern,
|
||||
object values,
|
||||
string httpMethod,
|
||||
RequestDelegate requestDelegate,
|
||||
string displayName,
|
||||
params object[] metadata)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (requestDelegate == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requestDelegate));
|
||||
}
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
Pattern = pattern;
|
||||
Values = new DispatcherValueCollection(values);
|
||||
HttpMethod = httpMethod;
|
||||
HandlerFactory = (next) => requestDelegate;
|
||||
DisplayName = displayName;
|
||||
Metadata = new MetadataCollection(metadata);
|
||||
}
|
||||
|
||||
public RoutePatternEndpoint(
|
||||
string pattern,
|
||||
object values,
|
||||
string httpMethod,
|
||||
Func<RequestDelegate, RequestDelegate> delegateFactory,
|
||||
string displayName,
|
||||
params object[] metadata)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
if (delegateFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(delegateFactory));
|
||||
}
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
Pattern = pattern;
|
||||
Values = new DispatcherValueCollection(values);
|
||||
HttpMethod = httpMethod;
|
||||
HandlerFactory = delegateFactory;
|
||||
DisplayName = displayName;
|
||||
Metadata = new MetadataCollection(metadata);
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
|
||||
public string HttpMethod { get; }
|
||||
|
||||
public override MetadataCollection Metadata { get; }
|
||||
|
||||
public Func<RequestDelegate, RequestDelegate> HandlerFactory { get; }
|
||||
|
||||
public string Pattern { get; }
|
||||
|
||||
public DispatcherValueCollection Values { get; }
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче