diff --git a/.vsts-pipelines/builds/ci-internal.yml b/.vsts-pipelines/builds/ci-internal.yml index dc7b8a3cb9..c2c5336fd0 100644 --- a/.vsts-pipelines/builds/ci-internal.yml +++ b/.vsts-pipelines/builds/ci-internal.yml @@ -7,7 +7,7 @@ resources: - repository: buildtools type: git name: aspnet-BuildTools - ref: refs/heads/release/2.2 + ref: refs/heads/master phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/.vsts-pipelines/builds/ci-public.yml b/.vsts-pipelines/builds/ci-public.yml index f5087d9c30..507c89b025 100644 --- a/.vsts-pipelines/builds/ci-public.yml +++ b/.vsts-pipelines/builds/ci-public.yml @@ -9,7 +9,7 @@ resources: type: github endpoint: DotNet-Bot GitHub Connection name: aspnet/BuildTools - ref: refs/heads/release/2.2 + ref: refs/heads/master phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs b/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs index 2814badc32..954a143b0e 100644 --- a/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs +++ b/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs @@ -3,7 +3,7 @@ using System.Text; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Internal; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; @@ -47,4 +47,4 @@ namespace Benchmarks app.UseEndpoint(); } } -} \ No newline at end of file +} diff --git a/benchmarkapps/Benchmarks/benchmarks.json b/benchmarkapps/Benchmarks/benchmarks.json index 1779a80c8c..d69ee7afce 100644 --- a/benchmarkapps/Benchmarks/benchmarks.json +++ b/benchmarkapps/Benchmarks/benchmarks.json @@ -8,7 +8,7 @@ }, "Source": { "Repository": "https://github.com/aspnet/routing.git", - "BranchOrCommit": "release/2.2", + "BranchOrCommit": "master", "Project": "benchmarkapps/Benchmarks/Benchmarks.csproj" }, "Port": 8080 diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherFindCandidateSetSmallEntryCountBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherFindCandidateSetSmallEntryCountBenchmark.cs index 993dabc419..96b938825c 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherFindCandidateSetSmallEntryCountBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherFindCandidateSetSmallEntryCountBenchmark.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing.Matching private TrivialMatcher _baseline; private DfaMatcher _dfa; - private EndpointFeature _feature; + private IEndpointFeature _feature; [GlobalSetup] public void Setup() diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/RouteValueDictionaryBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/RouteValueDictionaryBenchmark.cs deleted file mode 100644 index 522d8d3ed4..0000000000 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/RouteValueDictionaryBenchmark.cs +++ /dev/null @@ -1,182 +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 BenchmarkDotNet.Attributes; - -namespace Microsoft.AspNetCore.Routing -{ - public class RouteValueDictionaryBenchmark - { - private RouteValueDictionary _arrayValues; - private RouteValueDictionary _propertyValues; - - // We modify the route value dictionaries in many of these benchmarks. - [IterationSetup] - public void Setup() - { - _arrayValues = new RouteValueDictionary() - { - { "action", "Index" }, - { "controller", "Home" }, - { "id", "17" }, - }; - _propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" }); - } - - [Benchmark] - public RouteValueDictionary AddSingleItem() - { - var dictionary = new RouteValueDictionary - { - { "action", "Index" } - }; - return dictionary; - } - - [Benchmark] - public RouteValueDictionary AddThreeItems() - { - var dictionary = new RouteValueDictionary - { - { "action", "Index" }, - { "controller", "Home" }, - { "id", "15" } - }; - return dictionary; - } - - [Benchmark] - public RouteValueDictionary ConditionalAdd_ContainsKeyAdd() - { - var dictionary = _arrayValues; - - if (!dictionary.ContainsKey("action")) - { - dictionary.Add("action", "Index"); - } - - if (!dictionary.ContainsKey("controller")) - { - dictionary.Add("controller", "Home"); - } - - if (!dictionary.ContainsKey("area")) - { - dictionary.Add("area", "Admin"); - } - - return dictionary; - } - - [Benchmark] - public RouteValueDictionary ConditionalAdd_TryAdd() - { - var dictionary = _arrayValues; - - dictionary.TryAdd("action", "Index"); - dictionary.TryAdd("controller", "Home"); - dictionary.TryAdd("area", "Admin"); - - return dictionary; - } - - [Benchmark] - public RouteValueDictionary ForEachThreeItems_Array() - { - var dictionary = _arrayValues; - foreach (var kvp in dictionary) - { - GC.KeepAlive(kvp.Value); - } - return dictionary; - } - - [Benchmark] - public RouteValueDictionary ForEachThreeItems_Properties() - { - var dictionary = _propertyValues; - foreach (var kvp in dictionary) - { - GC.KeepAlive(kvp.Value); - } - return dictionary; - } - - [Benchmark] - public RouteValueDictionary GetThreeItems_Array() - { - var dictionary = _arrayValues; - GC.KeepAlive(dictionary["action"]); - GC.KeepAlive(dictionary["controller"]); - GC.KeepAlive(dictionary["id"]); - return dictionary; - } - - [Benchmark] - public RouteValueDictionary GetThreeItems_Properties() - { - var dictionary = _propertyValues; - GC.KeepAlive(dictionary["action"]); - GC.KeepAlive(dictionary["controller"]); - GC.KeepAlive(dictionary["id"]); - return dictionary; - } - - [Benchmark] - public RouteValueDictionary SetSingleItem() - { - var dictionary = new RouteValueDictionary - { - ["action"] = "Index" - }; - return dictionary; - } - - [Benchmark] - public RouteValueDictionary SetExistingItem() - { - var dictionary = _arrayValues; - dictionary["action"] = "About"; - return dictionary; - } - - [Benchmark] - public RouteValueDictionary SetThreeItems() - { - var dictionary = new RouteValueDictionary - { - ["action"] = "Index", - ["controller"] = "Home", - ["id"] = "15" - }; - return dictionary; - } - - [Benchmark] - public RouteValueDictionary TryGetValueThreeItems_Array() - { - var dictionary = _arrayValues; - dictionary.TryGetValue("action", out var action); - dictionary.TryGetValue("controller", out var controller); - dictionary.TryGetValue("id", out var id); - GC.KeepAlive(action); - GC.KeepAlive(controller); - GC.KeepAlive(id); - return dictionary; - } - - [Benchmark] - public RouteValueDictionary TryGetValueThreeItems_Properties() - { - var dictionary = _propertyValues; - dictionary.TryGetValue("action", out var action); - dictionary.TryGetValue("controller", out var controller); - dictionary.TryGetValue("id", out var id); - GC.KeepAlive(action); - GC.KeepAlive(controller); - GC.KeepAlive(id); - return dictionary; - } - } -} \ No newline at end of file diff --git a/build/dependencies.props b/build/dependencies.props index 678786db8f..f66bc953d0 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,38 +4,38 @@ 0.10.13 - 2.2.0-preview1-20180911.1 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 + 3.0.0-alpha1-20180911.2 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 + 3.0.0-alpha1-10454 2.0.9 2.1.3 2.2.0-preview2-26905-02 15.6.1 - 4.7.49 + 4.9.0 2.0.3 11.0.2 4.3.0 diff --git a/build/repo.props b/build/repo.props index 9747864f3e..71840e75d1 100644 --- a/build/repo.props +++ b/build/repo.props @@ -4,7 +4,6 @@ Internal.AspNetCore.Universe.Lineup - 2.2.0-* https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 7124f37441..a817c10d28 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180911.1 -commithash:ddfecdfc6e8e4859db5a0daea578070b862aac65 +version:3.0.0-alpha1-20180911.2 +commithash:2a2b7dbea1b247930c41da497f4ea0b2bb756818 diff --git a/korebuild.json b/korebuild.json index d217d06e3e..8a276a7f35 100644 --- a/korebuild.json +++ b/korebuild.json @@ -1,4 +1,4 @@ { - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.2/tools/korebuild.schema.json", - "channel": "release/2.2" + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", + "channel": "master" } diff --git a/samples/RoutingSample.Web/HelloExtension/EndpointDataSourceBuilderExtensions.cs b/samples/RoutingSample.Web/HelloExtension/EndpointDataSourceBuilderExtensions.cs new file mode 100644 index 0000000000..3d1f53673a --- /dev/null +++ b/samples/RoutingSample.Web/HelloExtension/EndpointDataSourceBuilderExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Builder +{ + public static class EndpointDataSourceBuilderExtensions + { + public static EndpointBuilder MapHello(this EndpointDataSourceBuilder builder, string template, string greeter) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + var pipeline = builder.CreateApplicationBuilder() + .UseHello(greeter) + .Build(); + + return builder.MapEndpoint( + pipeline, + template, + "Hello"); + } + } +} diff --git a/samples/RoutingSample.Web/HelloExtension/HelloAppBuilderExtensions.cs b/samples/RoutingSample.Web/HelloExtension/HelloAppBuilderExtensions.cs new file mode 100644 index 0000000000..e1a587d5ca --- /dev/null +++ b/samples/RoutingSample.Web/HelloExtension/HelloAppBuilderExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Options; +using RoutingSample.Web.HelloExtension; + +namespace Microsoft.AspNetCore.Builder +{ + public static class HelloAppBuilderExtensions + { + public static IApplicationBuilder UseHello(this IApplicationBuilder app, string greeter) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + return app.UseMiddleware(Options.Create(new HelloOptions + { + Greeter = greeter + })); + } + } +} diff --git a/samples/RoutingSample.Web/HelloExtension/HelloMiddleware.cs b/samples/RoutingSample.Web/HelloExtension/HelloMiddleware.cs new file mode 100644 index 0000000000..6a4587e50c --- /dev/null +++ b/samples/RoutingSample.Web/HelloExtension/HelloMiddleware.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace RoutingSample.Web.HelloExtension +{ + public class HelloMiddleware + { + private readonly RequestDelegate _next; + private readonly HelloOptions _helloOptions; + private readonly byte[] _helloPayload; + + public HelloMiddleware(RequestDelegate next, IOptions helloOptions) + { + _next = next; + _helloOptions = helloOptions.Value; + + var payload = new List(); + payload.AddRange(Encoding.UTF8.GetBytes("Hello")); + if (!string.IsNullOrEmpty(_helloOptions.Greeter)) + { + payload.Add((byte)' '); + payload.AddRange(Encoding.UTF8.GetBytes(_helloOptions.Greeter)); + } + _helloPayload = payload.ToArray(); + } + + public Task InvokeAsync(HttpContext context) + { + var response = context.Response; + var payloadLength = _helloPayload.Length; + response.StatusCode = 200; + response.ContentType = "text/plain"; + response.ContentLength = payloadLength; + return response.Body.WriteAsync(_helloPayload, 0, payloadLength); + } + } +} diff --git a/samples/RoutingSample.Web/HelloExtension/HelloOptions.cs b/samples/RoutingSample.Web/HelloExtension/HelloOptions.cs new file mode 100644 index 0000000000..49f8b5c5df --- /dev/null +++ b/samples/RoutingSample.Web/HelloExtension/HelloOptions.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace RoutingSample.Web.HelloExtension +{ + public class HelloOptions + { + public string Greeter { get; set; } + } +} diff --git a/samples/RoutingSample.Web/RoutingSample.Web.csproj b/samples/RoutingSample.Web/RoutingSample.Web.csproj index d8cc8e8283..4c62c17f60 100644 --- a/samples/RoutingSample.Web/RoutingSample.Web.csproj +++ b/samples/RoutingSample.Web/RoutingSample.Web.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.2 diff --git a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs index d600b905a9..e8928b8200 100644 --- a/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs +++ b/samples/RoutingSample.Web/UseEndpointRoutingStartup.cs @@ -5,12 +5,10 @@ using System; using System.IO; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Internal; -using Microsoft.AspNetCore.Routing.Matching; -using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -19,7 +17,7 @@ namespace RoutingSample.Web public class UseEndpointRoutingStartup { private static readonly byte[] _homePayload = Encoding.UTF8.GetBytes("Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext"); - private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!"); + private static readonly byte[] _plainTextPayload = Encoding.UTF8.GetBytes("Plain text!"); public void ConfigureServices(IServiceCollection services) { @@ -29,114 +27,102 @@ namespace RoutingSample.Web { options.ConstraintMap.Add("endsWith", typeof(EndsWithStringRouteConstraint)); }); - - var endpointDataSource = new DefaultEndpointDataSource(new[] - { - new RouteEndpoint((httpContext) => - { - var response = httpContext.Response; - var payloadLength = _homePayload.Length; - response.StatusCode = 200; - response.ContentType = "text/plain"; - response.ContentLength = payloadLength; - return response.Body.WriteAsync(_homePayload, 0, payloadLength); - }, - RoutePatternFactory.Parse("/"), - 0, - EndpointMetadataCollection.Empty, - "Home"), - new RouteEndpoint((httpContext) => - { - var response = httpContext.Response; - var payloadLength = _helloWorldPayload.Length; - response.StatusCode = 200; - response.ContentType = "text/plain"; - response.ContentLength = payloadLength; - return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength); - }, - RoutePatternFactory.Parse("/plaintext"), - 0, - EndpointMetadataCollection.Empty, - "Plaintext"), - new RouteEndpoint((httpContext) => - { - var response = httpContext.Response; - response.StatusCode = 200; - response.ContentType = "text/plain"; - return response.WriteAsync("WithConstraints"); - }, - RoutePatternFactory.Parse("/withconstraints/{id:endsWith(_001)}"), - 0, - EndpointMetadataCollection.Empty, - "withconstraints"), - new RouteEndpoint((httpContext) => - { - var response = httpContext.Response; - response.StatusCode = 200; - response.ContentType = "text/plain"; - return response.WriteAsync("withoptionalconstraints"); - }, - RoutePatternFactory.Parse("/withoptionalconstraints/{id:endsWith(_001)?}"), - 0, - EndpointMetadataCollection.Empty, - "withoptionalconstraints"), - new RouteEndpoint((httpContext) => - { - using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true)) - { - var graphWriter = httpContext.RequestServices.GetRequiredService(); - var dataSource = httpContext.RequestServices.GetRequiredService(); - graphWriter.Write(dataSource, writer); - } - - return Task.CompletedTask; - }, - RoutePatternFactory.Parse("/graph"), - 0, - new EndpointMetadataCollection(new HttpMethodMetadata(new[]{ "GET", })), - "DFA Graph"), - new RouteEndpoint((httpContext) => - { - var linkGenerator = httpContext.RequestServices.GetRequiredService(); - - var response = httpContext.Response; - response.StatusCode = 200; - response.ContentType = "text/plain"; - return response.WriteAsync( - "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { })); - }, - RoutePatternFactory.Parse("/WithSingleAsteriskCatchAll/{*path}"), - 0, - new EndpointMetadataCollection( - new RouteValuesAddressMetadata( - routeName: "WithSingleAsteriskCatchAll", - requiredValues: new RouteValueDictionary())), - "WithSingleAsteriskCatchAll"), - new RouteEndpoint((httpContext) => - { - var linkGenerator = httpContext.RequestServices.GetRequiredService(); - - var response = httpContext.Response; - response.StatusCode = 200; - response.ContentType = "text/plain"; - return response.WriteAsync( - "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { })); - }, - RoutePatternFactory.Parse("/WithDoubleAsteriskCatchAll/{**path}"), - 0, - new EndpointMetadataCollection( - new RouteValuesAddressMetadata( - routeName: "WithDoubleAsteriskCatchAll", - requiredValues: new RouteValueDictionary())), - "WithDoubleAsteriskCatchAll"), - }); - - services.TryAddEnumerable(ServiceDescriptor.Singleton(endpointDataSource)); } - public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app) + public void Configure(IApplicationBuilder app) { - app.UseEndpointRouting(); + app.UseEndpointRouting(builder => + { + builder.MapHello("/helloworld", "World"); + + builder.MapEndpoint( + (httpContext) => + { + var response = httpContext.Response; + var payloadLength = _homePayload.Length; + response.StatusCode = 200; + response.ContentType = "text/plain"; + response.ContentLength = payloadLength; + return response.Body.WriteAsync(_homePayload, 0, payloadLength); + }, + "/", + "Home"); + builder.MapEndpoint( + (httpContext) => + { + var response = httpContext.Response; + var payloadLength = _plainTextPayload.Length; + response.StatusCode = 200; + response.ContentType = "text/plain"; + response.ContentLength = payloadLength; + return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength); + }, + "/plaintext", + "Plaintext"); + builder.MapEndpoint( + (httpContext) => + { + var response = httpContext.Response; + response.StatusCode = 200; + response.ContentType = "text/plain"; + return response.WriteAsync("WithConstraints"); + }, + "/withconstraints/{id:endsWith(_001)}", + "withconstraints"); + builder.MapEndpoint( + (httpContext) => + { + var response = httpContext.Response; + response.StatusCode = 200; + response.ContentType = "text/plain"; + return response.WriteAsync("withoptionalconstraints"); + }, + "/withoptionalconstraints/{id:endsWith(_001)?}", + "withoptionalconstraints"); + builder.MapEndpoint( + (httpContext) => + { + using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true)) + { + var graphWriter = httpContext.RequestServices.GetRequiredService(); + var dataSource = httpContext.RequestServices.GetRequiredService(); + graphWriter.Write(dataSource, writer); + } + + return Task.CompletedTask; + }, + "/graph", + "DFA Graph", + new HttpMethodMetadata(new[] { "GET", })); + builder.MapEndpoint( + (httpContext) => + { + var linkGenerator = httpContext.RequestServices.GetRequiredService(); + + var response = httpContext.Response; + response.StatusCode = 200; + response.ContentType = "text/plain"; + return response.WriteAsync( + "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { })); + }, + "/WithSingleAsteriskCatchAll/{*path}", + "WithSingleAsteriskCatchAll", + new RouteValuesAddressMetadata(routeName: "WithSingleAsteriskCatchAll", requiredValues: new RouteValueDictionary())); + builder.MapEndpoint( + (httpContext) => + { + var linkGenerator = httpContext.RequestServices.GetRequiredService(); + + var response = httpContext.Response; + response.StatusCode = 200; + response.ContentType = "text/plain"; + return response.WriteAsync( + "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { })); + }, + "/WithDoubleAsteriskCatchAll/{**path}", + "WithDoubleAsteriskCatchAll", + new RouteValuesAddressMetadata(routeName: "WithDoubleAsteriskCatchAll", requiredValues: new RouteValueDictionary())); + }); // Imagine some more stuff here... diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/Endpoint.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/Endpoint.cs deleted file mode 100644 index 16202b45f5..0000000000 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/Endpoint.cs +++ /dev/null @@ -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. - -namespace Microsoft.AspNetCore.Http -{ - /// - /// Respresents a logical endpoint in an application. - /// - public class Endpoint - { - /// - /// Creates a new instance of . - /// - /// The delegate used to process requests for the endpoint. - /// - /// The endpoint . May be null. - /// - /// - /// The informational display name of the endpoint. May be null. - /// - public Endpoint( - RequestDelegate requestDelegate, - EndpointMetadataCollection metadata, - string displayName) - { - // All are allowed to be null - RequestDelegate = requestDelegate; - Metadata = metadata ?? EndpointMetadataCollection.Empty; - DisplayName = displayName; - } - - /// - /// Gets the informational display name of this endpoint. - /// - public string DisplayName { get; } - - /// - /// Gets the collection of metadata associated with this endpoint. - /// - public EndpointMetadataCollection Metadata { get; } - - /// - /// Gets the delegate used to process requests for the endpoint. - /// - public RequestDelegate RequestDelegate { get; } - - public override string ToString() => DisplayName ?? base.ToString(); - } -} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/EndpointMetadataCollection.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/EndpointMetadataCollection.cs deleted file mode 100644 index a792fff295..0000000000 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/EndpointMetadataCollection.cs +++ /dev/null @@ -1,201 +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.Linq; -using System.Runtime.CompilerServices; - -namespace Microsoft.AspNetCore.Http -{ - /// - /// A collection of arbitrary metadata associated with an endpoint. - /// - /// - /// instances contain a list of metadata items - /// of arbitrary types. The metadata items are stored as an ordered collection with - /// items arranged in ascending order of precedence. - /// - public sealed class EndpointMetadataCollection : IReadOnlyList - { - /// - /// An empty . - /// - public static readonly EndpointMetadataCollection Empty = new EndpointMetadataCollection(Array.Empty()); - - private readonly object[] _items; - private readonly ConcurrentDictionary _cache; - - /// - /// Creates a new . - /// - /// The metadata items. - public EndpointMetadataCollection(IEnumerable items) - { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } - - _items = items.ToArray(); - _cache = new ConcurrentDictionary(); - } - - /// - /// Creates a new . - /// - /// The metadata items. - public EndpointMetadataCollection(params object[] items) - : this((IEnumerable)items) - { - } - - /// - /// Gets the item at . - /// - /// The index of the item to retrieve. - /// The item at . - public object this[int index] => _items[index]; - - /// - /// Gets the count of metadata items. - /// - public int Count => _items.Length; - - /// - /// Gets the most significant metadata item of type . - /// - /// The type of metadata to retrieve. - /// - /// The most significant metadata of type or null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T GetMetadata() where T : class - { - if (_cache.TryGetValue(typeof(T), out var result)) - { - var length = result.Length; - return length > 0 ? (T)result[length - 1] : default; - } - - return GetMetadataSlow(); - } - - private T GetMetadataSlow() where T : class - { - var array = GetOrderedMetadataSlow(); - var length = array.Length; - return length > 0 ? array[length - 1] : default; - } - - /// - /// Gets the metadata items of type in ascending - /// order of precedence. - /// - /// The type of metadata. - /// A sequence of metadata items of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IEnumerable GetOrderedMetadata() where T : class - { - if (_cache.TryGetValue(typeof(T), out var result)) - { - return (T[])result; - } - - return GetOrderedMetadataSlow(); - } - - private T[] GetOrderedMetadataSlow() where T : class - { - var items = new List(); - for (var i = 0; i < _items.Length; i++) - { - if (_items[i] is T item) - { - items.Add(item); - } - } - - var array = items.ToArray(); - _cache.TryAdd(typeof(T), array); - return array; - } - - /// - /// Gets an of all metadata items. - /// - /// An of all metadata items. - public Enumerator GetEnumerator() => new Enumerator(this); - - /// - /// Gets an of all metadata items. - /// - /// An of all metadata items. - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - /// Gets an of all metadata items. - /// - /// An of all metadata items. - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - /// Enumerates the elements of an . - /// - public struct Enumerator : IEnumerator - { - // Intentionally not readonly to prevent defensive struct copies - private object[] _items; - private int _index; - - internal Enumerator(EndpointMetadataCollection collection) - { - _items = collection._items; - _index = 0; - Current = null; - } - - /// - /// Gets the element at the current position of the enumerator - /// - public object Current { get; private set; } - - /// - /// Releases all resources used by the . - /// - public void Dispose() - { - } - - /// - /// Advances the enumerator to the next element of the . - /// - /// - /// true if the enumerator was successfully advanced to the next element; - /// false if the enumerator has passed the end of the collection. - /// - public bool MoveNext() - { - if (_index < _items.Length) - { - Current = _items[_index++]; - return true; - } - - Current = null; - return false; - } - - /// - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// - public void Reset() - { - _index = 0; - Current = null; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/IEndpointFeature.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/IEndpointFeature.cs deleted file mode 100644 index ff0762bb20..0000000000 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/IEndpointFeature.cs +++ /dev/null @@ -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.Http.Features -{ - /// - /// A feature interface for endpoint routing. Use - /// to access an instance associated with the current request. - /// - public interface IEndpointFeature - { - /// - /// Gets or sets the selected for the current - /// request. - /// - Endpoint Endpoint { get; set; } - } -} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/IRouteValuesFeature.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/IRouteValuesFeature.cs deleted file mode 100644 index 3ab74b6bc7..0000000000 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/IRouteValuesFeature.cs +++ /dev/null @@ -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.Routing; - -namespace Microsoft.AspNetCore.Http.Features -{ - public interface IRouteValuesFeature - { - /// - /// Gets or sets the associated with the currrent - /// request. - /// - RouteValueDictionary RouteValues { get; set; } - } -} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/AssemblyInfo.cs index fc39ed7ef2..fc9a32648f 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/AssemblyInfo.cs @@ -2,6 +2,15 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; + +[assembly: TypeForwardedTo(typeof(IEndpointFeature))] +[assembly: TypeForwardedTo(typeof(IRouteValuesFeature))] +[assembly: TypeForwardedTo(typeof(Endpoint))] +[assembly: TypeForwardedTo(typeof(EndpointMetadataCollection))] +[assembly: TypeForwardedTo(typeof(RouteValueDictionary))] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/Resources.Designer.cs deleted file mode 100644 index 8fdf6b8715..0000000000 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/Resources.Designer.cs +++ /dev/null @@ -1,58 +0,0 @@ -// -namespace Microsoft.AspNetCore.Routing.Abstractions -{ - using System.Globalization; - using System.Reflection; - using System.Resources; - - internal static class Resources - { - private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.AspNetCore.Routing.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly); - - /// - /// An element with the key '{0}' already exists in the {1}. - /// - internal static string RouteValueDictionary_DuplicateKey - { - get => GetString("RouteValueDictionary_DuplicateKey"); - } - - /// - /// An element with the key '{0}' already exists in the {1}. - /// - internal static string FormatRouteValueDictionary_DuplicateKey(object p0, object p1) - => string.Format(CultureInfo.CurrentCulture, GetString("RouteValueDictionary_DuplicateKey"), p0, p1); - - /// - /// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons. - /// - internal static string RouteValueDictionary_DuplicatePropertyName - { - get => GetString("RouteValueDictionary_DuplicatePropertyName"); - } - - /// - /// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons. - /// - internal static string FormatRouteValueDictionary_DuplicatePropertyName(object p0, object p1, object p2, object p3) - => string.Format(CultureInfo.CurrentCulture, GetString("RouteValueDictionary_DuplicatePropertyName"), p0, p1, p2, p3); - - private static string GetString(string name, params string[] formatterNames) - { - var value = _resourceManager.GetString(name); - - System.Diagnostics.Debug.Assert(value != null); - - if (formatterNames != null) - { - for (var i = 0; i < formatterNames.Length; i++) - { - value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); - } - } - - return value; - } - } -} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/Resources.resx b/src/Microsoft.AspNetCore.Routing.Abstractions/Resources.resx deleted file mode 100644 index 40e651af14..0000000000 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/Resources.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - An element with the key '{0}' already exists in the {1}. - - - The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons. - - \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs deleted file mode 100644 index 837e466d28..0000000000 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs +++ /dev/null @@ -1,674 +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 System.Runtime.CompilerServices; -using Microsoft.AspNetCore.Routing.Abstractions; -using Microsoft.Extensions.Internal; - -namespace Microsoft.AspNetCore.Routing -{ - /// - /// An type for route values. - /// - public class RouteValueDictionary : IDictionary, IReadOnlyDictionary - { - // 4 is a good default capacity here because that leaves enough space for area/controller/action/id - private const int DefaultCapacity = 4; - - internal KeyValuePair[] _arrayStorage; - internal PropertyStorage _propertyStorage; - private int _count; - - /// - /// Creates a new from the provided array. - /// The new instance will take ownership of the array, and may mutate it. - /// - /// The items array. - /// A new . - public static RouteValueDictionary FromArray(KeyValuePair[] items) - { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } - - // We need to compress the array by removing non-contiguous items. We - // typically have a very small number of items to process. We don't need - // to preserve order. - var start = 0; - var end = items.Length - 1; - - // We walk forwards from the beginning of the array and fill in 'null' slots. - // We walk backwards from the end of the array end move items in non-null' slots - // into whatever start is pointing to. O(n) - while (start <= end) - { - if (items[start].Key != null) - { - start++; - } - else if (items[end].Key != null) - { - // Swap this item into start and advance - items[start] = items[end]; - items[end] = default; - start++; - end--; - } - else - { - // Both null, we need to hold on 'start' since we - // still need to fill it with something. - end--; - } - } - - return new RouteValueDictionary() - { - _arrayStorage = items, - _count = start, - }; - } - - /// - /// Creates an empty . - /// - public RouteValueDictionary() - { - _arrayStorage = Array.Empty>(); - } - - /// - /// Creates a initialized with the specified . - /// - /// An object to initialize the dictionary. The value can be of type - /// or - /// or an object with public properties as key-value pairs. - /// - /// - /// If the value is a dictionary or other of , - /// 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. - /// - public RouteValueDictionary(object values) - : this() - { - if (values is RouteValueDictionary dictionary) - { - if (dictionary._propertyStorage != null) - { - // PropertyStorage is immutable so we can just copy it. - _propertyStorage = dictionary._propertyStorage; - _count = dictionary._count; - return; - } - - var other = dictionary._arrayStorage; - var storage = new KeyValuePair[other.Length]; - if (dictionary._count != 0) - { - Array.Copy(other, 0, storage, 0, dictionary._count); - } - - _arrayStorage = storage; - _count = dictionary._count; - return; - } - - if (values is IEnumerable> keyValueEnumerable) - { - foreach (var kvp in keyValueEnumerable) - { - Add(kvp.Key, kvp.Value); - } - - return; - } - - if (values is IEnumerable> stringValueEnumerable) - { - foreach (var kvp in stringValueEnumerable) - { - Add(kvp.Key, kvp.Value); - } - - return; - } - - if (values != null) - { - var storage = new PropertyStorage(values); - _propertyStorage = storage; - _count = storage.Properties.Length; - return; - } - } - - /// - public object this[string key] - { - get - { - if (key == null) - { - ThrowArgumentNullExceptionForKey(); - } - - object value; - TryGetValue(key, out value); - return value; - } - - set - { - if (key == null) - { - ThrowArgumentNullExceptionForKey(); - } - - // We're calling this here for the side-effect of converting from properties - // to array. We need to create the array even if we just set an existing value since - // property storage is immutable. - EnsureCapacity(_count); - - var index = FindIndex(key); - if (index < 0) - { - EnsureCapacity(_count + 1); - _arrayStorage[_count++] = new KeyValuePair(key, value); - } - else - { - _arrayStorage[index] = new KeyValuePair(key, value); - } - } - } - - /// - /// Gets the comparer for this dictionary. - /// - /// - /// This will always be a reference to - /// - public IEqualityComparer Comparer => StringComparer.OrdinalIgnoreCase; - - /// - public int Count => _count; - - /// - bool ICollection>.IsReadOnly => false; - - /// - public ICollection Keys - { - get - { - EnsureCapacity(_count); - - var array = _arrayStorage; - var keys = new string[_count]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = array[i].Key; - } - - return keys; - } - } - - IEnumerable IReadOnlyDictionary.Keys => Keys; - - /// - public ICollection Values - { - get - { - EnsureCapacity(_count); - - var array = _arrayStorage; - var values = new object[_count]; - for (var i = 0; i < values.Length; i++) - { - values[i] = array[i].Value; - } - - return values; - } - } - - IEnumerable IReadOnlyDictionary.Values => Values; - - /// - void ICollection>.Add(KeyValuePair item) - { - Add(item.Key, item.Value); - } - - /// - public void Add(string key, object value) - { - if (key == null) - { - ThrowArgumentNullExceptionForKey(); - } - - EnsureCapacity(_count + 1); - - var index = FindIndex(key); - if (index >= 0) - { - var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary)); - throw new ArgumentException(message, nameof(key)); - } - - _arrayStorage[_count] = new KeyValuePair(key, value); - _count++; - } - - /// - public void Clear() - { - if (_count == 0) - { - return; - } - - if (_propertyStorage != null) - { - _arrayStorage = Array.Empty>(); - _propertyStorage = null; - _count = 0; - return; - } - - Array.Clear(_arrayStorage, 0, _count); - _count = 0; - } - - /// - bool ICollection>.Contains(KeyValuePair item) - { - return TryGetValue(item.Key, out var value) && EqualityComparer.Default.Equals(value, item.Value); - } - - /// - public bool ContainsKey(string key) - { - if (key == null) - { - ThrowArgumentNullExceptionForKey(); - } - - return TryGetValue(key, out var _); - } - - /// - void ICollection>.CopyTo( - KeyValuePair[] 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 (Count == 0) - { - return; - } - - EnsureCapacity(Count); - - var storage = _arrayStorage; - Array.Copy(storage, 0, array, arrayIndex, _count); - } - - /// - public Enumerator GetEnumerator() - { - return new Enumerator(this); - } - - /// - IEnumerator> IEnumerable>.GetEnumerator() - { - return GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - bool ICollection>.Remove(KeyValuePair item) - { - if (Count == 0) - { - return false; - } - - EnsureCapacity(Count); - - var index = FindIndex(item.Key); - var array = _arrayStorage; - if (index >= 0 && EqualityComparer.Default.Equals(array[index].Value, item.Value)) - { - Array.Copy(array, index + 1, array, index, _count - index); - _count--; - array[_count] = default; - return true; - } - - return false; - } - - /// - public bool Remove(string key) - { - if (key == null) - { - ThrowArgumentNullExceptionForKey(); - } - - if (Count == 0) - { - return false; - } - - EnsureCapacity(Count); - - var index = FindIndex(key); - if (index >= 0) - { - _count--; - var array = _arrayStorage; - Array.Copy(array, index + 1, array, index, _count - index); - array[_count] = default; - - return true; - } - - return false; - } - - /// - /// Attempts to the add the provided and to the dictionary. - /// - /// The key. - /// The value. - /// Returns true if the value was added. Returns false if the key was already present. - public bool TryAdd(string key, object value) - { - if (key == null) - { - ThrowArgumentNullExceptionForKey(); - } - - // Since this is an attempt to write to the dictionary, just make it an array if it isn't. If the code - // path we're on event tries to write to the dictionary, it will likely get 'upgraded' at some point, - // so we do it here to keep the code size and complexity down. - EnsureCapacity(Count); - - var index = FindIndex(key); - if (index >= 0) - { - return false; - } - - EnsureCapacity(Count + 1); - _arrayStorage[Count] = new KeyValuePair(key, value); - _count++; - return true; - } - - /// - public bool TryGetValue(string key, out object value) - { - if (key == null) - { - ThrowArgumentNullExceptionForKey(); - } - - if (_propertyStorage == null) - { - return TryFindItem(key, out value); - } - - return TryGetValueSlow(key, out value); - } - - private bool TryGetValueSlow(string key, out object value) - { - if (_propertyStorage != null) - { - var storage = _propertyStorage; - for (var i = 0; i < storage.Properties.Length; i++) - { - if (string.Equals(storage.Properties[i].Name, key, StringComparison.OrdinalIgnoreCase)) - { - value = storage.Properties[i].GetValue(storage.Value); - return true; - } - } - } - - value = default; - return false; - } - - private static void ThrowArgumentNullExceptionForKey() - { - throw new ArgumentNullException("key"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EnsureCapacity(int capacity) - { - if (_propertyStorage != null || _arrayStorage.Length < capacity) - { - EnsureCapacitySlow(capacity); - } - } - - private void EnsureCapacitySlow(int capacity) - { - if (_propertyStorage != null) - { - var storage = _propertyStorage; - - // If we're converting from properties, it's likely due to an 'add' to make sure we have at least - // the default amount of space. - capacity = Math.Max(DefaultCapacity, Math.Max(storage.Properties.Length, capacity)); - var array = new KeyValuePair[capacity]; - - for (var i = 0; i < storage.Properties.Length; i++) - { - var property = storage.Properties[i]; - array[i] = new KeyValuePair(property.Name, property.GetValue(storage.Value)); - } - - _arrayStorage = array; - _propertyStorage = null; - return; - } - - if (_arrayStorage.Length < capacity) - { - capacity = _arrayStorage.Length == 0 ? DefaultCapacity : _arrayStorage.Length * 2; - var array = new KeyValuePair[capacity]; - if (_count > 0) - { - Array.Copy(_arrayStorage, 0, array, 0, _count); - } - - _arrayStorage = array; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int FindIndex(string key) - { - // Generally the bounds checking here will be elided by the JIT because this will be called - // on the same code path as EnsureCapacity. - var array = _arrayStorage; - var count = _count; - - for (var i = 0; i < count; i++) - { - if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase)) - { - return i; - } - } - - return -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryFindItem(string key, out object value) - { - var array = _arrayStorage; - var count = _count; - - // Elide bounds check for indexing. - if ((uint)count <= (uint)array.Length) - { - for (var i = 0; i < count; i++) - { - if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase)) - { - value = array[i].Value; - return true; - } - } - } - - value = null; - return false; - } - - public struct Enumerator : IEnumerator> - { - private readonly RouteValueDictionary _dictionary; - private int _index; - - public Enumerator(RouteValueDictionary dictionary) - { - if (dictionary == null) - { - throw new ArgumentNullException(); - } - - _dictionary = dictionary; - - Current = default; - _index = 0; - } - - public KeyValuePair Current { get; private set; } - - object IEnumerator.Current => Current; - - public void Dispose() - { - } - - // Similar to the design of List.Enumerator - Split into fast path and slow path for inlining friendliness - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - var dictionary = _dictionary; - - // The uncommon case is that the propertyStorage is in use - if (dictionary._propertyStorage == null && ((uint)_index < (uint)dictionary._count)) - { - Current = dictionary._arrayStorage[_index]; - _index++; - return true; - } - - return MoveNextRare(); - } - - private bool MoveNextRare() - { - var dictionary = _dictionary; - if (dictionary._propertyStorage != null && ((uint)_index < (uint)dictionary._count)) - { - var storage = dictionary._propertyStorage; - var property = storage.Properties[_index]; - Current = new KeyValuePair(property.Name, property.GetValue(storage.Value)); - _index++; - return true; - } - - _index = dictionary._count; - Current = default; - return false; - } - - public void Reset() - { - Current = default; - _index = 0; - } - } - - internal class PropertyStorage - { - private static readonly ConcurrentDictionary _propertyCache = new ConcurrentDictionary(); - - public readonly object Value; - public 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); - } - } - - private static void ValidatePropertyNames(Type type, PropertyHelper[] properties) - { - var names = new Dictionary(StringComparer.OrdinalIgnoreCase); - for (var i = 0; i < properties.Length; i++) - { - var property = properties[i]; - - if (names.TryGetValue(property.Name, out var duplicate)) - { - var message = Resources.FormatRouteValueDictionary_DuplicatePropertyName( - type.FullName, - property.Name, - duplicate.Name, - nameof(RouteValueDictionary)); - throw new InvalidOperationException(message); - } - - names.Add(property.Name, property); - } - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/Internal/EndpointRoutingApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs similarity index 79% rename from src/Microsoft.AspNetCore.Routing/Internal/EndpointRoutingApplicationBuilderExtensions.cs rename to src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs index 281607cfba..f7c14801f3 100644 --- a/src/Microsoft.AspNetCore.Routing/Internal/EndpointRoutingApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/Builder/EndpointRoutingApplicationBuilderExtensions.cs @@ -7,16 +7,28 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.AspNetCore.Internal +namespace Microsoft.AspNetCore.Builder { public static class EndpointRoutingApplicationBuilderExtensions { private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered"; public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder) + { + return builder.UseEndpointRouting(null); + } + + public static IApplicationBuilder UseEndpointRouting(this IApplicationBuilder builder, Action configure) { VerifyRoutingIsRegistered(builder); + if (configure != null) + { + var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)builder.ApplicationServices.GetRequiredService(); + dataSourceBuilder.ApplicationBuilder = builder; + configure(dataSourceBuilder); + } + builder.Properties[EndpointRoutingRegisteredKey] = true; return builder.UseMiddleware(); @@ -50,4 +62,4 @@ namespace Microsoft.AspNetCore.Internal } } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/Builder/MapEndpointEndpointDataSourceBuilderExtensions.cs b/src/Microsoft.AspNetCore.Routing/Builder/MapEndpointEndpointDataSourceBuilderExtensions.cs new file mode 100644 index 0000000000..fca430da9a --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/Builder/MapEndpointEndpointDataSourceBuilderExtensions.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Builder +{ + public static class MapEndpointEndpointDataSourceBuilderExtensions + { + public static RouteEndpointBuilder MapEndpoint( + this EndpointDataSourceBuilder builder, + RequestDelegate requestDelegate, + string pattern, + string displayName) + { + return MapEndpoint(builder, requestDelegate, pattern, displayName, metadata: null); + } + + public static RouteEndpointBuilder MapEndpoint( + this EndpointDataSourceBuilder builder, + RequestDelegate requestDelegate, + RoutePattern pattern, + string displayName) + { + return MapEndpoint(builder, requestDelegate, pattern, displayName, metadata: null); + } + + public static RouteEndpointBuilder MapEndpoint( + this EndpointDataSourceBuilder builder, + RequestDelegate requestDelegate, + string pattern, + string displayName, + params object[] metadata) + { + return MapEndpoint(builder, requestDelegate, RoutePatternFactory.Parse(pattern), displayName, metadata); + } + + public static RouteEndpointBuilder MapEndpoint( + this EndpointDataSourceBuilder builder, + RequestDelegate requestDelegate, + RoutePattern pattern, + string displayName, + params object[] metadata) + { + const int defaultOrder = 0; + + var endpointBuilder = new RouteEndpointBuilder( + requestDelegate, + pattern, + defaultOrder); + endpointBuilder.DisplayName = displayName; + if (metadata != null) + { + foreach (var item in metadata) + { + endpointBuilder.Metadata.Add(item); + } + } + + builder.Endpoints.Add(endpointBuilder); + return endpointBuilder; + } + } +} diff --git a/src/Microsoft.AspNetCore.Routing/BuilderEndpointDataSource.cs b/src/Microsoft.AspNetCore.Routing/BuilderEndpointDataSource.cs new file mode 100644 index 0000000000..d119f80ed2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/BuilderEndpointDataSource.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Routing +{ + internal class BuilderEndpointDataSource : EndpointDataSource + { + private readonly EndpointDataSourceBuilder _builder; + + public BuilderEndpointDataSource(EndpointDataSourceBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + _builder = builder; + } + + public override IChangeToken GetChangeToken() + { + return NullChangeToken.Singleton; + } + + public override IReadOnlyList Endpoints => _builder.Endpoints.Select(b => b.Build()).ToArray(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSourceBuilder.cs b/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSourceBuilder.cs new file mode 100644 index 0000000000..922a744cb7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/DefaultEndpointDataSourceBuilder.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; + +namespace Microsoft.AspNetCore.Routing +{ + internal class DefaultEndpointDataSourceBuilder : EndpointDataSourceBuilder + { + public IApplicationBuilder ApplicationBuilder { get; set; } + + public override ICollection Endpoints { get; } = new List(); + + public override IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs index 6037f8fa45..4db8249104 100644 --- a/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Routing/DependencyInjection/RoutingServiceCollectionExtensions.cs @@ -59,6 +59,12 @@ namespace Microsoft.Extensions.DependencyInjection return new CompositeEndpointDataSource(options.Value.DataSources); }); + // + // Endpoint Infrastructure + // + services.TryAddSingleton(); + services.TryAddSingleton(); + // // Default matcher implementation // diff --git a/src/Microsoft.AspNetCore.Routing/EndpointBuilder.cs b/src/Microsoft.AspNetCore.Routing/EndpointBuilder.cs new file mode 100644 index 0000000000..27d64f1625 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/EndpointBuilder.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Routing +{ + public abstract class EndpointBuilder + { + public RequestDelegate RequestDelegate { get; set; } + + public string DisplayName { get; set; } + + public IList Metadata { get; } = new List(); + + public abstract Endpoint Build(); + } +} diff --git a/src/Microsoft.AspNetCore.Routing/EndpointDataSourceBuilder.cs b/src/Microsoft.AspNetCore.Routing/EndpointDataSourceBuilder.cs new file mode 100644 index 0000000000..eea8b9afb2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/EndpointDataSourceBuilder.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; + +namespace Microsoft.AspNetCore.Routing +{ + public abstract class EndpointDataSourceBuilder + { + public abstract ICollection Endpoints { get; } + + public abstract IApplicationBuilder CreateApplicationBuilder(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/RouteEndpointBuilder.cs b/src/Microsoft.AspNetCore.Routing/RouteEndpointBuilder.cs new file mode 100644 index 0000000000..a45d71d391 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/RouteEndpointBuilder.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Routing +{ + public sealed class RouteEndpointBuilder : EndpointBuilder + { + public RoutePattern RoutePattern { get; set; } + + public int Order { get; set; } + + public RouteEndpointBuilder( + RequestDelegate requestDelegate, + RoutePattern routePattern, + int order) + { + RequestDelegate = requestDelegate; + RoutePattern = routePattern; + Order = order; + } + + public override Endpoint Build() + { + var matcherEndpoint = new RouteEndpoint( + RequestDelegate, + RoutePattern, + Order, + new EndpointMetadataCollection(Metadata), + DisplayName); + + return matcherEndpoint; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/EndpointMetadataCollectionTests.cs b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/EndpointMetadataCollectionTests.cs deleted file mode 100644 index 9f37f3ebec..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/EndpointMetadataCollectionTests.cs +++ /dev/null @@ -1,142 +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.Text; -using Microsoft.AspNetCore.Http; -using Xunit; - -namespace Microsoft.AspNetCore.Routing -{ - public class EndpointMetadataCollectionTests - { - [Fact] - public void Constructor_Enumeration_ContainsValues() - { - // Arrange & Act - var metadata = new EndpointMetadataCollection(new List - { - 1, - 2, - 3, - }); - - // Assert - Assert.Equal(3, metadata.Count); - - Assert.Collection(metadata, - value => Assert.Equal(1, value), - value => Assert.Equal(2, value), - value => Assert.Equal(3, value)); - } - - [Fact] - public void Constructor_ParamsArray_ContainsValues() - { - // Arrange & Act - var metadata = new EndpointMetadataCollection(1, 2, 3); - - // Assert - Assert.Equal(3, metadata.Count); - - Assert.Collection(metadata, - value => Assert.Equal(1, value), - value => Assert.Equal(2, value), - value => Assert.Equal(3, value)); - } - - [Fact] - public void GetMetadata_Match_ReturnsLastMatchingEntry() - { - // Arrange - var items = new object[] - { - new Metadata1(), - new Metadata2(), - new Metadata3(), - }; - - var metadata = new EndpointMetadataCollection(items); - - // Act - var result = metadata.GetMetadata(); - - // Assert - Assert.Same(items[1], result); - } - - [Fact] - public void GetMetadata_NoMatch_ReturnsNull() - { - // Arrange - var items = new object[] - { - new Metadata3(), - new Metadata3(), - new Metadata3(), - }; - - var metadata = new EndpointMetadataCollection(items); - - // Act - var result = metadata.GetMetadata(); - - // Assert - Assert.Null(result); - } - - [Fact] - public void GetOrderedMetadata_Match_ReturnsItemsInAscendingOrder() - { - // Arrange - var items = new object[] - { - new Metadata1(), - new Metadata2(), - new Metadata3(), - }; - - var metadata = new EndpointMetadataCollection(items); - - // Act - var result = metadata.GetOrderedMetadata(); - - // Assert - Assert.Collection( - result, - i => Assert.Same(items[0], i), - i => Assert.Same(items[1], i)); - } - - [Fact] - public void GetOrderedMetadata_NoMatch_ReturnsEmpty() - { - // Arrange - var items = new object[] - { - new Metadata3(), - new Metadata3(), - new Metadata3(), - }; - - var metadata = new EndpointMetadataCollection(items); - - // Act - var result = metadata.GetOrderedMetadata(); - - // Assert - Assert.Empty(result); - } - - private interface IMetadata1 { } - private interface IMetadata2 { } - private interface IMetadata3 { } - private interface IMetadata4 { } - private interface IMetadata5 { } - private class Metadata1 : IMetadata1, IMetadata4, IMetadata5 { } - private class Metadata2 : IMetadata2, IMetadata5 { } - private class Metadata3 : IMetadata3 { } - - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs deleted file mode 100644 index 1d4ec848af..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs +++ /dev/null @@ -1,1834 +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.Testing; -using Xunit; - -namespace Microsoft.AspNetCore.Routing.Tests -{ - public class RouteValueDictionaryTests - { - [Fact] - public void DefaultCtor_UsesEmptyStorage() - { - // Arrange - // Act - var dict = new RouteValueDictionary(); - - // Assert - Assert.Empty(dict); - Assert.Empty(dict._arrayStorage); - Assert.Null(dict._propertyStorage); - } - - [Fact] - public void CreateFromNull_UsesEmptyStorage() - { - // Arrange - // Act - var dict = new RouteValueDictionary(null); - - // Assert - Assert.Empty(dict); - Assert.Empty(dict._arrayStorage); - Assert.Null(dict._propertyStorage); - } - - [Fact] - public void CreateFromRouteValueDictionary_WithArrayStorage_CopiesStorage() - { - // Arrange - var other = new RouteValueDictionary() - { - { "1", 1 } - }; - - // Act - var dict = new RouteValueDictionary(other); - - // Assert - Assert.Equal(other, dict); - - var storage = Assert.IsType[]>(dict._arrayStorage); - var otherStorage = Assert.IsType[]>(other._arrayStorage); - Assert.NotSame(otherStorage, storage); - } - - [Fact] - public void CreateFromRouteValueDictionary_WithPropertyStorage_CopiesStorage() - { - // Arrange - var other = new RouteValueDictionary(new { key = "value" }); - - // Act - var dict = new RouteValueDictionary(other); - - // Assert - Assert.Equal(other, dict); - - var storage = dict._propertyStorage; - var otherStorage = other._propertyStorage; - Assert.Same(otherStorage, storage); - } - - public static IEnumerable IEnumerableKeyValuePairData - { - get - { - var routeValues = new[] - { - new KeyValuePair("Name", "James"), - new KeyValuePair("Age", 30), - new KeyValuePair("Address", new Address() { City = "Redmond", State = "WA" }) - }; - - yield return new object[] { routeValues.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) }; - - yield return new object[] { routeValues.ToList() }; - - yield return new object[] { routeValues }; - } - } - - public static IEnumerable IEnumerableStringValuePairData - { - get - { - var routeValues = new[] - { - new KeyValuePair("First Name", "James"), - new KeyValuePair("Last Name", "Henrik"), - new KeyValuePair("Middle Name", "Bob") - }; - - yield return new object[] { routeValues.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) }; - - yield return new object[] { routeValues.ToList() }; - - yield return new object[] { routeValues }; - } - } - - [Theory] - [MemberData(nameof(IEnumerableKeyValuePairData))] - public void CreateFromIEnumerableKeyValuePair_CopiesValues(object values) - { - // Arrange & Act - var dict = new RouteValueDictionary(values); - - // Assert - Assert.IsType[]>(dict._arrayStorage); - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("Address", kvp.Key); - var address = Assert.IsType
(kvp.Value); - Assert.Equal("Redmond", address.City); - Assert.Equal("WA", address.State); - }, - kvp => { Assert.Equal("Age", kvp.Key); Assert.Equal(30, kvp.Value); }, - kvp => { Assert.Equal("Name", kvp.Key); Assert.Equal("James", kvp.Value); }); - } - - [Theory] - [MemberData(nameof(IEnumerableStringValuePairData))] - public void CreateFromIEnumerableStringValuePair_CopiesValues(object values) - { - // Arrange & Act - var dict = new RouteValueDictionary(values); - - // Assert - Assert.IsType[]>(dict._arrayStorage); - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("First Name", kvp.Key); Assert.Equal("James", kvp.Value); }, - kvp => { Assert.Equal("Last Name", kvp.Key); Assert.Equal("Henrik", kvp.Value); }, - kvp => { Assert.Equal("Middle Name", kvp.Key); Assert.Equal("Bob", kvp.Value); }); - } - - [Fact] - public void CreateFromIEnumerableKeyValuePair_ThrowsExceptionForDuplicateKey() - { - // Arrange - var values = new List>() - { - new KeyValuePair("name", "Billy"), - new KeyValuePair("Name", "Joey"), - }; - - // Act & Assert - ExceptionAssert.ThrowsArgument( - () => new RouteValueDictionary(values), - "key", - $"An element with the key 'Name' already exists in the {nameof(RouteValueDictionary)}."); - } - - [Fact] - public void CreateFromIEnumerableStringValuePair_ThrowsExceptionForDuplicateKey() - { - // Arrange - var values = new List>() - { - new KeyValuePair("name", "Billy"), - new KeyValuePair("Name", "Joey"), - }; - - // Act & Assert - ExceptionAssert.ThrowsArgument( - () => new RouteValueDictionary(values), - "key", - $"An element with the key 'Name' already exists in the {nameof(RouteValueDictionary)}."); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromAnonymousType() - { - // Arrange - var obj = new { cool = "beans", awesome = 123 }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.NotNull(dict._propertyStorage); - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("awesome", kvp.Key); Assert.Equal(123, kvp.Value); }, - kvp => { Assert.Equal("cool", kvp.Key); Assert.Equal("beans", kvp.Value); }); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType() - { - // Arrange - var obj = new RegularType() { CoolnessFactor = 73 }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.NotNull(dict._propertyStorage); - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("CoolnessFactor", kvp.Key); - Assert.Equal(73, kvp.Value); - }, - kvp => - { - Assert.Equal("IsAwesome", kvp.Key); - var value = Assert.IsType(kvp.Value); - Assert.False(value); - }); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_PublicOnly() - { - // Arrange - var obj = new Visibility() { IsPublic = true, ItsInternalDealWithIt = 5 }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.NotNull(dict._propertyStorage); - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("IsPublic", kvp.Key); - var value = Assert.IsType(kvp.Value); - Assert.True(value); - }); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresStatic() - { - // Arrange - var obj = new StaticProperty(); - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.NotNull(dict._propertyStorage); - Assert.Empty(dict); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresSetOnly() - { - // Arrange - var obj = new SetterOnly() { CoolSetOnly = false }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.NotNull(dict._propertyStorage); - Assert.Empty(dict); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_IncludesInherited() - { - // Arrange - var obj = new Derived() { TotallySweetProperty = true, DerivedProperty = false }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.NotNull(dict._propertyStorage); - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("DerivedProperty", kvp.Key); - var value = Assert.IsType(kvp.Value); - Assert.False(value); - }, - kvp => - { - Assert.Equal("TotallySweetProperty", kvp.Key); - var value = Assert.IsType(kvp.Value); - Assert.True(value); - }); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_WithHiddenProperty() - { - // Arrange - var obj = new DerivedHiddenProperty() { DerivedProperty = 5 }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.NotNull(dict._propertyStorage); - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("DerivedProperty", kvp.Key); Assert.Equal(5, kvp.Value); }); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_WithIndexerProperty() - { - // Arrange - var obj = new IndexerProperty(); - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.NotNull(dict._propertyStorage); - Assert.Empty(dict); - } - - [Fact] - public void CreateFromObject_MixedCaseThrows() - { - // Arrange - var obj = new { controller = "Home", Controller = "Home" }; - - var message = - $"The type '{obj.GetType().FullName}' defines properties 'controller' and 'Controller' which differ " + - $"only by casing. This is not supported by {nameof(RouteValueDictionary)} which uses " + - $"case-insensitive comparisons."; - - // Act & Assert - var exception = Assert.Throws(() => - { - var dictionary = new RouteValueDictionary(obj); - }); - - // Ignoring case to make sure we're not testing reflection's ordering. - Assert.Equal(message, exception.Message, ignoreCase: true); - } - - // Our comparer is hardcoded to be OrdinalIgnoreCase no matter what. - [Fact] - public void Comparer_IsOrdinalIgnoreCase() - { - // Arrange - // Act - var dict = new RouteValueDictionary(); - - // Assert - Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); - } - - // Our comparer is hardcoded to be IsReadOnly==false no matter what. - [Fact] - public void IsReadOnly_False() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var result = ((ICollection>)dict).IsReadOnly; - - // Assert - Assert.False(result); - } - - [Fact] - public void IndexGet_EmptyStringIsAllowed() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var value = dict[""]; - - // Assert - Assert.Null(value); - } - - [Fact] - public void IndexGet_EmptyStorage_ReturnsNull() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var value = dict["key"]; - - // Assert - Assert.Null(value); - } - - [Fact] - public void IndexGet_PropertyStorage_NoMatch_ReturnsNull() - { - // Arrange - var dict = new RouteValueDictionary(new { age = 30 }); - - // Act - var value = dict["key"]; - - // Assert - Assert.Null(value); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void IndexGet_PropertyStorage_Match_ReturnsValue() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - var value = dict["key"]; - - // Assert - Assert.Equal("value", value); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void IndexGet_PropertyStorage_MatchIgnoreCase_ReturnsValue() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - var value = dict["kEy"]; - - // Assert - Assert.Equal("value", value); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void IndexGet_ArrayStorage_NoMatch_ReturnsNull() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "age", 30 }, - }; - - // Act - var value = dict["key"]; - - // Assert - Assert.Null(value); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void IndexGet_ListStorage_Match_ReturnsValue() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var value = dict["key"]; - - // Assert - Assert.Equal("value", value); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void IndexGet_ListStorage_MatchIgnoreCase_ReturnsValue() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var value = dict["kEy"]; - - // Assert - Assert.Equal("value", value); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void IndexSet_EmptyStringIsAllowed() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - dict[""] = "foo"; - - // Assert - Assert.Equal("foo", dict[""]); - } - - [Fact] - public void IndexSet_EmptyStorage_UpgradesToList() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - dict["key"] = "value"; - - // Assert - Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void IndexSet_PropertyStorage_NoMatch_AddsValue() - { - // Arrange - var dict = new RouteValueDictionary(new { age = 30 }); - - // Act - dict["key"] = "value"; - - // Assert - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, - kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void IndexSet_PropertyStorage_Match_SetsValue() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - dict["key"] = "value"; - - // Assert - Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void IndexSet_PropertyStorage_MatchIgnoreCase_SetsValue() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - dict["kEy"] = "value"; - - // Assert - Assert.Collection(dict, kvp => { Assert.Equal("kEy", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void IndexSet_ListStorage_NoMatch_AddsValue() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "age", 30 }, - }; - - // Act - dict["key"] = "value"; - - // Assert - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, - kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void IndexSet_ListStorage_Match_SetsValue() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - dict["key"] = "value"; - - // Assert - Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void IndexSet_ListStorage_MatchIgnoreCase_SetsValue() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - dict["key"] = "value"; - - // Assert - Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Count_EmptyStorage() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var count = dict.Count; - - // Assert - Assert.Equal(0, count); - } - - [Fact] - public void Count_PropertyStorage() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value", }); - - // Act - var count = dict.Count; - - // Assert - Assert.Equal(1, count); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void Count_ListStorage() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var count = dict.Count; - - // Assert - Assert.Equal(1, count); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Keys_EmptyStorage() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var keys = dict.Keys; - - // Assert - Assert.Empty(keys); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Keys_PropertyStorage() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value", }); - - // Act - var keys = dict.Keys; - - // Assert - Assert.Equal(new[] { "key" }, keys); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Keys_ListStorage() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var keys = dict.Keys; - - // Assert - Assert.Equal(new[] { "key" }, keys); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Values_EmptyStorage() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var values = dict.Values; - - // Assert - Assert.Empty(values); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Values_PropertyStorage() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value", }); - - // Act - var values = dict.Values; - - // Assert - Assert.Equal(new object[] { "value" }, values); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Values_ListStorage() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var values = dict.Values; - - // Assert - Assert.Equal(new object[] { "value" }, values); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Add_EmptyStorage() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - dict.Add("key", "value"); - - // Assert - Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Add_EmptyStringIsAllowed() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - dict.Add("", "foo"); - - // Assert - Assert.Equal("foo", dict[""]); - } - - [Fact] - public void Add_PropertyStorage() - { - // Arrange - var dict = new RouteValueDictionary(new { age = 30 }); - - // Act - dict.Add("key", "value"); - - // Assert - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, - kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - - // The upgrade from property -> array should make space for at least 4 entries - Assert.Collection( - dict._arrayStorage, - kvp => Assert.Equal(new KeyValuePair("age", 30), kvp), - kvp => Assert.Equal(new KeyValuePair("key", "value"), kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp)); - } - - [Fact] - public void Add_ListStorage() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "age", 30 }, - }; - - // Act - dict.Add("key", "value"); - - // Assert - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, - kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Add_DuplicateKey() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var message = $"An element with the key 'key' already exists in the {nameof(RouteValueDictionary)}"; - - // Act & Assert - ExceptionAssert.ThrowsArgument(() => dict.Add("key", "value2"), "key", message); - - // Assert - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Add_DuplicateKey_CaseInsensitive() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var message = $"An element with the key 'kEy' already exists in the {nameof(RouteValueDictionary)}"; - - // Act & Assert - ExceptionAssert.ThrowsArgument(() => dict.Add("kEy", "value2"), "key", message); - - // Assert - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Add_KeyValuePair() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "age", 30 }, - }; - - // Act - ((ICollection>)dict).Add(new KeyValuePair("key", "value")); - - // Assert - Assert.Collection( - dict.OrderBy(kvp => kvp.Key), - kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, - kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Clear_EmptyStorage() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - dict.Clear(); - - // Assert - Assert.Empty(dict); - } - - [Fact] - public void Clear_PropertyStorage_AlreadyEmpty() - { - // Arrange - var dict = new RouteValueDictionary(new { }); - - // Act - dict.Clear(); - - // Assert - Assert.Empty(dict); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void Clear_PropertyStorage() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - dict.Clear(); - - // Assert - Assert.Empty(dict); - Assert.Null(dict._propertyStorage); - } - - [Fact] - public void Clear_ListStorage() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - dict.Clear(); - - // Assert - Assert.Empty(dict); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Contains_KeyValuePair_True() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var input = new KeyValuePair("key", "value"); - - // Act - var result = ((ICollection>)dict).Contains(input); - - // Assert - Assert.True(result); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Contains_KeyValuePair_True_CaseInsensitive() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var input = new KeyValuePair("KEY", "value"); - - // Act - var result = ((ICollection>)dict).Contains(input); - - // Assert - Assert.True(result); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Contains_KeyValuePair_False() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var input = new KeyValuePair("other", "value"); - - // Act - var result = ((ICollection>)dict).Contains(input); - - // Assert - Assert.False(result); - Assert.IsType[]>(dict._arrayStorage); - } - - // Value comparisons use the default equality comparer. - [Fact] - public void Contains_KeyValuePair_False_ValueComparisonIsDefault() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var input = new KeyValuePair("key", "valUE"); - - // Act - var result = ((ICollection>)dict).Contains(input); - - // Assert - Assert.False(result); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void ContainsKey_EmptyStorage() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var result = dict.ContainsKey("key"); - - // Assert - Assert.False(result); - } - - [Fact] - public void ContainsKey_EmptyStringIsAllowed() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var result = dict.ContainsKey(""); - - // Assert - Assert.False(result); - } - - [Fact] - public void ContainsKey_PropertyStorage_False() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - var result = dict.ContainsKey("other"); - - // Assert - Assert.False(result); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void ContainsKey_PropertyStorage_True() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - var result = dict.ContainsKey("key"); - - // Assert - Assert.True(result); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void ContainsKey_PropertyStorage_True_CaseInsensitive() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - var result = dict.ContainsKey("kEy"); - - // Assert - Assert.True(result); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void ContainsKey_ListStorage_False() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var result = dict.ContainsKey("other"); - - // Assert - Assert.False(result); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void ContainsKey_ListStorage_True() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var result = dict.ContainsKey("key"); - - // Assert - Assert.True(result); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void ContainsKey_ListStorage_True_CaseInsensitive() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var result = dict.ContainsKey("kEy"); - - // Assert - Assert.True(result); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void CopyTo() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var array = new KeyValuePair[2]; - - // Act - ((ICollection>)dict).CopyTo(array, 1); - - // Assert - Assert.Equal( - new KeyValuePair[] - { - default(KeyValuePair), - new KeyValuePair("key", "value") - }, - array); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Remove_KeyValuePair_True() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var input = new KeyValuePair("key", "value"); - - // Act - var result = ((ICollection>)dict).Remove(input); - - // Assert - Assert.True(result); - Assert.Empty(dict); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Remove_KeyValuePair_True_CaseInsensitive() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var input = new KeyValuePair("KEY", "value"); - - // Act - var result = ((ICollection>)dict).Remove(input); - - // Assert - Assert.True(result); - Assert.Empty(dict); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Remove_KeyValuePair_False() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var input = new KeyValuePair("other", "value"); - - // Act - var result = ((ICollection>)dict).Remove(input); - - // Assert - Assert.False(result); - Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - // Value comparisons use the default equality comparer. - [Fact] - public void Remove_KeyValuePair_False_ValueComparisonIsDefault() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - var input = new KeyValuePair("key", "valUE"); - - // Act - var result = ((ICollection>)dict).Remove(input); - - // Assert - Assert.False(result); - Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Remove_EmptyStorage() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var result = dict.Remove("key"); - - // Assert - Assert.False(result); - } - - [Fact] - public void Remove_EmptyStringIsAllowed() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var result = dict.Remove(""); - - // Assert - Assert.False(result); - } - - [Fact] - public void Remove_PropertyStorage_Empty() - { - // Arrange - var dict = new RouteValueDictionary(new { }); - - // Act - var result = dict.Remove("other"); - - // Assert - Assert.False(result); - Assert.Empty(dict); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void Remove_PropertyStorage_False() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - var result = dict.Remove("other"); - - // Assert - Assert.False(result); - Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Remove_PropertyStorage_True() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - var result = dict.Remove("key"); - - // Assert - Assert.True(result); - Assert.Empty(dict); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Remove_PropertyStorage_True_CaseInsensitive() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - var result = dict.Remove("kEy"); - - // Assert - Assert.True(result); - Assert.Empty(dict); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Remove_ListStorage_False() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var result = dict.Remove("other"); - - // Assert - Assert.False(result); - Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Remove_ListStorage_True() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var result = dict.Remove("key"); - - // Assert - Assert.True(result); - Assert.Empty(dict); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void Remove_ListStorage_True_CaseInsensitive() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - var result = dict.Remove("kEy"); - - // Assert - Assert.True(result); - Assert.Empty(dict); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void TryAdd_EmptyStringIsAllowed() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var result = dict.TryAdd("", "foo"); - - // Assert - Assert.True(result); - } - - // We always 'upgrade' if you are trying to write to the dictionary. - [Fact] - public void TryAdd_ConvertsPropertyStorage_ToArrayStorage() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value", }); - - // Act - var result = dict.TryAdd("key", "value"); - - // Assert - Assert.False(result); - Assert.Null(dict._propertyStorage); - Assert.Collection( - dict._arrayStorage, - kvp => Assert.Equal(new KeyValuePair("key", "value"), kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp)); - } - - [Fact] - public void TryAdd_EmptyStorage_CanAdd() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var result = dict.TryAdd("key", "value"); - - // Assert - Assert.True(result); - Assert.Collection( - dict._arrayStorage, - kvp => Assert.Equal(new KeyValuePair("key", "value"), kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp)); - } - - [Fact] - public void TryAdd_ArrayStorage_CanAdd() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key0", "value0" }, - }; - - // Act - var result = dict.TryAdd("key1", "value1"); - - // Assert - Assert.True(result); - Assert.Collection( - dict._arrayStorage, - kvp => Assert.Equal(new KeyValuePair("key0", "value0"), kvp), - kvp => Assert.Equal(new KeyValuePair("key1", "value1"), kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp)); - } - - [Fact] - public void TryAdd_ArrayStorage_CanAddWithResize() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key0", "value0" }, - { "key1", "value1" }, - { "key2", "value2" }, - { "key3", "value3" }, - }; - - // Act - var result = dict.TryAdd("key4", "value4"); - - // Assert - Assert.True(result); - Assert.Collection( - dict._arrayStorage, - kvp => Assert.Equal(new KeyValuePair("key0", "value0"), kvp), - kvp => Assert.Equal(new KeyValuePair("key1", "value1"), kvp), - kvp => Assert.Equal(new KeyValuePair("key2", "value2"), kvp), - kvp => Assert.Equal(new KeyValuePair("key3", "value3"), kvp), - kvp => Assert.Equal(new KeyValuePair("key4", "value4"), kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp)); - } - - [Fact] - public void TryAdd_ArrayStorage_DoesNotAddWhenKeyIsPresent() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key0", "value0" }, - }; - - // Act - var result = dict.TryAdd("key0", "value1"); - - // Assert - Assert.False(result); - Assert.Collection( - dict._arrayStorage, - kvp => Assert.Equal(new KeyValuePair("key0", "value0"), kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp), - kvp => Assert.Equal(default, kvp)); - } - - [Fact] - public void TryGetValue_EmptyStorage() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - object value; - var result = dict.TryGetValue("key", out value); - - // Assert - Assert.False(result); - Assert.Null(value); - } - - [Fact] - public void TryGetValue_EmptyStringIsAllowed() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act - var result = dict.TryGetValue("", out var value); - - // Assert - Assert.False(result); - Assert.Null(value); - } - - [Fact] - public void TryGetValue_PropertyStorage_False() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - object value; - var result = dict.TryGetValue("other", out value); - - // Assert - Assert.False(result); - Assert.Null(value); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void TryGetValue_PropertyStorage_True() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - object value; - var result = dict.TryGetValue("key", out value); - - // Assert - Assert.True(result); - Assert.Equal("value", value); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void TryGetValue_PropertyStorage_True_CaseInsensitive() - { - // Arrange - var dict = new RouteValueDictionary(new { key = "value" }); - - // Act - object value; - var result = dict.TryGetValue("kEy", out value); - - // Assert - Assert.True(result); - Assert.Equal("value", value); - Assert.NotNull(dict._propertyStorage); - } - - [Fact] - public void TryGetValue_ListStorage_False() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - object value; - var result = dict.TryGetValue("other", out value); - - // Assert - Assert.False(result); - Assert.Null(value); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void TryGetValue_ListStorage_True() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - object value; - var result = dict.TryGetValue("key", out value); - - // Assert - Assert.True(result); - Assert.Equal("value", value); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void TryGetValue_ListStorage_True_CaseInsensitive() - { - // Arrange - var dict = new RouteValueDictionary() - { - { "key", "value" }, - }; - - // Act - object value; - var result = dict.TryGetValue("kEy", out value); - - // Assert - Assert.True(result); - Assert.Equal("value", value); - Assert.IsType[]>(dict._arrayStorage); - } - - [Fact] - public void ListStorage_DynamicallyAdjustsCapacity() - { - // Arrange - var dict = new RouteValueDictionary(); - - // Act 1 - dict.Add("key", "value"); - - // Assert 1 - var storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(4, storage.Length); - - // Act 2 - dict.Add("key2", "value2"); - dict.Add("key3", "value3"); - dict.Add("key4", "value4"); - dict.Add("key5", "value5"); - - // Assert 2 - storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(8, storage.Length); - } - - [Fact] - public void ListStorage_RemoveAt_RearrangesInnerArray() - { - // Arrange - var dict = new RouteValueDictionary(); - dict.Add("key", "value"); - dict.Add("key2", "value2"); - dict.Add("key3", "value3"); - - // Assert 1 - var storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(3, dict.Count); - - // Act - dict.Remove("key2"); - - // Assert 2 - storage = Assert.IsType[]>(dict._arrayStorage); - Assert.Equal(2, dict.Count); - Assert.Equal("key", storage[0].Key); - Assert.Equal("value", storage[0].Value); - Assert.Equal("key3", storage[1].Key); - Assert.Equal("value3", storage[1].Value); - } - - [Fact] - public void FromArray_TakesOwnershipOfArray() - { - // Arrange - var array = new KeyValuePair[] - { - new KeyValuePair("a", 0), - new KeyValuePair("b", 1), - new KeyValuePair("c", 2), - }; - - var dictionary = RouteValueDictionary.FromArray(array); - - // Act - modifying the array should modify the dictionary - array[0] = new KeyValuePair("aa", 10); - - // Assert - Assert.Equal(3, dictionary.Count); - Assert.Equal(10, dictionary["aa"]); - } - - [Fact] - public void FromArray_EmptyArray() - { - // Arrange - var array = Array.Empty>(); - - // Act - var dictionary = RouteValueDictionary.FromArray(array); - - // Assert - Assert.Empty(dictionary); - } - - [Fact] - public void FromArray_RemovesGapsInArray() - { - // Arrange - var array = new KeyValuePair[] - { - new KeyValuePair(null, null), - new KeyValuePair("a", 0), - new KeyValuePair(null, null), - new KeyValuePair(null, null), - new KeyValuePair("b", 1), - new KeyValuePair("c", 2), - new KeyValuePair("d", 3), - new KeyValuePair(null, null), - }; - - // Act - calling From should modify the array - var dictionary = RouteValueDictionary.FromArray(array); - - // Assert - Assert.Equal(4, dictionary.Count); - Assert.Equal( - new KeyValuePair[] - { - new KeyValuePair("d", 3), - new KeyValuePair("a", 0), - new KeyValuePair("c", 2), - new KeyValuePair("b", 1), - new KeyValuePair(null, null), - new KeyValuePair(null, null), - new KeyValuePair(null, null), - new KeyValuePair(null, null), - }, - array); - } - - private class RegularType - { - public bool IsAwesome { get; set; } - - public int CoolnessFactor { get; set; } - } - - private class Visibility - { - private string PrivateYo { get; set; } - - internal int ItsInternalDealWithIt { get; set; } - - public bool IsPublic { get; set; } - } - - private class StaticProperty - { - public static bool IsStatic { get; set; } - } - - private class SetterOnly - { - private bool _coolSetOnly; - - public bool CoolSetOnly { set { _coolSetOnly = value; } } - } - - private class Base - { - public bool DerivedProperty { get; set; } - } - - private class Derived : Base - { - public bool TotallySweetProperty { get; set; } - } - - private class DerivedHiddenProperty : Base - { - public new int DerivedProperty { get; set; } - } - - private class IndexerProperty - { - public bool this[string key] - { - get { return false; } - set { } - } - } - - private class Address - { - public string City { get; set; } - - public string State { get; set; } - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs b/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs index 6dd2373efe..db27235e70 100644 --- a/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs +++ b/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests { // Arrange var expectedContentType = "text/plain"; - var expectedContent = "Hello, World!"; + var expectedContent = "Plain text!"; // Act var response = await _client.GetAsync("/plaintext"); @@ -62,6 +62,25 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests Assert.Equal(expectedContent, actualContent); } + [Fact] + public async Task MatchesHelloMiddleware_AndReturnsPlaintext() + { + // Arrange + var expectedContentType = "text/plain"; + var expectedContent = "Hello World"; + + // Act + var response = await _client.GetAsync("/helloworld"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.NotNull(response.Content.Headers.ContentType); + Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType); + var actualContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedContent, actualContent); + } + [Fact] public async Task MatchesEndpoint_WithSuccessfulConstraintMatch() { diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs similarity index 86% rename from test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingBuilderExtensionsTest.cs rename to test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs index 106a47e27b..5e7f1964b5 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder.Internal; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; @@ -15,7 +14,7 @@ using Xunit; namespace Microsoft.AspNetCore.Builder { - public class EndpointRoutingBuilderExtensionsTest + public class EndpointRoutingApplicationBuilderExtensionsTest { [Fact] public void UseEndpointRouting_ServicesNotRegistered_Throws() @@ -140,6 +139,26 @@ namespace Microsoft.AspNetCore.Builder Assert.Null(httpContext.Features.Get()); } + [Fact] + public void UseEndpointRouting_CallWithBuilder_SetsEndpointBuilder() + { + // Arrange + var services = CreateServices(); + + var app = new ApplicationBuilder(services); + + // Act + app.UseEndpointRouting(builder => + { + builder.MapEndpoint(d => null, "/", "Test endpoint"); + }); + + // Assert + var dataSourceBuilder = (DefaultEndpointDataSourceBuilder)services.GetRequiredService(); + var endpointBuilder = Assert.Single(dataSourceBuilder.Endpoints); + Assert.Equal("Test endpoint", endpointBuilder.DisplayName); + } + private IServiceProvider CreateServices(params Endpoint[] endpoints) { var services = new ServiceCollection(); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs new file mode 100644 index 0000000000..5a425c3075 --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; +using Xunit; + +namespace Microsoft.AspNetCore.Builder +{ + public class MapEndpointEndpointDataSourceBuilderExtensionsTest + { + [Fact] + public void MapEndpoint_StringPattern_BuildsEndpoint() + { + // Arrange + var builder = new DefaultEndpointDataSourceBuilder(); + RequestDelegate requestDelegate = (d) => null; + + // Act + var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!"); + + // Assert + Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); + Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); + Assert.Equal("Display name!", endpointBuilder.DisplayName); + Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + } + + [Fact] + public void MapEndpoint_TypedPattern_BuildsEndpoint() + { + // Arrange + var builder = new DefaultEndpointDataSourceBuilder(); + RequestDelegate requestDelegate = (d) => null; + + // Act + var endpointBuilder = builder.MapEndpoint(requestDelegate, RoutePatternFactory.Parse("/"), "Display name!"); + + // Assert + Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); + Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); + Assert.Equal("Display name!", endpointBuilder.DisplayName); + Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + } + + [Fact] + public void MapEndpoint_StringPatternAndMetadata_BuildsEndpoint() + { + // Arrange + var metadata = new object(); + var builder = new DefaultEndpointDataSourceBuilder(); + RequestDelegate requestDelegate = (d) => null; + + // Act + var endpointBuilder = builder.MapEndpoint(requestDelegate, "/", "Display name!", new[] { metadata }); + + // Assert + Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); + Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); + Assert.Equal("Display name!", endpointBuilder.DisplayName); + Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata)); + } + + [Fact] + public void MapEndpoint_TypedPatternAndMetadata_BuildsEndpoint() + { + // Arrange + var metadata = new object(); + var builder = new DefaultEndpointDataSourceBuilder(); + RequestDelegate requestDelegate = (d) => null; + + // Act + var endpointBuilder = builder.MapEndpoint(requestDelegate, RoutePatternFactory.Parse("/"), "Display name!", new[] { metadata }); + + // Assert + Assert.Equal(endpointBuilder, Assert.Single(builder.Endpoints)); + Assert.Equal(requestDelegate, endpointBuilder.RequestDelegate); + Assert.Equal("Display name!", endpointBuilder.DisplayName); + Assert.Equal("/", endpointBuilder.RoutePattern.RawText); + Assert.Equal(metadata, Assert.Single(endpointBuilder.Metadata)); + } + } +} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/EndpointFactory.cs b/test/Microsoft.AspNetCore.Routing.Tests/EndpointFactory.cs index eb23c5daab..d7ffcb16b8 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/EndpointFactory.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/EndpointFactory.cs @@ -1,10 +1,11 @@ // Copyright (c) .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; -using Microsoft.AspNetCore.Routing.Patterns; using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; namespace Microsoft.AspNetCore.Routing { diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherConformanceTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherConformanceTest.cs index 45673bb525..06f4e14a73 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherConformanceTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherConformanceTest.cs @@ -21,7 +21,10 @@ namespace Microsoft.AspNetCore.Routing.Matching httpContext.Request.Path = path; httpContext.RequestServices = CreateServices(); - var feature = new EndpointFeature(); + var feature = new EndpointFeature + { + RouteValues = new RouteValueDictionary() + }; httpContext.Features.Set(feature); httpContext.Features.Set(feature); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointBuilderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointBuilderTest.cs new file mode 100644 index 0000000000..3e93c1d3db --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/MatcherEndpointBuilderTest.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Patterns; +using Xunit; + +namespace Microsoft.AspNetCore.Routing.Matching +{ + public class MatcherEndpointBuilderTest + { + [Fact] + public void Build_AllValuesSet_EndpointCreated() + { + const int defaultOrder = 0; + object metadata = new object(); + RequestDelegate requestDelegate = (d) => null; + + var builder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder) + { + DisplayName = "Display name!", + Metadata = { metadata } + }; + + var endpoint = Assert.IsType(builder.Build()); + Assert.Equal("Display name!", endpoint.DisplayName); + Assert.Equal(defaultOrder, endpoint.Order); + Assert.Equal(requestDelegate, endpoint.RequestDelegate); + Assert.Equal("/", endpoint.RoutePattern.RawText); + Assert.Equal(metadata, Assert.Single(endpoint.Metadata)); + } + } +} diff --git a/version.props b/version.props index 704cac087b..71a78cddd8 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@ - + - 2.2.0 - preview3 + 3.0.0 + alpha1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000