diff --git a/src/Http/Http.Abstractions/src/Extensions/EndpointBuilder.cs b/src/Http/Http.Abstractions/src/Extensions/EndpointBuilder.cs
index 005ecc6ea9a8..3afd75bb4091 100644
--- a/src/Http/Http.Abstractions/src/Extensions/EndpointBuilder.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/EndpointBuilder.cs
@@ -10,6 +10,11 @@ namespace Microsoft.AspNetCore.Builder;
///
public abstract class EndpointBuilder
{
+ ///
+ /// Gets the list of filters that apply to this endpoint.
+ ///
+ public IList> FilterFactories { get; } = new List>();
+
///
/// Gets or sets the delegate used to process requests for the endpoint.
///
diff --git a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
index 43a78d85ac62..cadc3cab2da6 100644
--- a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
+++ b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
@@ -6,6 +6,7 @@ abstract Microsoft.AspNetCore.Http.EndpointFilterInvocationContext.GetArgument Microsoft.AspNetCore.Http.HttpContext!
Microsoft.AspNetCore.Builder.EndpointBuilder.ApplicationServices.get -> System.IServiceProvider!
Microsoft.AspNetCore.Builder.EndpointBuilder.ApplicationServices.set -> void
+Microsoft.AspNetCore.Builder.EndpointBuilder.FilterFactories.get -> System.Collections.Generic.IList!>!
Microsoft.AspNetCore.Http.AsParametersAttribute
Microsoft.AspNetCore.Http.AsParametersAttribute.AsParametersAttribute() -> void
Microsoft.AspNetCore.Http.CookieBuilder.Extensions.get -> System.Collections.Generic.IList!
diff --git a/src/Http/Routing/src/Builder/EndpointFilterExtensions.cs b/src/Http/Routing/src/Builder/EndpointFilterExtensions.cs
index 6205a129a329..91ed5e9c7f4c 100644
--- a/src/Http/Routing/src/Builder/EndpointFilterExtensions.cs
+++ b/src/Http/Routing/src/Builder/EndpointFilterExtensions.cs
@@ -112,13 +112,7 @@ public static TBuilder AddEndpointFilterFactory(this TBuilder builder,
{
builder.Add(endpointBuilder =>
{
- if (endpointBuilder is not RouteEndpointBuilder routeEndpointBuilder)
- {
- return;
- }
-
- routeEndpointBuilder.EndpointFilterFactories ??= new();
- routeEndpointBuilder.EndpointFilterFactories.Add(filterFactory);
+ endpointBuilder.FilterFactories.Add(filterFactory);
});
return builder;
diff --git a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs
index 441545f5eafd..edfd3a004fc3 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs
@@ -1084,7 +1084,15 @@ public static RoutePatternParameterPolicyReference ParameterPolicy(string parame
return ParameterPolicyCore(parameterPolicy);
}
- internal static RoutePattern Combine(RoutePattern? left, RoutePattern right)
+ ///
+ /// Creates a that combines the specified patterns.
+ ///
+ /// A string representing the first part of the route.
+ /// A stirng representing the second part of the route.
+ /// The combined .
+ ///
+ ///
+ public static RoutePattern Combine(RoutePattern? left, RoutePattern right)
{
static IReadOnlyDictionary CombineDictionaries(
IReadOnlyDictionary leftDictionary,
diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt
index 3b87380bf45b..621294b4f017 100644
--- a/src/Http/Routing/src/PublicAPI.Unshipped.txt
+++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt
@@ -33,6 +33,7 @@ static Microsoft.AspNetCore.Routing.LinkGeneratorRouteValuesAddressExtensions.Ge
static Microsoft.AspNetCore.Routing.LinkGeneratorRouteValuesAddressExtensions.GetPathByRouteValues(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, Microsoft.AspNetCore.Http.PathString pathBase = default(Microsoft.AspNetCore.Http.PathString), Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string?
static Microsoft.AspNetCore.Routing.LinkGeneratorRouteValuesAddressExtensions.GetUriByRouteValues(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, Microsoft.AspNetCore.Http.HttpContext! httpContext, string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, string? scheme = null, Microsoft.AspNetCore.Http.HostString? host = null, Microsoft.AspNetCore.Http.PathString? pathBase = null, Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string?
static Microsoft.AspNetCore.Routing.LinkGeneratorRouteValuesAddressExtensions.GetUriByRouteValues(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! values, string! scheme, Microsoft.AspNetCore.Http.HostString host, Microsoft.AspNetCore.Http.PathString pathBase = default(Microsoft.AspNetCore.Http.PathString), Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string?
+static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Combine(Microsoft.AspNetCore.Routing.Patterns.RoutePattern? left, Microsoft.AspNetCore.Routing.Patterns.RoutePattern! right) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern!
static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Parse(string! pattern, Microsoft.AspNetCore.Routing.RouteValueDictionary? defaults, Microsoft.AspNetCore.Routing.RouteValueDictionary? parameterPolicies) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern!
static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Parse(string! pattern, Microsoft.AspNetCore.Routing.RouteValueDictionary? defaults, Microsoft.AspNetCore.Routing.RouteValueDictionary? parameterPolicies, Microsoft.AspNetCore.Routing.RouteValueDictionary? requiredValues) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern!
static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Pattern(Microsoft.AspNetCore.Routing.RouteValueDictionary? defaults, Microsoft.AspNetCore.Routing.RouteValueDictionary? parameterPolicies, System.Collections.Generic.IEnumerable! segments) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern!
diff --git a/src/Http/Routing/src/RouteEndpointBuilder.cs b/src/Http/Routing/src/RouteEndpointBuilder.cs
index 2660c24bb261..3b96d85ff466 100644
--- a/src/Http/Routing/src/RouteEndpointBuilder.cs
+++ b/src/Http/Routing/src/RouteEndpointBuilder.cs
@@ -12,10 +12,6 @@ namespace Microsoft.AspNetCore.Routing;
///
public sealed class RouteEndpointBuilder : EndpointBuilder
{
- // TODO: Make this public as a gettable IReadOnlyList>.
- // AddEndpointFilter will still be the only way to mutate this list.
- internal List>? EndpointFilterFactories { get; set; }
-
///
/// Gets or sets the associated with this endpoint.
///
diff --git a/src/Http/Routing/src/RouteEndpointDataSource.cs b/src/Http/Routing/src/RouteEndpointDataSource.cs
index 476b032fbf2d..64c288819716 100644
--- a/src/Http/Routing/src/RouteEndpointDataSource.cs
+++ b/src/Http/Routing/src/RouteEndpointDataSource.cs
@@ -56,7 +56,7 @@ public RouteHandlerBuilder AddRouteHandler(
if (isFallback)
{
routeAttributes |= RouteAttributes.Fallback;
- }
+ }
_routeEntries.Add(new()
{
@@ -196,7 +196,7 @@ private RouteEndpointBuilder CreateRouteEndpointBuilder(
entrySpecificConvention(builder);
}
- if (isRouteHandler || builder.EndpointFilterFactories is { Count: > 0})
+ if (isRouteHandler || builder.FilterFactories.Count > 0)
{
var routeParamNames = new List(pattern.Parameters.Count);
foreach (var parameter in pattern.Parameters)
@@ -211,7 +211,7 @@ private RouteEndpointBuilder CreateRouteEndpointBuilder(
ThrowOnBadRequest = _throwOnBadRequest,
DisableInferBodyFromParameters = ShouldDisableInferredBodyParameters(entry.HttpMethods),
EndpointMetadata = builder.Metadata,
- EndpointFilterFactories = builder.EndpointFilterFactories,
+ EndpointFilterFactories = builder.FilterFactories.AsReadOnly(),
};
// We ignore the returned EndpointMetadata has been already populated since we passed in non-null EndpointMetadata.
diff --git a/src/Http/Routing/test/UnitTests/RouteEndpointBuilderTest.cs b/src/Http/Routing/test/UnitTests/RouteEndpointBuilderTest.cs
index 1bfc5ecc724c..508871016482 100644
--- a/src/Http/Routing/test/UnitTests/RouteEndpointBuilderTest.cs
+++ b/src/Http/Routing/test/UnitTests/RouteEndpointBuilderTest.cs
@@ -45,8 +45,7 @@ public async void Build_DoesNot_RunFilters()
var builder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder);
- builder.EndpointFilterFactories = new List>();
- builder.EndpointFilterFactories.Add((endopintContext, next) =>
+ builder.FilterFactories.Add((endopintContext, next) =>
{
endpointFilterCallCount++;
diff --git a/src/Mvc/Mvc.Core/src/Controllers/ControllerActionDescriptor.cs b/src/Mvc/Mvc.Core/src/Controllers/ControllerActionDescriptor.cs
index ba84f8389eda..d8a9ee154aa3 100644
--- a/src/Mvc/Mvc.Core/src/Controllers/ControllerActionDescriptor.cs
+++ b/src/Mvc/Mvc.Core/src/Controllers/ControllerActionDescriptor.cs
@@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Internal;
@@ -36,6 +37,8 @@ public class ControllerActionDescriptor : ActionDescriptor
///
public TypeInfo ControllerTypeInfo { get; set; } = default!;
+ internal EndpointFilterDelegate? FilterDelegate { get; set; }
+
// Cache entry so we can avoid an external cache
internal ControllerActionInvokerCacheEntry? CacheEntry { get; set; }
diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/ActionMethodExecutor.cs b/src/Mvc/Mvc.Core/src/Infrastructure/ActionMethodExecutor.cs
index 037d045a986f..57c63674fa39 100644
--- a/src/Mvc/Mvc.Core/src/Infrastructure/ActionMethodExecutor.cs
+++ b/src/Mvc/Mvc.Core/src/Infrastructure/ActionMethodExecutor.cs
@@ -4,8 +4,9 @@
#nullable enable
using System.Diagnostics;
-using Microsoft.AspNetCore.Mvc.Core;
+using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Internal;
+using Resources = Microsoft.AspNetCore.Mvc.Core.Resources;
namespace Microsoft.AspNetCore.Mvc.Infrastructure;
@@ -26,7 +27,10 @@ internal abstract class ActionMethodExecutor
new AwaitableObjectResultExecutor(),
};
+ public static EmptyResult EmptyResultInstance { get; } = new();
+
public abstract ValueTask Execute(
+ ActionContext actionContext,
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
@@ -34,6 +38,8 @@ public abstract ValueTask Execute(
protected abstract bool CanExecute(ObjectMethodExecutor executor);
+ public abstract ValueTask