Addresses aspnet/Home#2741
This commit is contained in:
Jass Bagga 2018-01-10 12:53:17 -08:00 коммит произвёл GitHub
Родитель 34c60bc14d
Коммит 93d20ec78c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
167 изменённых файлов: 3287 добавлений и 15112 удалений

Просмотреть файл

@ -2,8 +2,7 @@
"adx-nonshipping": {
"rules": [],
"packages": {
"Microsoft.AspNetCore.Routing.DecisionTree.Sources": {},
"Microsoft.AspNetCore.Routing.UrlMatchingTree.Sources": {}
"Microsoft.AspNetCore.Routing.DecisionTree.Sources": {}
}
},
"Default": {

Просмотреть файл

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

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше