Fixes: #7611 - return convention builders for MVC
Adds support for returning convention builders from the various MVC endpoint routing methods. The fallback methods are excluded because the fallback endpoint doesn't actually get executed.
This commit is contained in:
parent
9ac2f52b22
commit
6a99743d33
|
|
@ -3,16 +3,21 @@
|
|||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public sealed partial class ControllerActionEndpointConventionBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder
|
||||
{
|
||||
internal ControllerActionEndpointConventionBuilder() { }
|
||||
public void Add(System.Action<Microsoft.AspNetCore.Builder.EndpointBuilder> convention) { }
|
||||
}
|
||||
public static partial class ControllerEndpointRouteBuilderExtensions
|
||||
{
|
||||
public static void MapAreaControllerRoute(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string name, string areaName, string pattern, object defaults = null, object constraints = null, object dataTokens = null) { }
|
||||
public static void MapControllerRoute(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string name, string pattern, object defaults = null, object constraints = null, object dataTokens = null) { }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapControllers(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapDefaultControllerRoute(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) { throw null; }
|
||||
public static void MapFallbackToAreaController(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string action, string controller, string area) { }
|
||||
public static void MapFallbackToAreaController(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, string action, string controller, string area) { }
|
||||
public static void MapFallbackToController(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string action, string controller) { }
|
||||
public static void MapFallbackToController(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, string action, string controller) { }
|
||||
public static Microsoft.AspNetCore.Builder.ControllerActionEndpointConventionBuilder MapAreaControllerRoute(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string name, string areaName, string pattern, object defaults = null, object constraints = null, object dataTokens = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.ControllerActionEndpointConventionBuilder MapControllerRoute(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string name, string pattern, object defaults = null, object constraints = null, object dataTokens = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.ControllerActionEndpointConventionBuilder MapControllers(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.ControllerActionEndpointConventionBuilder MapDefaultControllerRoute(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToAreaController(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string action, string controller, string area) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToAreaController(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, string action, string controller, string area) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToController(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string action, string controller) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToController(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, string action, string controller) { throw null; }
|
||||
}
|
||||
public static partial class MvcApplicationBuilderExtensions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
// 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.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds conventions that will be used for customization of <see cref="EndpointBuilder"/> instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This interface is used at application startup to customize endpoints for the application.
|
||||
/// </remarks>
|
||||
public sealed class ControllerActionEndpointConventionBuilder : IEndpointConventionBuilder
|
||||
{
|
||||
// The lock is shared with the data source.
|
||||
private readonly object _lock;
|
||||
private readonly List<Action<EndpointBuilder>> _conventions;
|
||||
|
||||
internal ControllerActionEndpointConventionBuilder(object @lock, List<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
_lock = @lock;
|
||||
_conventions = conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified convention to the builder. Conventions are used to customize <see cref="EndpointBuilder"/> instances.
|
||||
/// </summary>
|
||||
/// <param name="convention">The convention to add to the builder.</param>
|
||||
public void Add(Action<EndpointBuilder> convention)
|
||||
{
|
||||
if (convention == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(convention));
|
||||
}
|
||||
|
||||
// The lock is shared with the data source. We want to lock here
|
||||
// to avoid mutating this list while its read in the data source.
|
||||
lock (_lock)
|
||||
{
|
||||
_conventions.Add(convention);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,8 +21,8 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// Adds endpoints for controller actions to the <see cref="IEndpointRouteBuilder"/> without specifying any routes.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with controller actions.</returns>
|
||||
public static IEndpointConventionBuilder MapControllers(this IEndpointRouteBuilder endpoints)
|
||||
/// <returns>An <see cref="ControllerActionEndpointConventionBuilder"/> for endpoints associated with controller actions.</returns>
|
||||
public static ControllerActionEndpointConventionBuilder MapControllers(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
EnsureControllerServices(endpoints);
|
||||
|
||||
return GetOrCreateDataSource(endpoints);
|
||||
return GetOrCreateDataSource(endpoints).DefaultBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -39,8 +39,10 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// <c>{controller=Home}/{action=Index}/{id?}</c>.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with controller actions.</returns>
|
||||
public static IEndpointConventionBuilder MapDefaultControllerRoute(this IEndpointRouteBuilder endpoints)
|
||||
/// <returns>
|
||||
/// An <see cref="ControllerActionEndpointConventionBuilder"/> for endpoints associated with controller actions for this route.
|
||||
/// </returns>
|
||||
public static ControllerActionEndpointConventionBuilder MapDefaultControllerRoute(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
|
|
@ -50,14 +52,12 @@ namespace Microsoft.AspNetCore.Builder
|
|||
EnsureControllerServices(endpoints);
|
||||
|
||||
var dataSource = GetOrCreateDataSource(endpoints);
|
||||
dataSource.AddRoute(
|
||||
return dataSource.AddRoute(
|
||||
"default",
|
||||
"{controller=Home}/{action=Index}/{id?}",
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null);
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -80,7 +80,10 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// An object that contains data tokens for the route. The object's properties represent the names and
|
||||
/// values of the data tokens.
|
||||
/// </param>
|
||||
public static void MapControllerRoute(
|
||||
/// <returns>
|
||||
/// An <see cref="ControllerActionEndpointConventionBuilder"/> for endpoints associated with controller actions for this route.
|
||||
/// </returns>
|
||||
public static ControllerActionEndpointConventionBuilder MapControllerRoute(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string name,
|
||||
string pattern,
|
||||
|
|
@ -96,7 +99,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
EnsureControllerServices(endpoints);
|
||||
|
||||
var dataSource = GetOrCreateDataSource(endpoints);
|
||||
dataSource.AddRoute(
|
||||
return dataSource.AddRoute(
|
||||
name,
|
||||
pattern,
|
||||
new RouteValueDictionary(defaults),
|
||||
|
|
@ -125,7 +128,10 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// An object that contains data tokens for the route. The object's properties represent the names and
|
||||
/// values of the data tokens.
|
||||
/// </param>
|
||||
public static void MapAreaControllerRoute(
|
||||
/// <returns>
|
||||
/// An <see cref="ControllerActionEndpointConventionBuilder"/> for endpoints associated with controller actions for this route.
|
||||
/// </returns>
|
||||
public static ControllerActionEndpointConventionBuilder MapAreaControllerRoute(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string name,
|
||||
string areaName,
|
||||
|
|
@ -150,7 +156,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
var constraintsDictionary = new RouteValueDictionary(constraints);
|
||||
constraintsDictionary["area"] = constraintsDictionary["area"] ?? new StringRouteConstraint(areaName);
|
||||
|
||||
endpoints.MapControllerRoute(name, pattern, defaultsDictionary, constraintsDictionary, dataTokens);
|
||||
return endpoints.MapControllerRoute(name, pattern, defaultsDictionary, constraintsDictionary, dataTokens);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -183,7 +189,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// actions match these values, the result is implementation defined.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static void MapFallbackToController(
|
||||
public static IEndpointConventionBuilder MapFallbackToController(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string action,
|
||||
string controller)
|
||||
|
|
@ -210,11 +216,13 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
// Maps a fallback endpoint with an empty delegate. This is OK because
|
||||
// we don't expect the delegate to run.
|
||||
endpoints.MapFallback(context => Task.CompletedTask).Add(b =>
|
||||
var builder = endpoints.MapFallback(context => Task.CompletedTask);
|
||||
builder.Add(b =>
|
||||
{
|
||||
// MVC registers a policy that looks for this metadata.
|
||||
b.Metadata.Add(CreateDynamicControllerMetadata(action, controller, area: null));
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -251,7 +259,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// actions match these values, the result is implementation defined.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static void MapFallbackToController(
|
||||
public static IEndpointConventionBuilder MapFallbackToController(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string pattern,
|
||||
string action,
|
||||
|
|
@ -284,11 +292,13 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
// Maps a fallback endpoint with an empty delegate. This is OK because
|
||||
// we don't expect the delegate to run.
|
||||
endpoints.MapFallback(pattern, context => Task.CompletedTask).Add(b =>
|
||||
var builder = endpoints.MapFallback(pattern, context => Task.CompletedTask);
|
||||
builder.Add(b =>
|
||||
{
|
||||
// MVC registers a policy that looks for this metadata.
|
||||
b.Metadata.Add(CreateDynamicControllerMetadata(action, controller, area: null));
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -322,7 +332,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// actions match these values, the result is implementation defined.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static void MapFallbackToAreaController(
|
||||
public static IEndpointConventionBuilder MapFallbackToAreaController(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string action,
|
||||
string controller,
|
||||
|
|
@ -350,11 +360,13 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
// Maps a fallback endpoint with an empty delegate. This is OK because
|
||||
// we don't expect the delegate to run.
|
||||
endpoints.MapFallback(context => Task.CompletedTask).Add(b =>
|
||||
var builder = endpoints.MapFallback(context => Task.CompletedTask);
|
||||
builder.Add(b =>
|
||||
{
|
||||
// MVC registers a policy that looks for this metadata.
|
||||
b.Metadata.Add(CreateDynamicControllerMetadata(action, controller, area));
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -392,7 +404,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// actions match these values, the result is implementation defined.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static void MapFallbackToAreaController(
|
||||
public static IEndpointConventionBuilder MapFallbackToAreaController(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string pattern,
|
||||
string action,
|
||||
|
|
@ -426,11 +438,13 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
// Maps a fallback endpoint with an empty delegate. This is OK because
|
||||
// we don't expect the delegate to run.
|
||||
endpoints.MapFallback(pattern, context => Task.CompletedTask).Add(b =>
|
||||
var builder = endpoints.MapFallback(pattern, context => Task.CompletedTask);
|
||||
builder.Add(b =>
|
||||
{
|
||||
// MVC registers a policy that looks for this metadata.
|
||||
b.Metadata.Add(CreateDynamicControllerMetadata(action, controller, area));
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static DynamicControllerMetadata CreateDynamicControllerMetadata(string action, string controller, string area)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ using Microsoft.Extensions.Primitives;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
internal abstract class ActionEndpointDataSourceBase : EndpointDataSource, IEndpointConventionBuilder, IDisposable
|
||||
internal abstract class ActionEndpointDataSourceBase : EndpointDataSource, IDisposable
|
||||
{
|
||||
private readonly IActionDescriptorCollectionProvider _actions;
|
||||
|
||||
|
|
@ -23,19 +23,20 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
// all of the threading behaviors.
|
||||
protected readonly object Lock = new object();
|
||||
|
||||
// Protected for READS and WRITES.
|
||||
protected readonly List<Action<EndpointBuilder>> Conventions;
|
||||
|
||||
private List<Endpoint> _endpoints;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private IChangeToken _changeToken;
|
||||
private IDisposable _disposable;
|
||||
|
||||
// Protected for READS and WRITES.
|
||||
private readonly List<Action<EndpointBuilder>> _conventions;
|
||||
|
||||
public ActionEndpointDataSourceBase(IActionDescriptorCollectionProvider actions)
|
||||
{
|
||||
_actions = actions;
|
||||
|
||||
_conventions = new List<Action<EndpointBuilder>>();
|
||||
Conventions = new List<Action<EndpointBuilder>>();
|
||||
}
|
||||
|
||||
public override IReadOnlyList<Endpoint> Endpoints
|
||||
|
|
@ -67,19 +68,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
}
|
||||
}
|
||||
|
||||
public void Add(Action<EndpointBuilder> convention)
|
||||
{
|
||||
if (convention == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(convention));
|
||||
}
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
_conventions.Add(convention);
|
||||
}
|
||||
}
|
||||
|
||||
public override IChangeToken GetChangeToken()
|
||||
{
|
||||
Initialize();
|
||||
|
|
@ -113,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
{
|
||||
lock (Lock)
|
||||
{
|
||||
var endpoints = CreateEndpoints(_actions.ActionDescriptors.Items, _conventions);
|
||||
var endpoints = CreateEndpoints(_actions.ActionDescriptors.Items, Conventions);
|
||||
|
||||
// See comments in DefaultActionDescriptorCollectionProvider. These steps are done
|
||||
// in a specific order to ensure callers always see a consistent state.
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
route.DataTokens,
|
||||
suppressLinkGeneration: true,
|
||||
suppressPathMatching: false,
|
||||
conventions);
|
||||
conventions,
|
||||
route.Conventions);
|
||||
endpoints.Add(builder);
|
||||
}
|
||||
}
|
||||
|
|
@ -109,12 +110,17 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
dataTokens: null,
|
||||
action.AttributeRouteInfo.SuppressLinkGeneration,
|
||||
action.AttributeRouteInfo.SuppressPathMatching,
|
||||
conventions);
|
||||
conventions,
|
||||
perRouteConventions: Array.Empty<Action<EndpointBuilder>>());
|
||||
endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddConventionalLinkGenerationRoute(List<Endpoint> endpoints, HashSet<string> keys, ConventionalRouteEntry route, IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
public void AddConventionalLinkGenerationRoute(
|
||||
List<Endpoint> endpoints,
|
||||
HashSet<string> keys,
|
||||
ConventionalRouteEntry route,
|
||||
IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
|
|
@ -177,6 +183,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
conventions[i](builder);
|
||||
}
|
||||
|
||||
for (var i = 0; i < route.Conventions.Count; i++)
|
||||
{
|
||||
route.Conventions[i](builder);
|
||||
}
|
||||
|
||||
endpoints.Add((RouteEndpoint)builder.Build());
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +251,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
RouteValueDictionary dataTokens,
|
||||
bool suppressLinkGeneration,
|
||||
bool suppressPathMatching,
|
||||
IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
IReadOnlyList<Action<EndpointBuilder>> conventions,
|
||||
IReadOnlyList<Action<EndpointBuilder>> perRouteConventions)
|
||||
{
|
||||
|
||||
// We don't want to close over the retrieve the Invoker Factory in ActionEndpointFactory as
|
||||
|
|
@ -340,6 +352,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
conventions[i](builder);
|
||||
}
|
||||
|
||||
for (var i = 0; i < perRouteConventions.Count; i++)
|
||||
{
|
||||
perRouteConventions[i](builder);
|
||||
}
|
||||
|
||||
return (RouteEndpoint)builder.Build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
: base(actions)
|
||||
{
|
||||
_endpointFactory = endpointFactory;
|
||||
|
||||
|
||||
_routes = new List<ConventionalRouteEntry>();
|
||||
|
||||
// In traditional conventional routing setup, the routes defined by a user have a order
|
||||
|
|
@ -38,13 +38,16 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
|
||||
_order = 1;
|
||||
|
||||
DefaultBuilder = new ControllerActionEndpointConventionBuilder(Lock, Conventions);
|
||||
|
||||
// IMPORTANT: this needs to be the last thing we do in the constructor.
|
||||
// Change notifications can happen immediately!
|
||||
Subscribe();
|
||||
}
|
||||
|
||||
public ControllerActionEndpointConventionBuilder DefaultBuilder { get; }
|
||||
|
||||
public void AddRoute(
|
||||
public ControllerActionEndpointConventionBuilder AddRoute(
|
||||
string routeName,
|
||||
string pattern,
|
||||
RouteValueDictionary defaults,
|
||||
|
|
@ -53,7 +56,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
{
|
||||
lock (Lock)
|
||||
{
|
||||
_routes.Add(new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens, _order++));
|
||||
var conventions = new List<Action<EndpointBuilder>>();
|
||||
_routes.Add(new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens, _order++, conventions));
|
||||
return new ControllerActionEndpointConventionBuilder(Lock, conventions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
public readonly string RouteName;
|
||||
public readonly RouteValueDictionary DataTokens;
|
||||
public readonly int Order;
|
||||
public readonly IReadOnlyList<Action<EndpointBuilder>> Conventions;
|
||||
|
||||
public ConventionalRouteEntry(
|
||||
string routeName,
|
||||
|
|
@ -22,11 +24,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
RouteValueDictionary defaults,
|
||||
IDictionary<string, object> constraints,
|
||||
RouteValueDictionary dataTokens,
|
||||
int order)
|
||||
int order,
|
||||
List<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
RouteName = routeName;
|
||||
DataTokens = dataTokens;
|
||||
Order = order;
|
||||
Conventions = conventions;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
|
||||
private RouteEndpoint CreateConventionalRoutedEndpoint(ActionDescriptor action, string template)
|
||||
{
|
||||
return CreateConventionalRoutedEndpoint(action, new ConventionalRouteEntry(routeName: null, template, null, null, null, order: 0));
|
||||
return CreateConventionalRoutedEndpoint(action, new ConventionalRouteEntry(routeName: null, template, null, null, null, order: 0, new List<Action<EndpointBuilder>>()));
|
||||
}
|
||||
|
||||
private RouteEndpoint CreateConventionalRoutedEndpoint(ActionDescriptor action, ConventionalRouteEntry route)
|
||||
|
|
@ -341,9 +341,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
RouteValueDictionary defaults = null,
|
||||
IDictionary<string, object> constraints = null,
|
||||
RouteValueDictionary dataTokens = null,
|
||||
int order = 0)
|
||||
int order = 0,
|
||||
List<Action<EndpointBuilder>> conventions = null)
|
||||
{
|
||||
return new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens, order);
|
||||
conventions ??= new List<Action<EndpointBuilder>>();
|
||||
return new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens, order, conventions);
|
||||
}
|
||||
|
||||
private ActionDescriptor CreateActionDescriptor(
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
dataSource.AddRoute("1", "/1/{controller}/{action}/{id?}", null, null, null);
|
||||
dataSource.AddRoute("2", "/2/{controller}/{action}/{id?}", null, null, null);
|
||||
|
||||
dataSource.Add((b) =>
|
||||
dataSource.DefaultBuilder.Add((b) =>
|
||||
{
|
||||
b.Metadata.Add("Hi there");
|
||||
});
|
||||
|
|
@ -201,6 +201,87 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_AppliesConventions_RouteSpecificMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>
|
||||
{
|
||||
new ControllerActionDescriptor
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo()
|
||||
{
|
||||
Template = "/test",
|
||||
},
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Test" },
|
||||
{ "controller", "Test" },
|
||||
},
|
||||
},
|
||||
new ControllerActionDescriptor
|
||||
{
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Index" },
|
||||
{ "controller", "Home" },
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(actions, 0));
|
||||
|
||||
var dataSource = (ControllerActionEndpointDataSource)CreateDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.AddRoute("1", "/1/{controller}/{action}/{id?}", null, null, null).Add(b => b.Metadata.Add("A"));
|
||||
dataSource.AddRoute("2", "/2/{controller}/{action}/{id?}", null, null, null).Add(b => b.Metadata.Add("B"));
|
||||
|
||||
dataSource.DefaultBuilder.Add((b) =>
|
||||
{
|
||||
b.Metadata.Add("Hi there");
|
||||
});
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints.OfType<RouteEndpoint>().Where(e => !SupportsLinkGeneration(e)).OrderBy(e => e.RoutePattern.RawText),
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/1/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal(new[] { "Hi there", "A" }, e.Metadata.GetOrderedMetadata<string>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/2/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal(new[] { "Hi there", "B" }, e.Metadata.GetOrderedMetadata<string>());
|
||||
});
|
||||
|
||||
Assert.Collection(
|
||||
endpoints.OfType<RouteEndpoint>().Where(e => SupportsLinkGeneration(e)).OrderBy(e => e.RoutePattern.RawText),
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/1/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Null(e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal(new[] { "Hi there", "A" }, e.Metadata.GetOrderedMetadata<string>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/2/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Null(e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal(new[] { "Hi there", "B" }, e.Metadata.GetOrderedMetadata<string>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/test", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[0], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal("Hi there", e.Metadata.GetMetadata<string>());
|
||||
});
|
||||
}
|
||||
|
||||
private static bool SupportsLinkGeneration(RouteEndpoint endpoint)
|
||||
{
|
||||
return !(endpoint.Metadata.GetMetadata<ISuppressLinkGenerationMetadata>()?.SuppressLinkGeneration == true);
|
||||
|
|
|
|||
|
|
@ -3,13 +3,18 @@
|
|||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public sealed partial class PageActionEndpointConventionBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder
|
||||
{
|
||||
internal PageActionEndpointConventionBuilder() { }
|
||||
public void Add(System.Action<Microsoft.AspNetCore.Builder.EndpointBuilder> convention) { }
|
||||
}
|
||||
public static partial class RazorPagesEndpointRouteBuilderExtensions
|
||||
{
|
||||
public static void MapFallbackToAreaPage(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string page, string area) { }
|
||||
public static void MapFallbackToAreaPage(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, string page, string area) { }
|
||||
public static void MapFallbackToPage(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string page) { }
|
||||
public static void MapFallbackToPage(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, string page) { }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapRazorPages(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToAreaPage(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string page, string area) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToAreaPage(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, string page, string area) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToPage(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string page) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToPage(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, string page) { throw null; }
|
||||
public static Microsoft.AspNetCore.Builder.PageActionEndpointConventionBuilder MapRazorPages(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) { throw null; }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
// 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.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds conventions that will be used for customization of <see cref="EndpointBuilder"/> instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This interface is used at application startup to customize endpoints for the application.
|
||||
/// </remarks>
|
||||
public sealed class PageActionEndpointConventionBuilder : IEndpointConventionBuilder
|
||||
{
|
||||
// The lock is shared with the data source.
|
||||
private readonly object _lock;
|
||||
private readonly List<Action<EndpointBuilder>> _conventions;
|
||||
|
||||
internal PageActionEndpointConventionBuilder(object @lock, List<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
_lock = @lock;
|
||||
_conventions = conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified convention to the builder. Conventions are used to customize <see cref="EndpointBuilder"/> instances.
|
||||
/// </summary>
|
||||
/// <param name="convention">The convention to add to the builder.</param>
|
||||
public void Add(Action<EndpointBuilder> convention)
|
||||
{
|
||||
if (convention == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(convention));
|
||||
}
|
||||
|
||||
// The lock is shared with the data source. We want to lock here
|
||||
// to avoid mutating this list while its read in the data source.
|
||||
lock (_lock)
|
||||
{
|
||||
_conventions.Add(convention);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,8 +20,8 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// Adds endpoints for Razor Pages to the <see cref="IEndpointRouteBuilder"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with Razor Pages.</returns>
|
||||
public static IEndpointConventionBuilder MapRazorPages(this IEndpointRouteBuilder endpoints)
|
||||
/// <returns>An <see cref="PageActionEndpointConventionBuilder"/> for endpoints associated with Razor Pages.</returns>
|
||||
public static PageActionEndpointConventionBuilder MapRazorPages(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
EnsureRazorPagesServices(endpoints);
|
||||
|
||||
return GetOrCreateDataSource(endpoints);
|
||||
return GetOrCreateDataSource(endpoints).DefaultBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// will be available.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static void MapFallbackToPage(this IEndpointRouteBuilder endpoints, string page)
|
||||
public static IEndpointConventionBuilder MapFallbackToPage(this IEndpointRouteBuilder endpoints, string page)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
|
|
@ -78,11 +78,13 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
// Maps a fallback endpoint with an empty delegate. This is OK because
|
||||
// we don't expect the delegate to run.
|
||||
endpoints.MapFallback(context => Task.CompletedTask).Add(b =>
|
||||
var builder = endpoints.MapFallback(context => Task.CompletedTask);
|
||||
builder.Add(b =>
|
||||
{
|
||||
// MVC registers a policy that looks for this metadata.
|
||||
b.Metadata.Add(CreateDynamicPageMetadata(page, area: null));
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -113,7 +115,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// <paramref name="pattern"/> will be available.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static void MapFallbackToPage(
|
||||
public static IEndpointConventionBuilder MapFallbackToPage(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string pattern,
|
||||
string page)
|
||||
|
|
@ -142,11 +144,13 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
// Maps a fallback endpoint with an empty delegate. This is OK because
|
||||
// we don't expect the delegate to run.
|
||||
endpoints.MapFallback(pattern, context => Task.CompletedTask).Add(b =>
|
||||
var builder = endpoints.MapFallback(pattern, context => Task.CompletedTask);
|
||||
builder.Add(b =>
|
||||
{
|
||||
// MVC registers a policy that looks for this metadata.
|
||||
b.Metadata.Add(CreateDynamicPageMetadata(page, area: null));
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -174,7 +178,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// will be available.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static void MapFallbackToAreaPage(
|
||||
public static IEndpointConventionBuilder MapFallbackToAreaPage(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string page,
|
||||
string area)
|
||||
|
|
@ -198,11 +202,13 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
// Maps a fallback endpoint with an empty delegate. This is OK because
|
||||
// we don't expect the delegate to run.
|
||||
endpoints.MapFallback(context => Task.CompletedTask).Add(b =>
|
||||
var builder = endpoints.MapFallback(context => Task.CompletedTask);
|
||||
builder.Add(b =>
|
||||
{
|
||||
// MVC registers a policy that looks for this metadata.
|
||||
b.Metadata.Add(CreateDynamicPageMetadata(page, area));
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -234,7 +240,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// <paramref name="pattern"/> will be available.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static void MapFallbackToAreaPage(
|
||||
public static IEndpointConventionBuilder MapFallbackToAreaPage(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string pattern,
|
||||
string page,
|
||||
|
|
@ -264,11 +270,13 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
// Maps a fallback endpoint with an empty delegate. This is OK because
|
||||
// we don't expect the delegate to run.
|
||||
endpoints.MapFallback(pattern, context => Task.CompletedTask).Add(b =>
|
||||
var builder = endpoints.MapFallback(pattern, context => Task.CompletedTask);
|
||||
builder.Add(b =>
|
||||
{
|
||||
// MVC registers a policy that looks for this metadata.
|
||||
b.Metadata.Add(CreateDynamicPageMetadata(page, area));
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static DynamicPageMetadata CreateDynamicPageMetadata(string page, string area)
|
||||
|
|
|
|||
|
|
@ -20,11 +20,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
_endpointFactory = endpointFactory;
|
||||
|
||||
DefaultBuilder = new PageActionEndpointConventionBuilder(Lock, Conventions);
|
||||
|
||||
// IMPORTANT: this needs to be the last thing we do in the constructor.
|
||||
// Change notifications can happen immediately!
|
||||
Subscribe();
|
||||
}
|
||||
|
||||
public PageActionEndpointConventionBuilder DefaultBuilder { get; }
|
||||
|
||||
protected override List<Endpoint> CreateEndpoints(IReadOnlyList<ActionDescriptor> actions, IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
var dataSource = (PageActionEndpointDataSource)CreateDataSource(mockDescriptorProvider.Object);
|
||||
|
||||
dataSource.Add((b) =>
|
||||
dataSource.DefaultBuilder.Add((b) =>
|
||||
{
|
||||
b.Metadata.Add("Hi there");
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue