Merge remote-tracking branch 'origin/release/5.0-preview8' into merge/release/5.0-preview8-to-master
This commit is contained in:
commit
10ff1d6e2b
|
|
@ -473,6 +473,36 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
throw new ArgumentNullException(nameof(endpoints));
|
throw new ArgumentNullException(nameof(endpoints));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MapDynamicControllerRoute<TTransformer>(endpoints, pattern, state: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will
|
||||||
|
/// attempt to select a controller action using the route values produced by <typeparamref name="TTransformer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
|
||||||
|
/// <param name="pattern">The URL pattern of the route.</param>
|
||||||
|
/// <param name="state">A state object to provide to the <typeparamref name="TTransformer" /> instance.</param>
|
||||||
|
/// <typeparam name="TTransformer">The type of a <see cref="DynamicRouteValueTransformer"/>.</typeparam>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This method allows the registration of a <see cref="RouteEndpoint"/> and <see cref="DynamicRouteValueTransformer"/>
|
||||||
|
/// that combine to dynamically select a controller action using custom logic.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The instance of <typeparamref name="TTransformer"/> will be retrieved from the dependency injection container.
|
||||||
|
/// Register <typeparamref name="TTransformer"/> as transient in <c>ConfigureServices</c>. Using the transient lifetime
|
||||||
|
/// is required when using <paramref name="state" />.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static void MapDynamicControllerRoute<TTransformer>(this IEndpointRouteBuilder endpoints, string pattern, object state)
|
||||||
|
where TTransformer : DynamicRouteValueTransformer
|
||||||
|
{
|
||||||
|
if (endpoints == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endpoints));
|
||||||
|
}
|
||||||
|
|
||||||
EnsureControllerServices(endpoints);
|
EnsureControllerServices(endpoints);
|
||||||
|
|
||||||
// Called for side-effect to make sure that the data source is registered.
|
// Called for side-effect to make sure that the data source is registered.
|
||||||
|
|
@ -486,7 +516,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
})
|
})
|
||||||
.Add(b =>
|
.Add(b =>
|
||||||
{
|
{
|
||||||
b.Metadata.Add(new DynamicControllerRouteValueTransformerMetadata(typeof(TTransformer)));
|
b.Metadata.Add(new DynamicControllerRouteValueTransformerMetadata(typeof(TTransformer), state));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -519,4 +519,7 @@
|
||||||
<data name="ValidationVisitor_ContainerCannotBeSpecified" xml:space="preserve">
|
<data name="ValidationVisitor_ContainerCannotBeSpecified" xml:space="preserve">
|
||||||
<value>A container cannot be specified when the ModelMetada is of kind '{0}'.</value>
|
<value>A container cannot be specified when the ModelMetada is of kind '{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
<data name="StateShouldBeNullForRouteValueTransformers" xml:space="preserve">
|
||||||
|
<value>Transformer '{0}' was retrieved from dependency injection with a state value. State can only be specified when the dynamic route is mapped using MapDynamicControllerRoute's state argument together with transient lifetime transformer. Ensure that '{0}' doesn't set its own state and that the transformer is registered with a transient lifetime in dependency injection.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -97,13 +98,21 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
// no realistic way this could happen.
|
// no realistic way this could happen.
|
||||||
var dynamicControllerMetadata = endpoint.Metadata.GetMetadata<DynamicControllerMetadata>();
|
var dynamicControllerMetadata = endpoint.Metadata.GetMetadata<DynamicControllerMetadata>();
|
||||||
var transformerMetadata = endpoint.Metadata.GetMetadata<DynamicControllerRouteValueTransformerMetadata>();
|
var transformerMetadata = endpoint.Metadata.GetMetadata<DynamicControllerRouteValueTransformerMetadata>();
|
||||||
|
|
||||||
|
DynamicRouteValueTransformer transformer = null;
|
||||||
if (dynamicControllerMetadata != null)
|
if (dynamicControllerMetadata != null)
|
||||||
{
|
{
|
||||||
dynamicValues = dynamicControllerMetadata.Values;
|
dynamicValues = dynamicControllerMetadata.Values;
|
||||||
}
|
}
|
||||||
else if (transformerMetadata != null)
|
else if (transformerMetadata != null)
|
||||||
{
|
{
|
||||||
var transformer = (DynamicRouteValueTransformer)httpContext.RequestServices.GetRequiredService(transformerMetadata.SelectorType);
|
transformer = (DynamicRouteValueTransformer)httpContext.RequestServices.GetRequiredService(transformerMetadata.SelectorType);
|
||||||
|
if (transformer.State != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatStateShouldBeNullForRouteValueTransformers(transformerMetadata.SelectorType.Name));
|
||||||
|
}
|
||||||
|
transformer.State = transformerMetadata.State;
|
||||||
|
|
||||||
dynamicValues = await transformer.TransformAsync(httpContext, originalValues);
|
dynamicValues = await transformer.TransformAsync(httpContext, originalValues);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -146,6 +155,16 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transformer != null)
|
||||||
|
{
|
||||||
|
endpoints = await transformer.FilterAsync(httpContext, values, endpoints);
|
||||||
|
if (endpoints.Count == 0)
|
||||||
|
{
|
||||||
|
candidates.ReplaceEndpoint(i, null, null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update the route values
|
// Update the route values
|
||||||
candidates.ReplaceEndpoint(i, endpoint, values);
|
candidates.ReplaceEndpoint(i, endpoint, values);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
{
|
{
|
||||||
internal class DynamicControllerRouteValueTransformerMetadata : IDynamicEndpointMetadata
|
internal class DynamicControllerRouteValueTransformerMetadata : IDynamicEndpointMetadata
|
||||||
{
|
{
|
||||||
public DynamicControllerRouteValueTransformerMetadata(Type selectorType)
|
public DynamicControllerRouteValueTransformerMetadata(Type selectorType, object state)
|
||||||
{
|
{
|
||||||
if (selectorType == null)
|
if (selectorType == null)
|
||||||
{
|
{
|
||||||
|
|
@ -23,10 +23,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectorType = selectorType;
|
SelectorType = selectorType;
|
||||||
|
State = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsDynamic => true;
|
public bool IsDynamic => true;
|
||||||
|
|
||||||
public Type SelectorType { get; }
|
public Type SelectorType { get; }
|
||||||
|
|
||||||
|
public object State { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
@ -20,17 +21,40 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
/// <para>
|
/// <para>
|
||||||
/// The route values returned from a <see cref="TransformAsync(HttpContext, RouteValueDictionary)"/> implementation
|
/// The route values returned from a <see cref="TransformAsync(HttpContext, RouteValueDictionary)"/> implementation
|
||||||
/// will be used to select an action based on matching of the route values. All actions that match the route values
|
/// will be used to select an action based on matching of the route values. All actions that match the route values
|
||||||
/// will be considered as candidates, and may be further disambiguated by <see cref="IEndpointSelectorPolicy" />
|
/// will be considered as candidates, and may be further disambiguated by
|
||||||
/// implementations such as <see cref="HttpMethodMatcherPolicy" />.
|
/// <see cref="FilterAsync(HttpContext, RouteValueDictionary, IReadOnlyList{Endpoint})" /> as well as
|
||||||
|
/// <see cref="IEndpointSelectorPolicy" /> implementations such as <see cref="HttpMethodMatcherPolicy" />.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Operations on a <see cref="DynamicRouteValueTransformer" /> instance will be called for each dynamic endpoint
|
||||||
|
/// in the following sequence:
|
||||||
|
///
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item><description><see cref="State" /> is set</description></item>
|
||||||
|
/// <item><description><see cref="TransformAsync(HttpContext, RouteValueDictionary)"/></description></item>
|
||||||
|
/// <item><description><see cref="FilterAsync(HttpContext, RouteValueDictionary, IReadOnlyList{Endpoint})" /></description></item>
|
||||||
|
/// </list>
|
||||||
|
///
|
||||||
|
/// Implementations that are registered with the service collection as transient may safely use class
|
||||||
|
/// members to persist state across these operations.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Implementations <see cref="DynamicRouteValueTransformer" /> should be registered with the service
|
/// Implementations <see cref="DynamicRouteValueTransformer" /> should be registered with the service
|
||||||
/// collection as type <see cref="DynamicRouteValueTransformer" />. Implementations can use any service
|
/// collection as type <see cref="DynamicRouteValueTransformer" />. Implementations can use any service
|
||||||
/// lifetime.
|
/// lifetime. Implementations that make use of <see cref="State" /> must be registered as transient.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public abstract class DynamicRouteValueTransformer
|
public abstract class DynamicRouteValueTransformer
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a state value. An arbitrary value passed to the transformer from where it was registered.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Implementations that make use of <see cref="State" /> must be registered as transient with the service
|
||||||
|
/// collection.
|
||||||
|
/// </remarks>
|
||||||
|
public object State { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a set of transformed route values that will be used to select an action.
|
/// Creates a set of transformed route values that will be used to select an action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -38,5 +62,32 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
/// <param name="values">The route values associated with the current match. Implementations should not modify <paramref name="values"/>.</param>
|
/// <param name="values">The route values associated with the current match. Implementations should not modify <paramref name="values"/>.</param>
|
||||||
/// <returns>A task which asynchronously returns a set of route values.</returns>
|
/// <returns>A task which asynchronously returns a set of route values.</returns>
|
||||||
public abstract ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values);
|
public abstract ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filters the set of endpoints that were chosen as a result of lookup based on the route values returned by
|
||||||
|
/// <see cref="TransformAsync(HttpContext, RouteValueDictionary)" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext" /> associated with the current request.</param>
|
||||||
|
/// <param name="values">The route values returned from <see cref="TransformAsync(HttpContext, RouteValueDictionary)" />.</param>
|
||||||
|
/// <param name="endpoints">
|
||||||
|
/// The endpoints that were chosen as a result of lookup based on the route values returned by
|
||||||
|
/// <see cref="TransformAsync(HttpContext, RouteValueDictionary)" />.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>Asynchronously returns a list of endpoints to apply to the matches collection.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Implementations of <see cref="FilterAsync(HttpContext, RouteValueDictionary, IReadOnlyList{Endpoint})" /> may further
|
||||||
|
/// refine the list of endpoints chosen based on route value matching by returning a new list of endpoints based on
|
||||||
|
/// <paramref name="endpoints" />.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="FilterAsync(HttpContext, RouteValueDictionary, IReadOnlyList{Endpoint})" /> will not be called in the case
|
||||||
|
/// where zero endpoints were matched based on route values.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public virtual ValueTask<IReadOnlyList<Endpoint>> FilterAsync(HttpContext httpContext, RouteValueDictionary values, IReadOnlyList<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
return new ValueTask<IReadOnlyList<Endpoint>>(endpoints);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
_ => Task.CompletedTask,
|
_ => Task.CompletedTask,
|
||||||
new EndpointMetadataCollection(new object[]
|
new EndpointMetadataCollection(new object[]
|
||||||
{
|
{
|
||||||
new DynamicControllerRouteValueTransformerMetadata(typeof(CustomTransformer)),
|
new DynamicControllerRouteValueTransformerMetadata(typeof(CustomTransformer), State),
|
||||||
}),
|
}),
|
||||||
"dynamic");
|
"dynamic");
|
||||||
|
|
||||||
|
|
@ -68,10 +68,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddRouting();
|
services.AddRouting();
|
||||||
services.AddScoped<CustomTransformer>(s =>
|
services.AddTransient<CustomTransformer>(s =>
|
||||||
{
|
{
|
||||||
var transformer = new CustomTransformer();
|
var transformer = new CustomTransformer();
|
||||||
transformer.Transform = (c, values) => Transform(c, values);
|
transformer.Transform = (c, values, state) => Transform(c, values, state);
|
||||||
|
transformer.Filter = (c, values, state, candidates) => Filter(c, values, state, candidates);
|
||||||
return transformer;
|
return transformer;
|
||||||
});
|
});
|
||||||
Services = services.BuildServiceProvider();
|
Services = services.BuildServiceProvider();
|
||||||
|
|
@ -91,7 +92,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
private IServiceProvider Services { get; }
|
private IServiceProvider Services { get; }
|
||||||
|
|
||||||
private Func<HttpContext, RouteValueDictionary, ValueTask<RouteValueDictionary>> Transform { get; set; }
|
private Func<HttpContext, RouteValueDictionary, object, ValueTask<RouteValueDictionary>> Transform { get; set; }
|
||||||
|
|
||||||
|
private Func<HttpContext, RouteValueDictionary, object, IReadOnlyList<Endpoint>, ValueTask<IReadOnlyList<Endpoint>>> Filter { get; set; } = (_, __, ___, e) => new ValueTask<IReadOnlyList<Endpoint>>(e);
|
||||||
|
|
||||||
|
private object State { get; } = new object();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ApplyAsync_NoMatch()
|
public async Task ApplyAsync_NoMatch()
|
||||||
|
|
@ -106,7 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var candidates = new CandidateSet(endpoints, values, scores);
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
candidates.SetValidity(0, false);
|
candidates.SetValidity(0, false);
|
||||||
|
|
||||||
Transform = (c, values) =>
|
Transform = (c, values, state) =>
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
};
|
};
|
||||||
|
|
@ -135,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
var candidates = new CandidateSet(endpoints, values, scores);
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
Transform = (c, values) =>
|
Transform = (c, values, state) =>
|
||||||
{
|
{
|
||||||
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary());
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary());
|
||||||
};
|
};
|
||||||
|
|
@ -166,7 +171,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
var candidates = new CandidateSet(endpoints, values, scores);
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
Transform = (c, values) =>
|
Transform = (c, values, state) =>
|
||||||
{
|
{
|
||||||
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
{
|
{
|
||||||
|
|
@ -200,6 +205,37 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
Assert.True(candidates.IsValidCandidate(0));
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_ThrowsForTransformerWithInvalidLifetime()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var policy = new DynamicControllerEndpointMatcherPolicy(Selector, Comparer);
|
||||||
|
|
||||||
|
var endpoints = new[] { DynamicEndpoint, };
|
||||||
|
var values = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
|
||||||
|
var scores = new[] { 0, };
|
||||||
|
|
||||||
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
|
Transform = (c, values, state) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
|
{
|
||||||
|
controller = "Home",
|
||||||
|
action = "Index",
|
||||||
|
state
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = new ServiceCollection().AddScoped(sp => new CustomTransformer { State = "Invalid" }).BuildServiceProvider(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(() => policy.ApplyAsync(httpContext, candidates));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ApplyAsync_HasMatchFindsEndpoint_WithRouteValues()
|
public async Task ApplyAsync_HasMatchFindsEndpoint_WithRouteValues()
|
||||||
{
|
{
|
||||||
|
|
@ -212,12 +248,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
var candidates = new CandidateSet(endpoints, values, scores);
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
Transform = (c, values) =>
|
Transform = (c, values, state) =>
|
||||||
{
|
{
|
||||||
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
{
|
{
|
||||||
controller = "Home",
|
controller = "Home",
|
||||||
action = "Index",
|
action = "Index",
|
||||||
|
state
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -242,15 +279,164 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
{
|
{
|
||||||
Assert.Equal("controller", kvp.Key);
|
Assert.Equal("controller", kvp.Key);
|
||||||
Assert.Equal("Home", kvp.Value);
|
Assert.Equal("Home", kvp.Value);
|
||||||
},
|
},
|
||||||
kvp =>
|
kvp =>
|
||||||
{
|
{
|
||||||
Assert.Equal("slug", kvp.Key);
|
Assert.Equal("slug", kvp.Key);
|
||||||
Assert.Equal("test", kvp.Value);
|
Assert.Equal("test", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("state", kvp.Key);
|
||||||
|
Assert.Same(State, kvp.Value);
|
||||||
});
|
});
|
||||||
Assert.True(candidates.IsValidCandidate(0));
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_CanDiscardFoundEndpoints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var policy = new DynamicControllerEndpointMatcherPolicy(Selector, Comparer);
|
||||||
|
|
||||||
|
var endpoints = new[] { DynamicEndpoint, };
|
||||||
|
var values = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
|
||||||
|
var scores = new[] { 0, };
|
||||||
|
|
||||||
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
|
Transform = (c, values, state) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
|
{
|
||||||
|
controller = "Home",
|
||||||
|
action = "Index",
|
||||||
|
state
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter = (c, values, state, endpoints) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<IReadOnlyList<Endpoint>>(Array.Empty<Endpoint>());
|
||||||
|
};
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = Services,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_CanReplaceFoundEndpoints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var policy = new DynamicControllerEndpointMatcherPolicy(Selector, Comparer);
|
||||||
|
|
||||||
|
var endpoints = new[] { DynamicEndpoint, };
|
||||||
|
var values = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
|
||||||
|
var scores = new[] { 0, };
|
||||||
|
|
||||||
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
|
Transform = (c, values, state) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
|
{
|
||||||
|
controller = "Home",
|
||||||
|
action = "Index",
|
||||||
|
state
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter = (c, values, state, endpoints) => new ValueTask<IReadOnlyList<Endpoint>>(new[]
|
||||||
|
{
|
||||||
|
new Endpoint((ctx) => Task.CompletedTask, new EndpointMetadataCollection(Array.Empty<object>()), "ReplacedEndpoint")
|
||||||
|
});
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = Services,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
candidates[0].Values.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("action", kvp.Key);
|
||||||
|
Assert.Equal("Index", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("controller", kvp.Key);
|
||||||
|
Assert.Equal("Home", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("slug", kvp.Key);
|
||||||
|
Assert.Equal("test", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("state", kvp.Key);
|
||||||
|
Assert.Same(State, kvp.Value);
|
||||||
|
});
|
||||||
|
Assert.Equal("ReplacedEndpoint", candidates[0].Endpoint.DisplayName);
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_CanExpandTheListOfFoundEndpoints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var policy = new DynamicControllerEndpointMatcherPolicy(Selector, Comparer);
|
||||||
|
|
||||||
|
var endpoints = new[] { DynamicEndpoint, };
|
||||||
|
var values = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
|
||||||
|
var scores = new[] { 0, };
|
||||||
|
|
||||||
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
|
Transform = (c, values, state) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
|
{
|
||||||
|
controller = "Home",
|
||||||
|
action = "Index",
|
||||||
|
state
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter = (c, values, state, endpoints) => new ValueTask<IReadOnlyList<Endpoint>>(new[]
|
||||||
|
{
|
||||||
|
ControllerEndpoints[1], ControllerEndpoints[2]
|
||||||
|
});
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = Services,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, candidates.Count);
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
Assert.True(candidates.IsValidCandidate(1));
|
||||||
|
Assert.Same(ControllerEndpoints[1], candidates[0].Endpoint);
|
||||||
|
Assert.Same(ControllerEndpoints[2], candidates[1].Endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestDynamicControllerEndpointSelector : DynamicControllerEndpointSelector
|
private class TestDynamicControllerEndpointSelector : DynamicControllerEndpointSelector
|
||||||
{
|
{
|
||||||
public TestDynamicControllerEndpointSelector(EndpointDataSource dataSource)
|
public TestDynamicControllerEndpointSelector(EndpointDataSource dataSource)
|
||||||
|
|
@ -261,11 +447,18 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
private class CustomTransformer : DynamicRouteValueTransformer
|
private class CustomTransformer : DynamicRouteValueTransformer
|
||||||
{
|
{
|
||||||
public Func<HttpContext, RouteValueDictionary, ValueTask<RouteValueDictionary>> Transform { get; set; }
|
public Func<HttpContext, RouteValueDictionary, object, ValueTask<RouteValueDictionary>> Transform { get; set; }
|
||||||
|
|
||||||
|
public Func<HttpContext, RouteValueDictionary, object, IReadOnlyList<Endpoint>, ValueTask<IReadOnlyList<Endpoint>>> Filter { get; set; }
|
||||||
|
|
||||||
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
|
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
|
||||||
{
|
{
|
||||||
return Transform(httpContext, values);
|
return Transform(httpContext, values, State);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValueTask<IReadOnlyList<Endpoint>> FilterAsync(HttpContext httpContext, RouteValueDictionary values, IReadOnlyList<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
return Filter(httpContext, values, State, endpoints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -299,6 +299,30 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static void MapDynamicPageRoute<TTransformer>(this IEndpointRouteBuilder endpoints, string pattern)
|
public static void MapDynamicPageRoute<TTransformer>(this IEndpointRouteBuilder endpoints, string pattern)
|
||||||
where TTransformer : DynamicRouteValueTransformer
|
where TTransformer : DynamicRouteValueTransformer
|
||||||
|
{
|
||||||
|
MapDynamicPageRoute<TTransformer>(endpoints, pattern, state: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will
|
||||||
|
/// attempt to select a page using the route values produced by <typeparamref name="TTransformer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
|
||||||
|
/// <param name="pattern">The URL pattern of the route.</param>
|
||||||
|
/// <param name="state">A state object to provide to the <typeparamref name="TTransformer" /> instance.</param>
|
||||||
|
/// <typeparam name="TTransformer">The type of a <see cref="DynamicRouteValueTransformer"/>.</typeparam>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This method allows the registration of a <see cref="RouteEndpoint"/> and <see cref="DynamicRouteValueTransformer"/>
|
||||||
|
/// that combine to dynamically select a page using custom logic.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The instance of <typeparamref name="TTransformer"/> will be retrieved from the dependency injection container.
|
||||||
|
/// Register <typeparamref name="TTransformer"/> with the desired service lifetime in <c>ConfigureServices</c>.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static void MapDynamicPageRoute<TTransformer>(this IEndpointRouteBuilder endpoints, string pattern, object state)
|
||||||
|
where TTransformer : DynamicRouteValueTransformer
|
||||||
{
|
{
|
||||||
if (endpoints == null)
|
if (endpoints == null)
|
||||||
{
|
{
|
||||||
|
|
@ -316,14 +340,14 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
GetOrCreateDataSource(endpoints).CreateInertEndpoints = true;
|
GetOrCreateDataSource(endpoints).CreateInertEndpoints = true;
|
||||||
|
|
||||||
endpoints.Map(
|
endpoints.Map(
|
||||||
pattern,
|
pattern,
|
||||||
context =>
|
context =>
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
|
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
|
||||||
})
|
})
|
||||||
.Add(b =>
|
.Add(b =>
|
||||||
{
|
{
|
||||||
b.Metadata.Add(new DynamicPageRouteValueTransformerMetadata(typeof(TTransformer)));
|
b.Metadata.Add(new DynamicPageRouteValueTransformerMetadata(typeof(TTransformer), state));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -105,13 +105,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
// no realistic way this could happen.
|
// no realistic way this could happen.
|
||||||
var dynamicPageMetadata = endpoint.Metadata.GetMetadata<DynamicPageMetadata>();
|
var dynamicPageMetadata = endpoint.Metadata.GetMetadata<DynamicPageMetadata>();
|
||||||
var transformerMetadata = endpoint.Metadata.GetMetadata<DynamicPageRouteValueTransformerMetadata>();
|
var transformerMetadata = endpoint.Metadata.GetMetadata<DynamicPageRouteValueTransformerMetadata>();
|
||||||
|
DynamicRouteValueTransformer transformer = null;
|
||||||
if (dynamicPageMetadata != null)
|
if (dynamicPageMetadata != null)
|
||||||
{
|
{
|
||||||
dynamicValues = dynamicPageMetadata.Values;
|
dynamicValues = dynamicPageMetadata.Values;
|
||||||
}
|
}
|
||||||
else if (transformerMetadata != null)
|
else if (transformerMetadata != null)
|
||||||
{
|
{
|
||||||
var transformer = (DynamicRouteValueTransformer)httpContext.RequestServices.GetRequiredService(transformerMetadata.SelectorType);
|
transformer = (DynamicRouteValueTransformer)httpContext.RequestServices.GetRequiredService(transformerMetadata.SelectorType);
|
||||||
|
if (transformer.State != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatStateShouldBeNullForRouteValueTransformers(transformerMetadata.SelectorType.Name));
|
||||||
|
}
|
||||||
|
transformer.State = transformerMetadata.State;
|
||||||
dynamicValues = await transformer.TransformAsync(httpContext, originalValues);
|
dynamicValues = await transformer.TransformAsync(httpContext, originalValues);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -154,6 +160,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transformer != null)
|
||||||
|
{
|
||||||
|
endpoints = await transformer.FilterAsync(httpContext, values, endpoints);
|
||||||
|
if (endpoints.Count == 0)
|
||||||
|
{
|
||||||
|
candidates.ReplaceEndpoint(i, null, null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update the route values
|
// Update the route values
|
||||||
candidates.ReplaceEndpoint(i, endpoint, values);
|
candidates.ReplaceEndpoint(i, endpoint, values);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
internal class DynamicPageRouteValueTransformerMetadata : IDynamicEndpointMetadata
|
internal class DynamicPageRouteValueTransformerMetadata : IDynamicEndpointMetadata
|
||||||
{
|
{
|
||||||
public DynamicPageRouteValueTransformerMetadata(Type selectorType)
|
public DynamicPageRouteValueTransformerMetadata(Type selectorType, object state)
|
||||||
{
|
{
|
||||||
if (selectorType == null)
|
if (selectorType == null)
|
||||||
{
|
{
|
||||||
|
|
@ -24,10 +24,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectorType = selectorType;
|
SelectorType = selectorType;
|
||||||
|
State = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsDynamic => true;
|
public bool IsDynamic => true;
|
||||||
|
|
||||||
|
public object State { get; }
|
||||||
|
|
||||||
public Type SelectorType { get; }
|
public Type SelectorType { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,4 +153,7 @@
|
||||||
<data name="InvalidActionDescriptorModelType" xml:space="preserve">
|
<data name="InvalidActionDescriptorModelType" xml:space="preserve">
|
||||||
<value>The model type for '{0}' is of type '{1}' which is not assignable to its declared model type '{2}'.</value>
|
<value>The model type for '{0}' is of type '{1}' which is not assignable to its declared model type '{2}'.</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
<data name="StateShouldBeNullForRouteValueTransformers" xml:space="preserve">
|
||||||
|
<value>Transformer '{0}' was retrieved from dependency injection with a state value. State can only be specified when the dynamic route is mapped using MapDynamicPageRoute's state argument together with transient lifetime transformer. Ensure that '{0}' doesn't set its own state and that the transformer is registered with a transient lifetime in dependency injection.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
|
|
@ -30,6 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
["page"] = "/Index",
|
["page"] = "/Index",
|
||||||
},
|
},
|
||||||
|
DisplayName = "/Index",
|
||||||
},
|
},
|
||||||
new PageActionDescriptor()
|
new PageActionDescriptor()
|
||||||
{
|
{
|
||||||
|
|
@ -37,6 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
["page"] = "/About",
|
["page"] = "/About",
|
||||||
},
|
},
|
||||||
|
DisplayName = "/About"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
_ => Task.CompletedTask,
|
_ => Task.CompletedTask,
|
||||||
new EndpointMetadataCollection(new object[]
|
new EndpointMetadataCollection(new object[]
|
||||||
{
|
{
|
||||||
new DynamicPageRouteValueTransformerMetadata(typeof(CustomTransformer)),
|
new DynamicPageRouteValueTransformerMetadata(typeof(CustomTransformer), State),
|
||||||
}),
|
}),
|
||||||
"dynamic");
|
"dynamic");
|
||||||
|
|
||||||
|
|
@ -60,24 +60,38 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddRouting();
|
services.AddRouting();
|
||||||
services.AddScoped<CustomTransformer>(s =>
|
services.AddTransient<CustomTransformer>(s =>
|
||||||
{
|
{
|
||||||
var transformer = new CustomTransformer();
|
var transformer = new CustomTransformer();
|
||||||
transformer.Transform = (c, values) => Transform(c, values);
|
transformer.Transform = (c, values, state) => Transform(c, values, state);
|
||||||
|
transformer.Filter = (c, values, state, endpoints) => Filter(c, values, state, endpoints);
|
||||||
return transformer;
|
return transformer;
|
||||||
});
|
});
|
||||||
Services = services.BuildServiceProvider();
|
Services = services.BuildServiceProvider();
|
||||||
|
|
||||||
Comparer = Services.GetRequiredService<EndpointMetadataComparer>();
|
Comparer = Services.GetRequiredService<EndpointMetadataComparer>();
|
||||||
|
|
||||||
LoadedEndpoint = new Endpoint(_ => Task.CompletedTask, EndpointMetadataCollection.Empty, "Loaded");
|
LoadedEndpoints = new[]
|
||||||
|
{
|
||||||
|
new Endpoint(_ => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test1"),
|
||||||
|
new Endpoint(_ => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test2"),
|
||||||
|
new Endpoint(_ => Task.CompletedTask, EndpointMetadataCollection.Empty, "ReplacedLoaded")
|
||||||
|
};
|
||||||
|
|
||||||
var loader = new Mock<PageLoader>();
|
var loader = new Mock<PageLoader>();
|
||||||
loader
|
loader
|
||||||
.Setup(l => l.LoadAsync(It.IsAny<PageActionDescriptor>()))
|
.Setup(l => l.LoadAsync(It.IsAny<PageActionDescriptor>()))
|
||||||
.Returns(Task.FromResult(new CompiledPageActionDescriptor() { Endpoint = LoadedEndpoint, }));
|
.Returns<PageActionDescriptor>(descriptor => Task.FromResult(new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
Endpoint = descriptor.DisplayName switch
|
||||||
|
{
|
||||||
|
"/Index" => LoadedEndpoints[0],
|
||||||
|
"/About" => LoadedEndpoints[1],
|
||||||
|
"/ReplacedEndpoint" => LoadedEndpoints[2],
|
||||||
|
_ => throw new InvalidOperationException($"Invalid endpoint '{descriptor.DisplayName}'.")
|
||||||
|
}
|
||||||
|
}));
|
||||||
Loader = loader.Object;
|
Loader = loader.Object;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private EndpointMetadataComparer Comparer { get; }
|
private EndpointMetadataComparer Comparer { get; }
|
||||||
|
|
@ -88,15 +102,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
private Endpoint DynamicEndpoint { get; }
|
private Endpoint DynamicEndpoint { get; }
|
||||||
|
|
||||||
private Endpoint LoadedEndpoint { get; }
|
private Endpoint [] LoadedEndpoints { get; }
|
||||||
|
|
||||||
private PageLoader Loader { get; }
|
private PageLoader Loader { get; }
|
||||||
|
|
||||||
private DynamicPageEndpointSelector Selector { get; }
|
private DynamicPageEndpointSelector Selector { get; }
|
||||||
|
|
||||||
|
private object State { get; }
|
||||||
|
|
||||||
private IServiceProvider Services { get; }
|
private IServiceProvider Services { get; }
|
||||||
|
|
||||||
private Func<HttpContext, RouteValueDictionary, ValueTask<RouteValueDictionary>> Transform { get; set; }
|
private Func<HttpContext, RouteValueDictionary, object, ValueTask<RouteValueDictionary>> Transform { get; set; }
|
||||||
|
|
||||||
|
private Func<HttpContext, RouteValueDictionary, object, IReadOnlyList<Endpoint>, ValueTask<IReadOnlyList<Endpoint>>> Filter { get; set; } = (_, __, ___, e) => new ValueTask<IReadOnlyList<Endpoint>>(e);
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ApplyAsync_NoMatch()
|
public async Task ApplyAsync_NoMatch()
|
||||||
|
|
@ -111,7 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var candidates = new CandidateSet(endpoints, values, scores);
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
candidates.SetValidity(0, false);
|
candidates.SetValidity(0, false);
|
||||||
|
|
||||||
Transform = (c, values) =>
|
Transform = (c, values, state) =>
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
};
|
};
|
||||||
|
|
@ -140,7 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
var candidates = new CandidateSet(endpoints, values, scores);
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
Transform = (c, values) =>
|
Transform = (c, values, state) =>
|
||||||
{
|
{
|
||||||
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary());
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary());
|
||||||
};
|
};
|
||||||
|
|
@ -171,7 +189,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
var candidates = new CandidateSet(endpoints, values, scores);
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
Transform = (c, values) =>
|
Transform = (c, values, state) =>
|
||||||
{
|
{
|
||||||
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
{
|
{
|
||||||
|
|
@ -188,7 +206,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
await policy.ApplyAsync(httpContext, candidates);
|
await policy.ApplyAsync(httpContext, candidates);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(LoadedEndpoint, candidates[0].Endpoint);
|
Assert.Same(LoadedEndpoints[0], candidates[0].Endpoint);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
candidates[0].Values.OrderBy(kvp => kvp.Key),
|
candidates[0].Values.OrderBy(kvp => kvp.Key),
|
||||||
kvp =>
|
kvp =>
|
||||||
|
|
@ -211,11 +229,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
var candidates = new CandidateSet(endpoints, values, scores);
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
Transform = (c, values) =>
|
Transform = (c, values, state) =>
|
||||||
{
|
{
|
||||||
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
{
|
{
|
||||||
page = "/Index",
|
page = "/Index",
|
||||||
|
state
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -228,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
await policy.ApplyAsync(httpContext, candidates);
|
await policy.ApplyAsync(httpContext, candidates);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(LoadedEndpoint, candidates[0].Endpoint);
|
Assert.Same(LoadedEndpoints[0], candidates[0].Endpoint);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
candidates[0].Values.OrderBy(kvp => kvp.Key),
|
candidates[0].Values.OrderBy(kvp => kvp.Key),
|
||||||
kvp =>
|
kvp =>
|
||||||
|
|
@ -240,10 +259,185 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
Assert.Equal("slug", kvp.Key);
|
Assert.Equal("slug", kvp.Key);
|
||||||
Assert.Equal("test", kvp.Value);
|
Assert.Equal("test", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("state", kvp.Key);
|
||||||
|
Assert.Same(State, kvp.Value);
|
||||||
});
|
});
|
||||||
Assert.True(candidates.IsValidCandidate(0));
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_Throws_ForTransformersWithInvalidLifetime()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var policy = new DynamicPageEndpointMatcherPolicy(Selector, Loader, Comparer);
|
||||||
|
|
||||||
|
var endpoints = new[] { DynamicEndpoint, };
|
||||||
|
var values = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
|
||||||
|
var scores = new[] { 0, };
|
||||||
|
|
||||||
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
|
Transform = (c, values, state) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
|
{
|
||||||
|
page = "/Index",
|
||||||
|
state
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = new ServiceCollection().AddScoped(sp => new CustomTransformer() { State = "Invalid" }).BuildServiceProvider()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(() => policy.ApplyAsync(httpContext, candidates));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_CanDiscardFoundEndpoints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var policy = new DynamicPageEndpointMatcherPolicy(Selector, Loader, Comparer);
|
||||||
|
|
||||||
|
var endpoints = new[] { DynamicEndpoint, };
|
||||||
|
var values = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
|
||||||
|
var scores = new[] { 0, };
|
||||||
|
|
||||||
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
|
Transform = (c, values, state) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
|
{
|
||||||
|
page = "/Index",
|
||||||
|
state
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter = (c, values, state, endpoints) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<IReadOnlyList<Endpoint>>(Array.Empty<Endpoint>());
|
||||||
|
};
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = Services,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_CanReplaceFoundEndpoints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var policy = new DynamicPageEndpointMatcherPolicy(Selector, Loader, Comparer);
|
||||||
|
|
||||||
|
var endpoints = new[] { DynamicEndpoint, };
|
||||||
|
var values = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
|
||||||
|
var scores = new[] { 0, };
|
||||||
|
|
||||||
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
|
Transform = (c, values, state) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
|
{
|
||||||
|
page = "/Index",
|
||||||
|
state
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter = (c, values, state, endpoints) => new ValueTask<IReadOnlyList<Endpoint>>(new[]
|
||||||
|
{
|
||||||
|
new Endpoint((ctx) => Task.CompletedTask, new EndpointMetadataCollection(new PageActionDescriptor()
|
||||||
|
{
|
||||||
|
DisplayName = "/ReplacedEndpoint",
|
||||||
|
}), "ReplacedEndpoint")
|
||||||
|
});
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = Services,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(1, candidates.Count);
|
||||||
|
Assert.Collection(
|
||||||
|
candidates[0].Values.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("page", kvp.Key);
|
||||||
|
Assert.Equal("/Index", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("slug", kvp.Key);
|
||||||
|
Assert.Equal("test", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("state", kvp.Key);
|
||||||
|
Assert.Same(State, kvp.Value);
|
||||||
|
});
|
||||||
|
Assert.Equal("ReplacedLoaded", candidates[0].Endpoint.DisplayName);
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_CanExpandTheListOfFoundEndpoints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var policy = new DynamicPageEndpointMatcherPolicy(Selector, Loader, Comparer);
|
||||||
|
|
||||||
|
var endpoints = new[] { DynamicEndpoint, };
|
||||||
|
var values = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
|
||||||
|
var scores = new[] { 0, };
|
||||||
|
|
||||||
|
var candidates = new CandidateSet(endpoints, values, scores);
|
||||||
|
|
||||||
|
Transform = (c, values, state) =>
|
||||||
|
{
|
||||||
|
return new ValueTask<RouteValueDictionary>(new RouteValueDictionary(new
|
||||||
|
{
|
||||||
|
page = "/Index",
|
||||||
|
state
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter = (c, values, state, endpoints) => new ValueTask<IReadOnlyList<Endpoint>>(new[]
|
||||||
|
{
|
||||||
|
PageEndpoints[0], PageEndpoints[1]
|
||||||
|
});
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = Services,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, candidates.Count);
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
Assert.True(candidates.IsValidCandidate(1));
|
||||||
|
Assert.Same(LoadedEndpoints[0], candidates[0].Endpoint);
|
||||||
|
Assert.Same(LoadedEndpoints[1], candidates[1].Endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestDynamicPageEndpointSelector : DynamicPageEndpointSelector
|
private class TestDynamicPageEndpointSelector : DynamicPageEndpointSelector
|
||||||
{
|
{
|
||||||
public TestDynamicPageEndpointSelector(EndpointDataSource dataSource)
|
public TestDynamicPageEndpointSelector(EndpointDataSource dataSource)
|
||||||
|
|
@ -254,11 +448,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
private class CustomTransformer : DynamicRouteValueTransformer
|
private class CustomTransformer : DynamicRouteValueTransformer
|
||||||
{
|
{
|
||||||
public Func<HttpContext, RouteValueDictionary, ValueTask<RouteValueDictionary>> Transform { get; set; }
|
public Func<HttpContext, RouteValueDictionary, object, ValueTask<RouteValueDictionary>> Transform { get; set; }
|
||||||
|
|
||||||
|
public Func<HttpContext, RouteValueDictionary, object, IReadOnlyList<Endpoint>, ValueTask<IReadOnlyList<Endpoint>>> Filter { get; set; }
|
||||||
|
|
||||||
|
public override ValueTask<IReadOnlyList<Endpoint>> FilterAsync(HttpContext httpContext, RouteValueDictionary values, IReadOnlyList<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
return Filter(httpContext, values, State, endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
|
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
|
||||||
{
|
{
|
||||||
return Transform(httpContext, values);
|
return Transform(httpContext, values, State);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
public async Task DynamicController_CanSelectControllerInArea()
|
public async Task DynamicController_CanSelectControllerInArea()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var url = "http://localhost/dynamic/area%3Dadmin,controller%3Ddynamic,action%3Dindex";
|
var url = "http://localhost/v1/dynamic/area%3Dadmin,controller%3Ddynamic,action%3Dindex";
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -71,11 +71,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Equal("Hello from dynamic controller: /link_generation/dynamic/index", content);
|
Assert.Equal("Hello from dynamic controller: /link_generation/dynamic/index", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DynamicController_CanFilterResultsBasedOnState()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var url = "http://localhost/v2/dynamic/area%3Dadmin,controller%3Ddynamic,action%3Dindex";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DynamicController_CanSelectControllerInArea_WithActionConstraints()
|
public async Task DynamicController_CanSelectControllerInArea_WithActionConstraints()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var url = "http://localhost/dynamic/area%3Dadmin,controller%3Ddynamic,action%3Dindex";
|
var url = "http://localhost/v1/dynamic/area%3Dadmin,controller%3Ddynamic,action%3Dindex";
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -91,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
public async Task DynamicPage_CanSelectPage()
|
public async Task DynamicPage_CanSelectPage()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var url = "http://localhost/dynamicpage/page%3D%2FDynamicPage";
|
var url = "http://localhost/v1/dynamicpage/page%3D%2FDynamicPage";
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -103,6 +117,21 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Equal("Hello from dynamic page: /DynamicPage", content);
|
Assert.Equal("Hello from dynamic page: /DynamicPage", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DynamicPage_CanFilterBasedOnState()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var url = "http://localhost/v2/dynamicpage/page%3D%2FDynamicPage";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AppWithDynamicRouteAndMapRazorPages_CanRouteToRazorPage()
|
public async Task AppWithDynamicRouteAndMapRazorPages_CanRouteToRazorPage()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 RoutingWebSite
|
||||||
|
{
|
||||||
|
public class DynamicVersion
|
||||||
|
{
|
||||||
|
public string Version { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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 System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
@ -21,7 +23,7 @@ namespace RoutingWebSite
|
||||||
.AddNewtonsoftJson()
|
.AddNewtonsoftJson()
|
||||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||||
|
|
||||||
services.AddSingleton<Transformer>();
|
services.AddTransient<Transformer>();
|
||||||
|
|
||||||
// Used by some controllers defined in this project.
|
// Used by some controllers defined in this project.
|
||||||
services.Configure<RouteOptions>(options => options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
|
services.Configure<RouteOptions>(options => options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
|
||||||
|
|
@ -32,8 +34,10 @@ namespace RoutingWebSite
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
endpoints.MapDynamicControllerRoute<Transformer>("dynamic/{**slug}");
|
endpoints.MapDynamicControllerRoute<Transformer>("v1/dynamic/{**slug}", new DynamicVersion { Version = "V1" });
|
||||||
endpoints.MapDynamicPageRoute<Transformer>("dynamicpage/{**slug}");
|
endpoints.MapDynamicControllerRoute<Transformer>("v2/dynamic/{**slug}", new DynamicVersion { Version = "V2" });
|
||||||
|
endpoints.MapDynamicPageRoute<Transformer>("v1/dynamicpage/{**slug}", new DynamicVersion { Version = "V1" });
|
||||||
|
endpoints.MapDynamicPageRoute<Transformer>("v2/dynamicpage/{**slug}", new DynamicVersion { Version = "V2" });
|
||||||
|
|
||||||
endpoints.MapControllerRoute("link", "link_generation/{controller}/{action}/{id?}");
|
endpoints.MapControllerRoute("link", "link_generation/{controller}/{action}/{id?}");
|
||||||
|
|
||||||
|
|
@ -59,8 +63,21 @@ namespace RoutingWebSite
|
||||||
results[split[0]] = split[1];
|
results[split[0]] = split[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
results["version"] = ((DynamicVersion)State).Version;
|
||||||
|
|
||||||
return new ValueTask<RouteValueDictionary>(results);
|
return new ValueTask<RouteValueDictionary>(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ValueTask<IReadOnlyList<Endpoint>> FilterAsync(HttpContext httpContext, RouteValueDictionary values, IReadOnlyList<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
var version = ((DynamicVersion)State).Version;
|
||||||
|
if (version == "V2" && version == (string)values["version"])
|
||||||
|
{
|
||||||
|
// For v1 routes this transformer will work fine, for v2 routes, it will filter them.
|
||||||
|
return new ValueTask<IReadOnlyList<Endpoint>>(Array.Empty<Endpoint>());
|
||||||
|
}
|
||||||
|
return base.FilterAsync(httpContext, values, endpoints);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ namespace RoutingWebSite
|
||||||
.AddMvc()
|
.AddMvc()
|
||||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||||
|
|
||||||
services.AddSingleton<Transformer>();
|
services.AddTransient<Transformer>();
|
||||||
|
|
||||||
// Used by some controllers defined in this project.
|
// Used by some controllers defined in this project.
|
||||||
services.Configure<RouteOptions>(options => options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
|
services.Configure<RouteOptions>(options => options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue