diff --git a/src/Routing/Directory.Build.targets b/src/Routing/Directory.Build.targets
index 78626b773e..7e3f8df92e 100644
--- a/src/Routing/Directory.Build.targets
+++ b/src/Routing/Directory.Build.targets
@@ -1,10 +1,6 @@
- $(MicrosoftNETCoreApp20PackageVersion)
- $(MicrosoftNETCoreApp21PackageVersion)
- $(MicrosoftNETCoreApp22PackageVersion)
+ $(MicrosoftNETCoreAppPackageVersion)
$(NETStandardLibrary20PackageVersion)
-
- 99.9
diff --git a/src/Routing/README.md b/src/Routing/README.md
index b3202c355a..1f66dfdd7f 100644
--- a/src/Routing/README.md
+++ b/src/Routing/README.md
@@ -1,10 +1,8 @@
-ASP.NET Routing
-===
+ASP.NET Routing [Archived]
+===========================
-AppVeyor: [](https://ci.appveyor.com/project/aspnetci/Routing/branch/master)
-
-Travis: [](https://travis-ci.org/aspnet/Routing)
+**This GitHub project has been archived.** Ongoing development on this project can be found in .
Contains routing middleware for routing requests to application logic.
-This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
+This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo.
diff --git a/src/Routing/benchmarkapps/Benchmarks/Benchmarks.csproj b/src/Routing/benchmarkapps/Benchmarks/Benchmarks.csproj
index 3c5753cc64..0db37a19dd 100644
--- a/src/Routing/benchmarkapps/Benchmarks/Benchmarks.csproj
+++ b/src/Routing/benchmarkapps/Benchmarks/Benchmarks.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.2
+ netcoreapp3.0
$(BenchmarksTargetFramework)
true
diff --git a/src/Routing/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs b/src/Routing/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs
index 2814badc32..f6062f0531 100644
--- a/src/Routing/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs
+++ b/src/Routing/benchmarkapps/Benchmarks/StartupUsingEndpointRouting.cs
@@ -3,11 +3,10 @@
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;
-using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Benchmarks
{
@@ -18,8 +17,13 @@ namespace Benchmarks
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
+ }
- var endpointDataSource = new DefaultEndpointDataSource(new[]
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseEndpointRouting(builder =>
+ {
+ var endpointDataSource = new DefaultEndpointDataSource(new[]
{
new RouteEndpoint(
requestDelegate: (httpContext) =>
@@ -37,14 +41,10 @@ namespace Benchmarks
displayName: "Plaintext"),
});
- services.TryAddEnumerable(ServiceDescriptor.Singleton(endpointDataSource));
- }
-
- public void Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder app)
- {
- app.UseEndpointRouting();
+ builder.DataSources.Add(endpointDataSource);
+ });
app.UseEndpoint();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Routing/benchmarkapps/Benchmarks/benchmarks.json b/src/Routing/benchmarkapps/Benchmarks/benchmarks.json
index 1779a80c8c..d69ee7afce 100644
--- a/src/Routing/benchmarkapps/Benchmarks/benchmarks.json
+++ b/src/Routing/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/src/Routing/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj b/src/Routing/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj
index 625a26262a..ebe8f50b50 100644
--- a/src/Routing/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj
+++ b/src/Routing/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.2
+ netcoreapp3.0
Exe
true
true
diff --git a/src/Routing/benchmarks/Microsoft.AspNetCore.Routing.Performance/RouteValueDictionaryBenchmark.cs b/src/Routing/benchmarks/Microsoft.AspNetCore.Routing.Performance/RouteValueDictionaryBenchmark.cs
deleted file mode 100644
index 522d8d3ed4..0000000000
--- a/src/Routing/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/src/Routing/build/dependencies.props b/src/Routing/build/dependencies.props
index 3c93a7647d..15eca73927 100644
--- a/src/Routing/build/dependencies.props
+++ b/src/Routing/build/dependencies.props
@@ -4,43 +4,39 @@
0.10.13
- 2.2.0-preview2-20181011.10
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.2.0-preview3-35496
- 2.0.9
- 2.1.3
- 2.2.0-preview3-27008-03
+ 3.0.0-build-20181114.5
+ 3.0.0-alpha1-10742
+ 3.0.0-preview-181113-11
+ 3.0.0-alpha1-10742
+ 3.0.0-alpha1-10742
+ 3.0.0-alpha1-10742
+ 3.0.0-alpha1-10742
+ 3.0.0-alpha1-10742
+ 3.0.0-alpha1-10742
+ 3.0.0-alpha1-10742
+ 3.0.0-alpha1-10742
+ 3.0.0-alpha1-10742
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview-181113-11
+ 3.0.0-preview1-26907-05
15.6.1
4.10.0
2.0.3
11.0.2
- 4.3.0
- 4.3.0
0.10.0
2.3.1
2.4.0
diff --git a/src/Routing/build/repo.props b/src/Routing/build/repo.props
index 9747864f3e..157a311580 100644
--- a/src/Routing/build/repo.props
+++ b/src/Routing/build/repo.props
@@ -4,14 +4,11 @@
Internal.AspNetCore.Universe.Lineup
- 2.2.0-*
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json
-
-
-
+
diff --git a/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/AuthorizationAppBuilderExtensions.cs b/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/AuthorizationAppBuilderExtensions.cs
new file mode 100644
index 0000000000..1566f8d127
--- /dev/null
+++ b/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/AuthorizationAppBuilderExtensions.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 System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using RoutingSample.Web.AuthorizationMiddleware;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ public static class AuthorizationAppBuilderExtensions
+ {
+ public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware();
+ }
+ }
+}
diff --git a/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/AuthorizationMetadata.cs b/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/AuthorizationMetadata.cs
new file mode 100644
index 0000000000..0e492ddc24
--- /dev/null
+++ b/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/AuthorizationMetadata.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 System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace RoutingSample.Web.AuthorizationMiddleware
+{
+ public class AuthorizationMetadata
+ {
+ public AuthorizationMetadata(IEnumerable roles)
+ {
+ if (roles == null)
+ {
+ throw new ArgumentNullException(nameof(roles));
+ }
+
+ Roles = roles.ToArray();
+ }
+
+ public IReadOnlyList Roles { get; }
+ }
+}
diff --git a/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/AuthorizationMiddleware.cs b/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/AuthorizationMiddleware.cs
new file mode 100644
index 0000000000..594b18c43b
--- /dev/null
+++ b/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/AuthorizationMiddleware.cs
@@ -0,0 +1,59 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
+
+namespace RoutingSample.Web.AuthorizationMiddleware
+{
+ public class AuthorizationMiddleware
+ {
+ private readonly ILogger _logger;
+ private readonly RequestDelegate _next;
+
+ public AuthorizationMiddleware(ILogger logger, RequestDelegate next)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
+
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+
+ _logger = logger;
+ _next = next;
+ }
+
+ public async Task Invoke(HttpContext httpContext)
+ {
+ var endpoint = httpContext.Features.Get()?.Endpoint;
+ if (endpoint != null)
+ {
+ var metadata = endpoint.Metadata.GetMetadata();
+ // Only run authorization if endpoint has metadata
+ if (metadata != null)
+ {
+ if (!httpContext.Request.Query.TryGetValue("x-role", out var role) ||
+ !metadata.Roles.Contains(role.ToString()))
+ {
+ httpContext.Response.StatusCode = 401;
+ httpContext.Response.ContentType = "text/plain";
+ await httpContext.Response.WriteAsync($"Unauthorized access to '{endpoint.DisplayName}'.");
+ return;
+ }
+ }
+ }
+
+ await _next(httpContext);
+ }
+ }
+}
diff --git a/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/EndpointConventionBuilderExtensions .cs b/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/EndpointConventionBuilderExtensions .cs
new file mode 100644
index 0000000000..51a5c9c469
--- /dev/null
+++ b/src/Routing/samples/RoutingSandbox/AuthorizationMiddleware/EndpointConventionBuilderExtensions .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 Microsoft.AspNetCore.Routing;
+using RoutingSample.Web.AuthorizationMiddleware;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ public static class EndpointConventionBuilderExtensions
+ {
+ public static IEndpointConventionBuilder RequireAuthorization(this IEndpointConventionBuilder builder, params string[] roles)
+ {
+ builder.Apply(endpointBuilder => endpointBuilder.Metadata.Add(new AuthorizationMetadata(roles)));
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Routing/samples/RoutingSandbox/HelloExtension/EndpointRouteBuilderExtensions.cs b/src/Routing/samples/RoutingSandbox/HelloExtension/EndpointRouteBuilderExtensions.cs
new file mode 100644
index 0000000000..fc26b50bf8
--- /dev/null
+++ b/src/Routing/samples/RoutingSandbox/HelloExtension/EndpointRouteBuilderExtensions.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 EndpointRouteBuilderExtensions
+ {
+ public static IEndpointConventionBuilder MapHello(this IEndpointRouteBuilder builder, string template, string greeter)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ var pipeline = builder.CreateApplicationBuilder()
+ .UseHello(greeter)
+ .Build();
+
+ return builder.Map(
+ template,
+ "Hello " + greeter,
+ pipeline);
+ }
+ }
+}
diff --git a/src/Routing/samples/RoutingSandbox/HelloExtension/HelloAppBuilderExtensions.cs b/src/Routing/samples/RoutingSandbox/HelloExtension/HelloAppBuilderExtensions.cs
new file mode 100644
index 0000000000..e1a587d5ca
--- /dev/null
+++ b/src/Routing/samples/RoutingSandbox/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/src/Routing/samples/RoutingSandbox/HelloExtension/HelloMiddleware.cs b/src/Routing/samples/RoutingSandbox/HelloExtension/HelloMiddleware.cs
new file mode 100644
index 0000000000..6a4587e50c
--- /dev/null
+++ b/src/Routing/samples/RoutingSandbox/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/src/Routing/samples/RoutingSandbox/HelloExtension/HelloOptions.cs b/src/Routing/samples/RoutingSandbox/HelloExtension/HelloOptions.cs
new file mode 100644
index 0000000000..49f8b5c5df
--- /dev/null
+++ b/src/Routing/samples/RoutingSandbox/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/src/Routing/samples/RoutingSandbox/RoutingSandbox.csproj b/src/Routing/samples/RoutingSandbox/RoutingSandbox.csproj
index 5f01b1bb1e..eaf327a420 100644
--- a/src/Routing/samples/RoutingSandbox/RoutingSandbox.csproj
+++ b/src/Routing/samples/RoutingSandbox/RoutingSandbox.csproj
@@ -1,8 +1,7 @@
-
+
- netcoreapp2.2
- $(TargetFrameworks);net461
+ netcoreapp3.0
diff --git a/src/Routing/samples/RoutingSandbox/UseEndpointRoutingStartup.cs b/src/Routing/samples/RoutingSandbox/UseEndpointRoutingStartup.cs
index bfb345cba9..fdeabd02b9 100644
--- a/src/Routing/samples/RoutingSandbox/UseEndpointRoutingStartup.cs
+++ b/src/Routing/samples/RoutingSandbox/UseEndpointRoutingStartup.cs
@@ -3,11 +3,11 @@
using System;
using System.IO;
+using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.AspNetCore.Routing.Patterns;
@@ -19,65 +19,70 @@ namespace RoutingSandbox
public class UseEndpointRoutingStartup
{
private static readonly byte[] _homePayload = Encoding.UTF8.GetBytes("Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext");
- private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
+ private static readonly byte[] _plainTextPayload = Encoding.UTF8.GetBytes("Plain text!");
public void ConfigureServices(IServiceCollection services)
{
- var endpointDataSource = new DefaultEndpointDataSource(new[]
- {
- new RouteEndpoint((httpContext) =>
- {
- var response = httpContext.Response;
- var payloadLength = _homePayload.Length;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- response.ContentLength = payloadLength;
- return response.Body.WriteAsync(_homePayload, 0, payloadLength);
- },
- RoutePatternFactory.Parse("/"),
- 0,
- EndpointMetadataCollection.Empty,
- "Home"),
- new RouteEndpoint((httpContext) =>
- {
- var response = httpContext.Response;
- var payloadLength = _helloWorldPayload.Length;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- response.ContentLength = payloadLength;
- return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
- },
- RoutePatternFactory.Parse("/plaintext"),
- 0,
- EndpointMetadataCollection.Empty,
- "Plaintext"),
- new RouteEndpoint((httpContext) =>
- {
- using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
- {
- var graphWriter = httpContext.RequestServices.GetRequiredService();
- var dataSource = httpContext.RequestServices.GetRequiredService();
- graphWriter.Write(dataSource, writer);
- }
-
- return Task.CompletedTask;
- },
- RoutePatternFactory.Parse("/graph"),
- 0,
- new EndpointMetadataCollection(new HttpMethodMetadata(new[]{ "GET", })),
- "DFA Graph"),
- });
-
- services.TryAddEnumerable(ServiceDescriptor.Singleton(endpointDataSource));
+ services.AddRouting();
}
public void Configure(IApplicationBuilder app)
{
- app.UseEndpointRouting();
+ app.UseEndpointRouting(builder =>
+ {
+ builder.MapHello("/helloworld", "World");
+
+ builder.MapHello("/helloworld-secret", "Secret World")
+ .RequireAuthorization("swordfish");
+
+ builder.MapGet(
+ "/",
+ (httpContext) =>
+ {
+ var dataSource = httpContext.RequestServices.GetRequiredService();
+
+ var sb = new StringBuilder();
+ sb.AppendLine("Endpoints:");
+ foreach (var endpoint in dataSource.Endpoints.OfType().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
+ {
+ sb.AppendLine($"- {endpoint.RoutePattern.RawText}");
+ }
+
+ var response = httpContext.Response;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ return response.WriteAsync(sb.ToString());
+ });
+ builder.MapGet(
+ "/plaintext",
+ (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);
+ });
+ builder.MapGet(
+ "/graph",
+ "DFA Graph",
+ (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;
+ });
+ });
app.UseStaticFiles();
-
- // Imagine some more stuff here...
+
+ app.UseAuthorization();
app.UseEndpoint();
}
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing.Abstractions/Endpoint.cs b/src/Routing/src/Microsoft.AspNetCore.Routing.Abstractions/Endpoint.cs
deleted file mode 100644
index 16202b45f5..0000000000
--- a/src/Routing/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/Routing/src/Microsoft.AspNetCore.Routing.Abstractions/EndpointMetadataCollection.cs b/src/Routing/src/Microsoft.AspNetCore.Routing.Abstractions/EndpointMetadataCollection.cs
deleted file mode 100644
index 137423a886..0000000000
--- a/src/Routing/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
-
- true
false
- IL_EMIT;$(DefineConstants)
IL_EMIT_SAVE_ASSEMBLIES;$(DefineConstants)
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/ModelEndpointDataSource.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/ModelEndpointDataSource.cs
new file mode 100644
index 0000000000..5614310342
--- /dev/null
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/ModelEndpointDataSource.cs
@@ -0,0 +1,68 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using 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 ModelEndpointDataSource : EndpointDataSource
+ {
+ private List _endpointConventionBuilders;
+
+ public ModelEndpointDataSource()
+ {
+ _endpointConventionBuilders = new List();
+ }
+
+ public IEndpointConventionBuilder AddEndpointModel(EndpointModel endpointModel)
+ {
+ var builder = new EndpointConventionBuilder(endpointModel);
+ _endpointConventionBuilders.Add(builder);
+
+ return builder;
+ }
+
+ public override IChangeToken GetChangeToken()
+ {
+ return NullChangeToken.Singleton;
+ }
+
+ public override IReadOnlyList Endpoints => _endpointConventionBuilders.Select(e => e.Build()).ToArray();
+
+ // for testing
+ internal IEnumerable EndpointModels => _endpointConventionBuilders.Select(b => b.EndpointModel);
+
+ private class EndpointConventionBuilder : IEndpointConventionBuilder
+ {
+ internal EndpointModel EndpointModel { get; }
+
+ private readonly List> _conventions;
+
+ public EndpointConventionBuilder(EndpointModel endpointModel)
+ {
+ EndpointModel = endpointModel;
+ _conventions = new List>();
+ }
+
+ public void Apply(Action convention)
+ {
+ _conventions.Add(convention);
+ }
+
+ public Endpoint Build()
+ {
+ foreach (var convention in _conventions)
+ {
+ convention(EndpointModel);
+ }
+
+ return EndpointModel.Build();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/DefaultRoutePatternTransformer.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/DefaultRoutePatternTransformer.cs
new file mode 100644
index 0000000000..b1f313f7f9
--- /dev/null
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/DefaultRoutePatternTransformer.cs
@@ -0,0 +1,227 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Routing.Patterns
+{
+ internal class DefaultRoutePatternTransformer : RoutePatternTransformer
+ {
+ private readonly ParameterPolicyFactory _policyFactory;
+
+ public DefaultRoutePatternTransformer(ParameterPolicyFactory policyFactory)
+ {
+ if (policyFactory == null)
+ {
+ throw new ArgumentNullException(nameof(policyFactory));
+ }
+
+ _policyFactory = policyFactory;
+ }
+
+ public override RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues)
+ {
+ if (original == null)
+ {
+ throw new ArgumentNullException(nameof(original));
+ }
+
+ return SubstituteRequiredValuesCore(original, new RouteValueDictionary(requiredValues));
+ }
+
+ private RoutePattern SubstituteRequiredValuesCore(RoutePattern original, RouteValueDictionary requiredValues)
+ {
+ // Process each required value in sequence. Bail if we find any rejection criteria. The goal
+ // of rejection is to avoid creating RoutePattern instances that can't *ever* match.
+ //
+ // If we succeed, then we need to create a new RoutePattern with the provided required values.
+ //
+ // Substitution can merge with existing RequiredValues already on the RoutePattern as long
+ // as all of the success criteria are still met at the end.
+ foreach (var kvp in requiredValues)
+ {
+ // There are three possible cases here:
+ // 1. Required value is null-ish
+ // 2. Required value corresponds to a parameter
+ // 3. Required value corresponds to a matching default value
+ //
+ // If none of these are true then we can reject this substitution.
+ RoutePatternParameterPart parameter;
+ if (RouteValueEqualityComparer.Default.Equals(kvp.Value, string.Empty))
+ {
+ // 1. Required value is null-ish - check to make sure that this route doesn't have a
+ // parameter or filter-like default.
+
+ if (original.GetParameter(kvp.Key) != null)
+ {
+ // Fail: we can't 'require' that a parameter be null. In theory this would be possible
+ // for an optional parameter, but that's not really in line with the usage of this feature
+ // so we don't handle it.
+ //
+ // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = "" }
+ return null;
+ }
+ else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
+ !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
+ {
+ // Fail: this route has a non-parameter default that doesn't match.
+ //
+ // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = "" }
+ return null;
+ }
+
+ // Success: (for this parameter at least)
+ //
+ // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
+ continue;
+ }
+ else if ((parameter = original.GetParameter(kvp.Key)) != null)
+ {
+ // 2. Required value corresponds to a parameter - check to make sure that this value matches
+ // any IRouteConstraint implementations.
+ if (!MatchesConstraints(original, parameter, kvp.Key, requiredValues))
+ {
+ // Fail: this route has a constraint that failed.
+ //
+ // Ex: Admin/{controller:regex(Home|Login)}/{action=Index}/{id?} - with required values: { controller = "Store" }
+ return null;
+ }
+
+ // Success: (for this parameter at least)
+ //
+ // Ex: {area}/{controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
+ continue;
+ }
+ else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
+ RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
+ {
+ // 3. Required value corresponds to a matching default value - check to make sure that this value matches
+ // any IRouteConstraint implementations. It's unlikely that this would happen in practice but it doesn't
+ // hurt for us to check.
+ if (!MatchesConstraints(original, parameter: null, kvp.Key, requiredValues))
+ {
+ // Fail: this route has a constraint that failed.
+ //
+ // Ex:
+ // Admin/Home/{action=Index}/{id?}
+ // defaults: { area = "Admin" }
+ // constraints: { area = "Blog" }
+ // with required values: { area = "Admin" }
+ return null;
+ }
+
+ // Success: (for this parameter at least)
+ //
+ // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Admin", ... }
+ continue;
+ }
+ else
+ {
+ // Fail: this is a required value for a key that doesn't appear in the templates, or the route
+ // pattern has a different default value for a non-parameter.
+ //
+ // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Blog", ... }
+ // OR (less likely)
+ // Ex: Admin/{controller=Home}/{action=Index}/{id?} with required values: { page = "/Index", ... }
+ return null;
+ }
+ }
+
+ List updatedParameters = null;
+ List updatedSegments = null;
+ RouteValueDictionary updatedDefaults = null;
+
+ // So if we get here, we're ready to update the route pattern. We need to update two things:
+ // 1. Remove any default values that conflict with the required values.
+ // 2. Merge any existing required values
+ foreach (var kvp in requiredValues)
+ {
+ var parameter = original.GetParameter(kvp.Key);
+
+ // We only need to handle the case where the required value maps to a parameter. That's the only
+ // case where we allow a default and a required value to disagree, and we already validated the
+ // other cases.
+ if (parameter != null &&
+ original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
+ !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
+ {
+ if (updatedDefaults == null && updatedSegments == null && updatedParameters == null)
+ {
+ updatedDefaults = new RouteValueDictionary(original.Defaults);
+ updatedSegments = new List(original.PathSegments);
+ updatedParameters = new List(original.Parameters);
+ }
+
+ updatedDefaults.Remove(kvp.Key);
+ RemoveParameterDefault(updatedSegments, updatedParameters, parameter);
+ }
+ }
+
+ foreach (var kvp in original.RequiredValues)
+ {
+ requiredValues.TryAdd(kvp.Key, kvp.Value);
+ }
+
+ return new RoutePattern(
+ original.RawText,
+ updatedDefaults ?? original.Defaults,
+ original.ParameterPolicies,
+ requiredValues,
+ updatedParameters ?? original.Parameters,
+ updatedSegments ?? original.PathSegments);
+ }
+
+ private bool MatchesConstraints(RoutePattern pattern, RoutePatternParameterPart parameter, string key, RouteValueDictionary requiredValues)
+ {
+ if (pattern.ParameterPolicies.TryGetValue(key, out var policies))
+ {
+ for (var i = 0; i < policies.Count; i++)
+ {
+ var policy = _policyFactory.Create(parameter, policies[i]);
+ if (policy is IRouteConstraint constraint)
+ {
+ if (!constraint.Match(httpContext: null, NullRouter.Instance, key, requiredValues, RouteDirection.IncomingRequest))
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private void RemoveParameterDefault(List segments, List parameters, RoutePatternParameterPart parameter)
+ {
+ // We know that a parameter can only appear once, so we only need to rewrite one segment and one parameter.
+ for (var i = 0; i < segments.Count; i++)
+ {
+ var segment = segments[i];
+ for (var j = 0; j < segment.Parts.Count; j++)
+ {
+ if (object.ReferenceEquals(parameter, segment.Parts[j]))
+ {
+ // Found it!
+ var updatedParameter = RoutePatternFactory.ParameterPart(parameter.Name, @default: null, parameter.ParameterKind, parameter.ParameterPolicies);
+
+ var updatedParts = new List(segment.Parts);
+ updatedParts[j] = updatedParameter;
+ segments[i] = RoutePatternFactory.Segment(updatedParts);
+
+ for (var k = 0; k < parameters.Count; k++)
+ {
+ if (ReferenceEquals(parameter, parameters[k]))
+ {
+ parameters[k] = updatedParameter;
+ break;
+ }
+ }
+
+ return;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs
index 49860dcb0f..0cb7e48989 100644
--- a/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RouteParameterParser.cs
@@ -2,8 +2,7 @@
// 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.Routing.Internal;
namespace Microsoft.AspNetCore.Routing.Patterns
{
@@ -77,7 +76,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
currentIndex++;
}
- var parseResults = ParseConstraints(parameter, parameterName, currentIndex, endIndex);
+ var parseResults = ParseConstraints(parameter, currentIndex, endIndex);
currentIndex = parseResults.CurrentIndex;
string defaultValue = null;
@@ -91,17 +90,16 @@ namespace Microsoft.AspNetCore.Routing.Patterns
parameterName,
defaultValue,
parameterKind,
- parseResults.ParameterPolicies.ToArray(),
+ parseResults.ParameterPolicies,
encodeSlashes);
}
private static ParameterPolicyParseResults ParseConstraints(
string text,
- string parameterName,
int currentIndex,
int endIndex)
{
- var constraints = new List();
+ var constraints = new ArrayBuilder(0);
var state = ParseState.Start;
var startIndex = currentIndex;
do
@@ -234,7 +232,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
} while (state != ParseState.End);
- return new ParameterPolicyParseResults(currentIndex, constraints);
+ return new ParameterPolicyParseResults(currentIndex, constraints.ToArray());
}
private enum ParseState
@@ -249,9 +247,9 @@ namespace Microsoft.AspNetCore.Routing.Patterns
{
public readonly int CurrentIndex;
- public readonly IReadOnlyList ParameterPolicies;
+ public readonly RoutePatternParameterPolicyReference[] ParameterPolicies;
- public ParameterPolicyParseResults(int currentIndex, IReadOnlyList parameterPolicies)
+ public ParameterPolicyParseResults(int currentIndex, RoutePatternParameterPolicyReference[] parameterPolicies)
{
CurrentIndex = currentIndex;
ParameterPolicies = parameterPolicies;
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePattern.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePattern.cs
index 1f1bf7ee0a..6852140bad 100644
--- a/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePattern.cs
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePattern.cs
@@ -23,17 +23,20 @@ namespace Microsoft.AspNetCore.Routing.Patterns
string rawText,
IReadOnlyDictionary defaults,
IReadOnlyDictionary> parameterPolicies,
+ IReadOnlyDictionary requiredValues,
IReadOnlyList parameters,
IReadOnlyList pathSegments)
{
Debug.Assert(defaults != null);
Debug.Assert(parameterPolicies != null);
Debug.Assert(parameters != null);
+ Debug.Assert(requiredValues != null);
Debug.Assert(pathSegments != null);
RawText = rawText;
Defaults = defaults;
ParameterPolicies = parameterPolicies;
+ RequiredValues = requiredValues;
Parameters = parameters;
PathSegments = pathSegments;
@@ -53,6 +56,29 @@ namespace Microsoft.AspNetCore.Routing.Patterns
///
public IReadOnlyDictionary> ParameterPolicies { get; }
+ ///
+ /// Gets a collection of route values that must be provided for this route pattern to be considered
+ /// applicable.
+ ///
+ ///
+ ///
+ /// allows a framework to substitute route values into a parameterized template
+ /// so that the same route template specification can be used to create multiple route patterns.
+ ///
+ /// This example shows how a route template can be used with required values to substitute known
+ /// route values for parameters.
+ ///
+ /// Route Template: "{controller=Home}/{action=Index}/{id?}"
+ /// Route Values: { controller = "Store", action = "Index" }
+ ///
+ ///
+ /// A route pattern produced in this way will match and generate URL paths like: /Store,
+ /// /Store/Index, and /Store/Index/17.
+ ///
+ ///
+ ///
+ public IReadOnlyDictionary RequiredValues { get; }
+
///
/// Gets the precedence value of the route pattern for URL matching.
///
@@ -110,7 +136,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
return null;
}
- private string DebuggerToString()
+ internal string DebuggerToString()
{
return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
}
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternFactory.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternFactory.cs
index 2f755a08e9..7b577e25bd 100644
--- a/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternFactory.cs
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternFactory.cs
@@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
///
public static class RoutePatternFactory
{
- private static readonly IReadOnlyDictionary EmptyDefaultsDictionary =
+ private static readonly IReadOnlyDictionary EmptyDictionary =
new ReadOnlyDictionary(new Dictionary());
private static readonly IReadOnlyDictionary> EmptyPoliciesDictionary =
@@ -61,7 +61,37 @@ namespace Microsoft.AspNetCore.Routing.Patterns
}
var original = RoutePatternParser.Parse(pattern);
- return Pattern(original.RawText, defaults, parameterPolicies, original.PathSegments);
+ return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), requiredValues: null, original.PathSegments);
+ }
+
+ ///
+ /// Creates a from its string representation along
+ /// with provided default values and parameter policies.
+ ///
+ /// The route pattern string to parse.
+ ///
+ /// Additional default values to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using
+ /// and then merged into the parsed route pattern.
+ ///
+ ///
+ /// Additional parameter policies to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using
+ /// and then merged into the parsed route pattern.
+ ///
+ ///
+ /// Route values that can be substituted for parameters in the route pattern. See remarks on .
+ ///
+ /// The .
+ public static RoutePattern Parse(string pattern, object defaults, object parameterPolicies, object requiredValues)
+ {
+ if (pattern == null)
+ {
+ throw new ArgumentNullException(nameof(pattern));
+ }
+
+ var original = RoutePatternParser.Parse(pattern);
+ return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), Wrap(requiredValues), original.PathSegments);
}
///
@@ -76,7 +106,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(segments));
}
- return PatternCore(null, null, null, segments);
+ return PatternCore(null, null, null, null, segments);
}
///
@@ -92,7 +122,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(segments));
}
- return PatternCore(rawText, null, null, segments);
+ return PatternCore(rawText, null, null, null, segments);
}
///
@@ -121,14 +151,14 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(segments));
}
- return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), segments);
+ return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
}
///
/// Creates a from a collection of segments along
/// with provided default values and parameter policies.
///
- /// The raw text to associate with the route pattern.
+ /// The raw text to associate with the route pattern. May be null.
///
/// Additional default values to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using
@@ -152,7 +182,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(segments));
}
- return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), segments);
+ return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
}
///
@@ -167,7 +197,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(segments));
}
- return PatternCore(null, null, null, segments);
+ return PatternCore(null, null, null, requiredValues: null, segments);
}
///
@@ -183,7 +213,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(segments));
}
- return PatternCore(rawText, null, null, segments);
+ return PatternCore(rawText, null, null, requiredValues: null, segments);
}
///
@@ -212,7 +242,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(segments));
}
- return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), segments);
+ return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
}
///
@@ -243,13 +273,14 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(segments));
}
- return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), segments);
+ return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
}
private static RoutePattern PatternCore(
string rawText,
RouteValueDictionary defaults,
RouteValueDictionary parameterPolicies,
+ RouteValueDictionary requiredValues,
IEnumerable segments)
{
// We want to merge the segment data with the 'out of line' defaults and parameter policies.
@@ -311,12 +342,56 @@ namespace Microsoft.AspNetCore.Routing.Patterns
}
}
+ // Each Required Value either needs to either:
+ // 1. be null-ish
+ // 2. have a corresponding parameter
+ // 3. have a corrsponding default that matches both key and value
+ if (requiredValues != null)
+ {
+ foreach (var kvp in requiredValues)
+ {
+ // 1.be null-ish
+ var found = RouteValueEqualityComparer.Default.Equals(string.Empty, kvp.Value);
+
+ // 2. have a corresponding parameter
+ if (!found && parameters != null)
+ {
+ for (var i = 0; i < parameters.Count; i++)
+ {
+ if (string.Equals(kvp.Key, parameters[i].Name, StringComparison.OrdinalIgnoreCase))
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ // 3. have a corrsponding default that matches both key and value
+ if (!found &&
+ updatedDefaults != null &&
+ updatedDefaults.TryGetValue(kvp.Key, out var defaultValue) &&
+ RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
+ {
+ found = true;
+ }
+
+ if (!found)
+ {
+ throw new InvalidOperationException(
+ $"No corresponding parameter or default value could be found for the required value " +
+ $"'{kvp.Key}={kvp.Value}'. A non-null required value must correspond to a route parameter or the " +
+ $"route pattern must have a matching default value.");
+ }
+ }
+ }
+
return new RoutePattern(
rawText,
- updatedDefaults ?? EmptyDefaultsDictionary,
+ updatedDefaults ?? EmptyDictionary,
updatedParameterPolicies != null
? updatedParameterPolicies.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value.ToArray())
: EmptyPoliciesDictionary,
+ requiredValues ?? EmptyDictionary,
(IReadOnlyList)parameters ?? Array.Empty(),
updatedSegments);
@@ -449,7 +524,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(parts));
}
- return SegmentCore((RoutePatternPart[]) parts.Clone());
+ return SegmentCore((RoutePatternPart[])parts.Clone());
}
private static RoutePatternPathSegment SegmentCore(RoutePatternPart[] parts)
@@ -670,7 +745,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
parameterName: parameterName,
@default: @default,
parameterKind: parameterKind,
- parameterPolicies: (RoutePatternParameterPolicyReference[]) parameterPolicies.Clone());
+ parameterPolicies: (RoutePatternParameterPolicyReference[])parameterPolicies.Clone());
}
private static RoutePatternParameterPart ParameterPartCore(
@@ -802,5 +877,10 @@ namespace Microsoft.AspNetCore.Routing.Patterns
{
return new RoutePatternParameterPolicyReference(parameterPolicy);
}
+
+ private static RouteValueDictionary Wrap(object values)
+ {
+ return values == null ? null : new RouteValueDictionary(values);
+ }
}
}
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternTransformer.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternTransformer.cs
new file mode 100644
index 0000000000..bea4c610fc
--- /dev/null
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/Patterns/RoutePatternTransformer.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Routing.Patterns
+{
+ ///
+ /// A singleton service that provides transformations on .
+ ///
+ public abstract class RoutePatternTransformer
+ {
+ ///
+ /// Attempts to substitute the provided into the provided
+ /// .
+ ///
+ /// The original .
+ /// The required values to substitute.
+ ///
+ /// A new if substitution succeeds, otherwise null.
+ ///
+ ///
+ ///
+ /// Substituting required values into a route pattern is intended for us with a general-purpose
+ /// parameterize route specification that can match many logical endpoints. Calling
+ /// can produce a derived route pattern
+ /// for each set of route values that corresponds to an endpoint.
+ ///
+ ///
+ /// The substitution process considers default values and implementations
+ /// when examining a required value. will
+ /// return null if any required value cannot be substituted.
+ ///
+ ///
+ public abstract RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues);
+ }
+}
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/RouteEndpointModel.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/RouteEndpointModel.cs
new file mode 100644
index 0000000000..8021c86b8f
--- /dev/null
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/RouteEndpointModel.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 RouteEndpointModel : EndpointModel
+ {
+ public RoutePattern RoutePattern { get; set; }
+
+ public int Order { get; set; }
+
+ public RouteEndpointModel(
+ RequestDelegate requestDelegate,
+ RoutePattern routePattern,
+ int order)
+ {
+ RequestDelegate = requestDelegate;
+ RoutePattern = routePattern;
+ Order = order;
+ }
+
+ public override Endpoint Build()
+ {
+ var routeEndpoint = new RouteEndpoint(
+ RequestDelegate,
+ RoutePattern,
+ Order,
+ new EndpointMetadataCollection(Metadata),
+ DisplayName);
+
+ return routeEndpoint;
+ }
+ }
+}
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/RouteOptions.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/RouteOptions.cs
index efabde03a1..910ec514b7 100644
--- a/src/Routing/src/Microsoft.AspNetCore.Routing/RouteOptions.cs
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/RouteOptions.cs
@@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Routing
{
public class RouteOptions
{
+ public ICollection EndpointDataSources { get; internal set; }
+
///
/// Gets or sets a value indicating whether all generated paths URLs are lower-case.
/// Use to configure the behavior for query strings.
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/RouteValueEqualityComparer.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/RouteValueEqualityComparer.cs
index 6f2a1eab45..f7cf0570c9 100644
--- a/src/Routing/src/Microsoft.AspNetCore.Routing/RouteValueEqualityComparer.cs
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/RouteValueEqualityComparer.cs
@@ -20,6 +20,8 @@ namespace Microsoft.AspNetCore.Routing
///
public class RouteValueEqualityComparer : IEqualityComparer
{
+ public static readonly RouteValueEqualityComparer Default = new RouteValueEqualityComparer();
+
///
public new bool Equals(object x, object y)
{
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/RouteValuesAddressScheme.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/RouteValuesAddressScheme.cs
index ae7bd66705..b31b59345e 100644
--- a/src/Routing/src/Microsoft.AspNetCore.Routing/RouteValuesAddressScheme.cs
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/RouteValuesAddressScheme.cs
@@ -14,11 +14,11 @@ namespace Microsoft.AspNetCore.Routing
{
internal class RouteValuesAddressScheme : IEndpointAddressScheme
{
- private readonly CompositeEndpointDataSource _dataSource;
+ private readonly EndpointDataSource _dataSource;
private LinkGenerationDecisionTree _allMatchesLinkGenerationTree;
private Dictionary> _namedMatchResults;
- public RouteValuesAddressScheme(CompositeEndpointDataSource dataSource)
+ public RouteValuesAddressScheme(EndpointDataSource dataSource)
{
_dataSource = dataSource;
@@ -125,12 +125,21 @@ namespace Microsoft.AspNetCore.Routing
continue;
}
+ var metadata = endpoint.Metadata.GetMetadata();
+ if (metadata == null && routeEndpoint.RoutePattern.RequiredValues.Count == 0)
+ {
+ continue;
+ }
+
if (endpoint.Metadata.GetMetadata()?.SuppressLinkGeneration == true)
{
continue;
}
- var entry = CreateOutboundRouteEntry(routeEndpoint);
+ var entry = CreateOutboundRouteEntry(
+ routeEndpoint,
+ metadata?.RequiredValues ?? routeEndpoint.RoutePattern.RequiredValues,
+ metadata?.RouteName);
var outboundMatch = new OutboundMatch() { Entry = entry };
allOutboundMatches.Add(outboundMatch);
@@ -151,18 +160,20 @@ namespace Microsoft.AspNetCore.Routing
return (allOutboundMatches, namedOutboundMatchResults);
}
- private OutboundRouteEntry CreateOutboundRouteEntry(RouteEndpoint endpoint)
+ private OutboundRouteEntry CreateOutboundRouteEntry(
+ RouteEndpoint endpoint,
+ IReadOnlyDictionary requiredValues,
+ string routeName)
{
- var routeValuesAddressMetadata = endpoint.Metadata.GetMetadata();
var entry = new OutboundRouteEntry()
{
Handler = NullRouter.Instance,
Order = endpoint.Order,
Precedence = RoutePrecedence.ComputeOutbound(endpoint.RoutePattern),
- RequiredLinkValues = new RouteValueDictionary(routeValuesAddressMetadata?.RequiredValues),
+ RequiredLinkValues = new RouteValueDictionary(requiredValues),
RouteTemplate = new RouteTemplate(endpoint.RoutePattern),
Data = endpoint,
- RouteName = routeValuesAddressMetadata?.RouteName,
+ RouteName = routeName,
};
entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
return entry;
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs
index 4d7ba66b35..d529c511d6 100644
--- a/src/Routing/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs
@@ -20,6 +20,8 @@ namespace Microsoft.AspNetCore.Routing.Template
// /api/template/{id:int} == 1.12
public static decimal ComputeInbound(RouteTemplate template)
{
+ ValidateSegementLength(template.Segments.Count);
+
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
// and 4 results in a combined precedence of 2.14 (decimal).
var precedence = 0m;
@@ -40,6 +42,8 @@ namespace Microsoft.AspNetCore.Routing.Template
// See description on ComputeInbound(RouteTemplate)
internal static decimal ComputeInbound(RoutePattern routePattern)
{
+ ValidateSegementLength(routePattern.PathSegments.Count);
+
var precedence = 0m;
for (var i = 0; i < routePattern.PathSegments.Count; i++)
@@ -62,6 +66,8 @@ namespace Microsoft.AspNetCore.Routing.Template
// /api/template/{id:int} == 5.54
public static decimal ComputeOutbound(RouteTemplate template)
{
+ ValidateSegementLength(template.Segments.Count);
+
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
// and 4 results in a combined precedence of 2.14 (decimal).
var precedence = 0m;
@@ -82,6 +88,8 @@ namespace Microsoft.AspNetCore.Routing.Template
// see description on ComputeOutbound(RouteTemplate)
internal static decimal ComputeOutbound(RoutePattern routePattern)
{
+ ValidateSegementLength(routePattern.PathSegments.Count);
+
// Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
// and 4 results in a combined precedence of 2.14 (decimal).
var precedence = 0m;
@@ -99,6 +107,15 @@ namespace Microsoft.AspNetCore.Routing.Template
return precedence;
}
+ private static void ValidateSegementLength(int length)
+ {
+ if (length > 28)
+ {
+ // An OverflowException will be thrown by Math.Pow when greater than 28
+ throw new InvalidOperationException("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.");
+ }
+ }
+
// Segments have the following order:
// 5 - Literal segments
// 4 - Multi-part segments && Constrained parameter segments
diff --git a/src/Routing/src/Microsoft.AspNetCore.Routing/Template/RouteTemplate.cs b/src/Routing/src/Microsoft.AspNetCore.Routing/Template/RouteTemplate.cs
index ff41bf6af7..027020a02d 100644
--- a/src/Routing/src/Microsoft.AspNetCore.Routing/Template/RouteTemplate.cs
+++ b/src/Routing/src/Microsoft.AspNetCore.Routing/Template/RouteTemplate.cs
@@ -16,6 +16,13 @@ namespace Microsoft.AspNetCore.Routing.Template
public RouteTemplate(RoutePattern other)
{
+ if (other == null)
+ {
+ throw new ArgumentNullException(nameof(other));
+ }
+
+ // RequiredValues will be ignored. RouteTemplate doesn't support them.
+
TemplateText = other.RawText;
Segments = new List(other.PathSegments.Select(p => new TemplateSegment(p)));
Parameters = new List();
diff --git a/src/Routing/test/Directory.Build.props b/src/Routing/test/Directory.Build.props
index c126684d3f..20029dfa54 100644
--- a/src/Routing/test/Directory.Build.props
+++ b/src/Routing/test/Directory.Build.props
@@ -1,13 +1,6 @@
-
- netcoreapp2.2
- $(DeveloperBuildTestTfms)
- $(StandardTestTfms)
- $(StandardTestTfms);net461
-
-
false
diff --git a/src/Routing/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/EndpointMetadataCollectionTests.cs b/src/Routing/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/EndpointMetadataCollectionTests.cs
deleted file mode 100644
index 9f37f3ebec..0000000000
--- a/src/Routing/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/src/Routing/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests.csproj b/src/Routing/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests.csproj
index c5fa909119..c6388013e4 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests.csproj
+++ b/src/Routing/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests.csproj
@@ -1,7 +1,7 @@
- $(StandardTestTfms)
+ netcoreapp3.0
diff --git a/src/Routing/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs b/src/Routing/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs
deleted file mode 100644
index 35b8031fe8..0000000000
--- a/src/Routing/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs
+++ /dev/null
@@ -1,2063 +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 Remove_KeyAndOutValue_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
-
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_EmptyStringIsAllowed()
- {
- // Arrange
- var dict = new RouteValueDictionary();
-
- // Act
- var result = dict.Remove("", out var removedValue);
-
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_PropertyStorage_Empty()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { });
-
- // Act
- var result = dict.Remove("other", out var removedValue);
-
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- Assert.Empty(dict);
- Assert.NotNull(dict._propertyStorage);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_PropertyStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
-
- // Act
- var result = dict.Remove("other", out var removedValue);
-
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType[]>(dict._arrayStorage);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_PropertyStorage_True()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary(new { key = value });
-
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Empty(dict);
- Assert.IsType[]>(dict._arrayStorage);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_PropertyStorage_True_CaseInsensitive()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary(new { key = value });
-
- // Act
- var result = dict.Remove("kEy", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Empty(dict);
- Assert.IsType[]>(dict._arrayStorage);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary()
- {
- { "key", "value" },
- };
-
- // Act
- var result = dict.Remove("other", out var removedValue);
-
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType[]>(dict._arrayStorage);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_True()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
- {
- { "key", value }
- };
-
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Empty(dict);
- Assert.IsType[]>(dict._arrayStorage);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_True_CaseInsensitive()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
- {
- { "key", value }
- };
-
- // Act
- var result = dict.Remove("kEy", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Empty(dict);
- Assert.IsType[]>(dict._arrayStorage);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_KeyExists_First()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
- {
- { "key", value },
- { "other", 5 },
- { "dotnet", "rocks" }
- };
-
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Equal(2, dict.Count);
- Assert.False(dict.ContainsKey("key"));
- Assert.True(dict.ContainsKey("other"));
- Assert.True(dict.ContainsKey("dotnet"));
- Assert.IsType[]>(dict._arrayStorage);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_KeyExists_Middle()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
- {
- { "other", 5 },
- { "key", value },
- { "dotnet", "rocks" }
- };
-
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Equal(2, dict.Count);
- Assert.False(dict.ContainsKey("key"));
- Assert.True(dict.ContainsKey("other"));
- Assert.True(dict.ContainsKey("dotnet"));
- Assert.IsType[]>(dict._arrayStorage);
- }
-
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_KeyExists_Last()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
- {
- { "other", 5 },
- { "dotnet", "rocks" },
- { "key", value }
- };
-
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Equal(2, dict.Count);
- Assert.False(dict.ContainsKey("key"));
- Assert.True(dict.ContainsKey("other"));
- Assert.True(dict.ContainsKey("dotnet"));
- 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/src/Routing/test/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests/DecisionTreeBuilderTest.cs b/src/Routing/test/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests/DecisionTreeBuilderTest.cs
index 5e28fd8917..b8eea95253 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests/DecisionTreeBuilderTest.cs
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests/DecisionTreeBuilderTest.cs
@@ -207,13 +207,7 @@ namespace Microsoft.AspNetCore.Routing.DecisionTree
private class ItemClassifier : IClassifier-
{
- public IEqualityComparer ValueComparer
- {
- get
- {
- return new RouteValueEqualityComparer();
- }
- }
+ public IEqualityComparer ValueComparer => RouteValueEqualityComparer.Default;
public IDictionary GetCriteria(Item item)
{
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests.csproj b/src/Routing/test/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests.csproj
index 286e462426..21fee51bbf 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests.csproj
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests/Microsoft.AspNetCore.Routing.DecisionTree.Sources.Tests.csproj
@@ -1,7 +1,7 @@
- $(StandardTestTfms)
+ netcoreapp3.0
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Benchmarks/EndpointRoutingBenchmarkTest.cs b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Benchmarks/EndpointRoutingBenchmarkTest.cs
index 2e39f5846c..b40da83fc9 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Benchmarks/EndpointRoutingBenchmarkTest.cs
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Benchmarks/EndpointRoutingBenchmarkTest.cs
@@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-#if NETCOREAPP2_2
using System;
using System.Net;
using System.Net.Http;
@@ -57,4 +56,3 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
}
}
}
-#endif
\ No newline at end of file
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Benchmarks/RouterBenchmarkTest.cs b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Benchmarks/RouterBenchmarkTest.cs
index bb7300388b..aecbaaab8c 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Benchmarks/RouterBenchmarkTest.cs
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Benchmarks/RouterBenchmarkTest.cs
@@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-#if NETCOREAPP2_2
using System;
using System.Net;
using System.Net.Http;
@@ -56,5 +55,4 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
_client.Dispose();
}
}
-}
-#endif
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs
index 6f5a978c5f..6201b01d7b 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/EndpointRoutingSampleTest.cs
@@ -24,16 +24,48 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
_client.BaseAddress = new Uri("http://localhost");
}
+ [Theory]
+ [InlineData("Branch1")]
+ [InlineData("Branch2")]
+ public async Task Routing_CanRouteRequest_ToBranchRouter(string branch)
+ {
+ // Arrange
+ var message = new HttpRequestMessage(HttpMethod.Get, $"{branch}/api/get/5");
+
+ // Act
+ var response = await _client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal($"{branch} - API Get 5", await response.Content.ReadAsStringAsync());
+ }
+
[Fact]
public async Task MatchesRootPath_AndReturnsPlaintext()
{
// Arrange
var expectedContentType = "text/plain";
- var expectedContent = "Endpoint Routing sample endpoints:" + Environment.NewLine + "/plaintext";
// Act
var response = await _client.GetAsync("/");
+ // 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);
+ }
+
+ [Fact]
+ public async Task MatchesStaticRouteTemplate_AndReturnsPlaintext()
+ {
+ // Arrange
+ var expectedContentType = "text/plain";
+ var expectedContent = "Plain text!";
+
+ // Act
+ var response = await _client.GetAsync("/plaintext");
+
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content);
@@ -44,14 +76,14 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
}
[Fact]
- public async Task MatchesStaticRouteTemplate_AndReturnsPlaintext()
+ public async Task MatchesHelloMiddleware_AndReturnsPlaintext()
{
// Arrange
var expectedContentType = "text/plain";
- var expectedContent = "Hello, World!";
+ var expectedContent = "Hello World";
// Act
- var response = await _client.GetAsync("/plaintext");
+ var response = await _client.GetAsync("/helloworld");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Microsoft.AspNetCore.Routing.FunctionalTests.csproj b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Microsoft.AspNetCore.Routing.FunctionalTests.csproj
index 0fecc56388..79adee4f98 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Microsoft.AspNetCore.Routing.FunctionalTests.csproj
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/Microsoft.AspNetCore.Routing.FunctionalTests.csproj
@@ -1,7 +1,7 @@
- $(StandardTestTfms)
+ netcoreapp3.0
@@ -10,7 +10,7 @@
-
+
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/RouterSampleTest.cs b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/RouterSampleTest.cs
index 315dcc1b68..9388a03781 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/RouterSampleTest.cs
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.FunctionalTests/RouterSampleTest.cs
@@ -24,6 +24,22 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
_client.BaseAddress = new Uri("http://localhost");
}
+ [Theory]
+ [InlineData("Branch1")]
+ [InlineData("Branch2")]
+ public async Task Routing_CanRouteRequest_ToBranchRouter(string branch)
+ {
+ // Arrange
+ var message = new HttpRequestMessage(HttpMethod.Get, $"{branch}/api/get/5");
+
+ // Act
+ var response = await _client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal($"{branch} - API Get 5", await response.Content.ReadAsStringAsync());
+ }
+
[Fact]
public async Task Routing_CanRouteRequestDelegate_ToSpecificHttpVerb()
{
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingBuilderExtensionsTest.cs b/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs
similarity index 54%
rename from src/Routing/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingBuilderExtensionsTest.cs
rename to src/Routing/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs
index 106a47e27b..7d0cb9a191 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingBuilderExtensionsTest.cs
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs
@@ -2,20 +2,23 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
-using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
+using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Builder
{
- public class EndpointRoutingBuilderExtensionsTest
+ public class EndpointRoutingApplicationBuilderExtensionsTest
{
[Fact]
public void UseEndpointRouting_ServicesNotRegistered_Throws()
@@ -24,7 +27,7 @@ namespace Microsoft.AspNetCore.Builder
var app = new ApplicationBuilder(Mock.Of());
// Act
- var ex = Assert.Throws(() => app.UseEndpointRouting());
+ var ex = Assert.Throws(() => app.UseEndpointRouting(builder => { }));
// Assert
Assert.Equal(
@@ -59,7 +62,7 @@ namespace Microsoft.AspNetCore.Builder
var app = new ApplicationBuilder(services);
- app.UseEndpointRouting();
+ app.UseEndpointRouting(builder => { });
var appFunc = app.Build();
var httpContext = new DefaultHttpContext();
@@ -76,17 +79,20 @@ namespace Microsoft.AspNetCore.Builder
{
// Arrange
var endpoint = new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("{*p}"),
- 0,
- EndpointMetadataCollection.Empty,
- "Test");
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("{*p}"),
+ 0,
+ EndpointMetadataCollection.Empty,
+ "Test");
- var services = CreateServices(endpoint);
+ var services = CreateServices();
var app = new ApplicationBuilder(services);
- app.UseEndpointRouting();
+ app.UseEndpointRouting(builder =>
+ {
+ builder.DataSources.Add(new DefaultEndpointDataSource(endpoint));
+ });
var appFunc = app.Build();
var httpContext = new DefaultHttpContext();
@@ -127,7 +133,7 @@ namespace Microsoft.AspNetCore.Builder
var app = new ApplicationBuilder(services);
- app.UseEndpointRouting();
+ app.UseEndpointRouting(builder => { });
app.UseEndpoint();
var appFunc = app.Build();
@@ -140,17 +146,84 @@ namespace Microsoft.AspNetCore.Builder
Assert.Null(httpContext.Features.Get());
}
- private IServiceProvider CreateServices(params Endpoint[] endpoints)
+ [Fact]
+ public void UseEndpointRouting_CallWithBuilder_SetsEndpointDataSource()
+ {
+ // Arrange
+ var matcherEndpointDataSources = new List();
+ var matcherFactoryMock = new Mock();
+ matcherFactoryMock
+ .Setup(m => m.CreateMatcher(It.IsAny()))
+ .Callback((EndpointDataSource arg) =>
+ {
+ matcherEndpointDataSources.Add(arg);
+ })
+ .Returns(new TestMatcher(false));
+
+ var services = CreateServices(matcherFactoryMock.Object);
+
+ var app = new ApplicationBuilder(services);
+
+ // Act
+ app.UseEndpointRouting(builder =>
+ {
+ builder.Map("/1", "Test endpoint 1", d => null);
+ builder.Map("/2", "Test endpoint 2", d => null);
+ });
+
+ app.UseEndpointRouting(builder =>
+ {
+ builder.Map("/3", "Test endpoint 3", d => null);
+ builder.Map("/4", "Test endpoint 4", d => null);
+ });
+
+ // This triggers the middleware to be created and the matcher factory to be called
+ // with the datasource we want to test
+ var requestDelegate = app.Build();
+ requestDelegate(new DefaultHttpContext());
+
+ // Assert
+ Assert.Equal(2, matcherEndpointDataSources.Count);
+
+ // Each middleware has its own endpoints
+ Assert.Collection(matcherEndpointDataSources[0].Endpoints,
+ e => Assert.Equal("Test endpoint 1", e.DisplayName),
+ e => Assert.Equal("Test endpoint 2", e.DisplayName));
+ Assert.Collection(matcherEndpointDataSources[1].Endpoints,
+ e => Assert.Equal("Test endpoint 3", e.DisplayName),
+ e => Assert.Equal("Test endpoint 4", e.DisplayName));
+
+ var compositeEndpointBuilder = services.GetRequiredService();
+
+ // Global middleware has all endpoints
+ Assert.Collection(compositeEndpointBuilder.Endpoints,
+ e => Assert.Equal("Test endpoint 1", e.DisplayName),
+ e => Assert.Equal("Test endpoint 2", e.DisplayName),
+ e => Assert.Equal("Test endpoint 3", e.DisplayName),
+ e => Assert.Equal("Test endpoint 4", e.DisplayName));
+ }
+
+ private IServiceProvider CreateServices()
+ {
+ return CreateServices(matcherFactory: null);
+ }
+
+ private IServiceProvider CreateServices(MatcherFactory matcherFactory)
{
var services = new ServiceCollection();
+ if (matcherFactory != null)
+ {
+ services.AddSingleton(matcherFactory);
+ }
+
services.AddLogging();
services.AddOptions();
services.AddRouting();
- services.AddSingleton(new DefaultEndpointDataSource(endpoints));
+ var serviceProvder = services.BuildServiceProvider();
- return services.BuildServiceProvider();
+ return serviceProvder;
}
}
}
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs b/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs
new file mode 100644
index 0000000000..ad714df63c
--- /dev/null
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/Builder/MapEndpointEndpointDataSourceBuilderExtensionsTest.cs
@@ -0,0 +1,101 @@
+// Copyright (c) .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
+ {
+ private ModelEndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder)
+ {
+ return Assert.IsType(Assert.Single(endpointRouteBuilder.DataSources));
+ }
+
+ private RouteEndpointModel GetRouteEndpointBuilder(IEndpointRouteBuilder endpointRouteBuilder)
+ {
+ return Assert.IsType(Assert.Single(GetBuilderEndpointDataSource(endpointRouteBuilder).EndpointModels));
+ }
+
+ [Fact]
+ public void MapEndpoint_StringPattern_BuildsEndpoint()
+ {
+ // Arrange
+ var builder = new DefaultEndpointRouteBuilder();
+ RequestDelegate requestDelegate = (d) => null;
+
+ // Act
+ var endpointBuilder = builder.Map("/", "Display name!", requestDelegate);
+
+ // Assert
+ var endpointBuilder1 = GetRouteEndpointBuilder(builder);
+
+ Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
+ Assert.Equal("Display name!", endpointBuilder1.DisplayName);
+ Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
+ }
+
+ [Fact]
+ public void MapEndpoint_TypedPattern_BuildsEndpoint()
+ {
+ // Arrange
+ var builder = new DefaultEndpointRouteBuilder();
+ RequestDelegate requestDelegate = (d) => null;
+
+ // Act
+ var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", requestDelegate);
+
+ // Assert
+ var endpointBuilder1 = GetRouteEndpointBuilder(builder);
+
+ Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
+ Assert.Equal("Display name!", endpointBuilder1.DisplayName);
+ Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
+ }
+
+ [Fact]
+ public void MapEndpoint_StringPatternAndMetadata_BuildsEndpoint()
+ {
+ // Arrange
+ var metadata = new object();
+ var builder = new DefaultEndpointRouteBuilder();
+ RequestDelegate requestDelegate = (d) => null;
+
+ // Act
+ var endpointBuilder = builder.Map("/", "Display name!", requestDelegate, new[] { metadata });
+
+ // Assert
+ var endpointBuilder1 = GetRouteEndpointBuilder(builder);
+ Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
+ Assert.Equal("Display name!", endpointBuilder1.DisplayName);
+ Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
+ Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata));
+ }
+
+ [Fact]
+ public void MapEndpoint_TypedPatternAndMetadata_BuildsEndpoint()
+ {
+ // Arrange
+ var metadata = new object();
+ var builder = new DefaultEndpointRouteBuilder();
+ RequestDelegate requestDelegate = (d) => null;
+
+ // Act
+ var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), "Display name!", requestDelegate, new[] { metadata });
+
+ // Assert
+ var endpointBuilder1 = GetRouteEndpointBuilder(builder);
+ Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
+ Assert.Equal("Display name!", endpointBuilder1.DisplayName);
+ Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
+ Assert.Equal(metadata, Assert.Single(endpointBuilder1.Metadata));
+ }
+ }
+}
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs b/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs
index 4f6792b47f..de47be3873 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorProcessTemplateTest.cs
@@ -1,11 +1,13 @@
// Copyright (c) .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.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.TestObjects;
+using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
@@ -259,7 +261,12 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
+ Action configure = (s) =>
+ {
+ s.Configure(o => o.LowercaseUrls = true);
+ };
+
+ var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
// Act
@@ -283,7 +290,12 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}");
- var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
+ Action configure = (s) =>
+ {
+ s.Configure(o => o.LowercaseUrls = true);
+ };
+
+ var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
var httpContext = CreateHttpContext();
// Act
@@ -334,7 +346,12 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- var linkGenerator = CreateLinkGenerator(new RouteOptions() { LowercaseUrls = true }, endpoints: new[] { endpoint, });
+ Action configure = (s) =>
+ {
+ s.Configure(o => o.LowercaseUrls = true);
+ };
+
+ var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
// Act
@@ -362,8 +379,17 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action configure = (s) =>
+ {
+ s.Configure(o =>
+ {
+ o.LowercaseUrls = true;
+ o.LowercaseQueryStrings = true;
+ });
+ };
+
var linkGenerator = CreateLinkGenerator(
- new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true },
+ configure,
endpoints: new[] { endpoint, });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@@ -387,8 +413,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action configure = (s) =>
+ {
+ s.Configure(o => o.AppendTrailingSlash = true);
+ };
+
var linkGenerator = CreateLinkGenerator(
- new RouteOptions() { AppendTrailingSlash = true },
+ configure,
endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@@ -412,8 +443,18 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action configure = (s) =>
+ {
+ s.Configure(o =>
+ {
+ o.LowercaseUrls = true;
+ o.LowercaseQueryStrings = true;
+ o.AppendTrailingSlash = true;
+ });
+ };
+
var linkGenerator = CreateLinkGenerator(
- new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true, AppendTrailingSlash = true },
+ configure,
endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@@ -437,8 +478,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action configure = (s) =>
+ {
+ s.Configure(o => o.LowercaseUrls = true);
+ };
+
var linkGenerator = CreateLinkGenerator(
- new RouteOptions() { LowercaseUrls = true },
+ configure,
endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
@@ -466,8 +512,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action configure = (s) =>
+ {
+ s.Configure(o => o.LowercaseUrls = false);
+ };
+
var linkGenerator = CreateLinkGenerator(
- new RouteOptions() { LowercaseUrls = false },
+ configure,
endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
@@ -494,8 +545,17 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action configure = (s) =>
+ {
+ s.Configure(o =>
+ {
+ o.LowercaseUrls = true;
+ o.LowercaseQueryStrings = true;
+ });
+ };
+
var linkGenerator = CreateLinkGenerator(
- new RouteOptions() { LowercaseUrls = true, LowercaseQueryStrings = true },
+ configure,
endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@@ -523,8 +583,17 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action configure = (s) =>
+ {
+ s.Configure(o =>
+ {
+ o.LowercaseUrls = false;
+ o.LowercaseQueryStrings = false;
+ });
+ };
+
var linkGenerator = CreateLinkGenerator(
- new RouteOptions() { LowercaseUrls = false, LowercaseQueryStrings = false },
+ configure,
endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
@@ -552,8 +621,13 @@ namespace Microsoft.AspNetCore.Routing
{
// Arrange
var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action configure = (s) =>
+ {
+ s.Configure(o => o.AppendTrailingSlash = false);
+ };
+
var linkGenerator = CreateLinkGenerator(
- new RouteOptions() { AppendTrailingSlash = false },
+ configure,
endpoints: new[] { endpoint });
var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
diff --git a/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs
index 3c20c704a0..9b423891d8 100644
--- a/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs
+++ b/src/Routing/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs
@@ -177,10 +177,15 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var routeOptions = new RouteOptions();
- routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ Action configureServices = s =>
+ {
+ s.Configure(o =>
+ {
+ o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ });
+ };
- var linkGenerator = CreateLinkGenerator(routeOptions: routeOptions, configureServices: null, endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
// Act
var path = linkGenerator.GetPathByAddress(
@@ -198,10 +203,15 @@ namespace Microsoft.AspNetCore.Routing
var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var routeOptions = new RouteOptions();
- routeOptions.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ Action configureServices = s =>
+ {
+ s.Configure(o =>
+ {
+ o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ });
+ };
- var linkGenerator = CreateLinkGenerator(routeOptions: routeOptions, configureServices: null, endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
// Act
var path = linkGenerator.GetPathByAddress(
@@ -311,17 +321,17 @@ namespace Microsoft.AspNetCore.Routing
public void GetLink_ParameterTransformer()
{
// Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}");
-
- var routeOptions = new RouteOptions();
- routeOptions.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", requiredValues: new { controller = "Home", name = "Test" });
Action configure = (s) =>
{
- s.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform());
+ s.Configure