diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs index d21b481b7e63..4378e5f8665b 100644 --- a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs +++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Concurrent; -using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; @@ -203,15 +202,7 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext } else if (parameterCustomAttributes.OfType().FirstOrDefault() is { } bodyAttribute) { - if (factoryContext.JsonRequestBodyType is not null) - { - throw new InvalidOperationException("Action cannot have more than one FromBody attribute."); - } - - factoryContext.JsonRequestBodyType = parameter.ParameterType; - factoryContext.AllowEmptyRequestBody = bodyAttribute.AllowEmpty; - - return Expression.Convert(BodyValueExpr, parameter.ParameterType); + return BindParameterFromBody(parameter.ParameterType, bodyAttribute.AllowEmpty, factoryContext); } else if (parameter.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType))) { @@ -229,10 +220,14 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext { return BindParameterFromRouteValueOrQueryString(parameter, parameter.Name, factoryContext); } - else + else if (parameter.ParameterType.IsInterface) { return Expression.Call(GetRequiredServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr); } + else + { + return BindParameterFromBody(parameter.ParameterType, allowEmpty: false, factoryContext); + } } private static Expression CreateMethodCall(MethodInfo methodInfo, Expression? target, Expression[] arguments) => @@ -428,7 +423,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall, var invoker = Expression.Lambda>( responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr).Compile(); - var bodyType = factoryContext.JsonRequestBodyType!; + var bodyType = factoryContext.JsonRequestBodyType; object? defaultBodyValue = null; if (factoryContext.AllowEmptyRequestBody && bodyType.IsValueType) @@ -627,6 +622,19 @@ private static Expression BindParameterFromRouteValueOrQueryString(ParameterInfo return BindParameterFromValue(parameter, Expression.Coalesce(routeValue, queryValue), factoryContext); } + private static Expression BindParameterFromBody(Type parameterType, bool allowEmpty, FactoryContext factoryContext) + { + if (factoryContext.JsonRequestBodyType is not null) + { + throw new InvalidOperationException("Action cannot have more than one FromBody attribute."); + } + + factoryContext.JsonRequestBodyType = parameterType; + factoryContext.AllowEmptyRequestBody = allowEmpty; + + return Expression.Convert(BodyValueExpr, parameterType); + } + private static MethodInfo GetMethodInfo(Expression expr) { var mc = (MethodCallExpression)expr.Body; diff --git a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs index 79ece71eddd3..9ec2c2585d73 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs @@ -361,14 +361,8 @@ public static bool TryParse(string? value, out MyTryParsableRecord? result) [MemberData(nameof(TryParsableParameters))] public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromRouteValue(Delegate action, string? routeValue, object? expectedParameterValue) { - var invalidDataException = new InvalidDataException(); - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(LoggerFactory); - var httpContext = new DefaultHttpContext(); httpContext.Request.RouteValues["tryParsable"] = routeValue; - httpContext.Features.Set(new TestHttpRequestLifetimeFeature()); - httpContext.RequestServices = serviceCollection.BuildServiceProvider(); var requestDelegate = RequestDelegateFactory.Create(action); @@ -416,7 +410,7 @@ public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromR Assert.Equal(42, httpContext.Items["tryParsable"]); } - public static object[][] DelegatesWithInvalidAttributes + public static object[][] DelegatesWithAttributesOnNotTryParsableParameters { get { @@ -434,7 +428,7 @@ void InvalidFromHeader([FromHeader] object notTryParsable) { } } [Theory] - [MemberData(nameof(DelegatesWithInvalidAttributes))] + [MemberData(nameof(DelegatesWithAttributesOnNotTryParsableParameters))] public void CreateThrowsInvalidOperationExceptionWhenAttributeRequiresTryParseMethodThatDoesNotExist(Delegate action) { var ex = Assert.Throws(() => RequestDelegateFactory.Create(action)); @@ -460,7 +454,6 @@ void TestAction([FromRoute] int tryParsable, [FromRoute] int tryParsable2) invoked = true; } - var invalidDataException = new InvalidDataException(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(LoggerFactory); @@ -542,20 +535,36 @@ void TestAction([FromHeader(Name = customHeaderName)] int value) Assert.Equal(originalHeaderParam, deserializedRouteParam); } - [Fact] - public async Task RequestDelegatePopulatesFromBodyParameter() + public static object[][] FromBodyActions { - Todo originalTodo = new() + get { - Name = "Write more tests!" - }; + void TestExplicitFromBody(HttpContext httpContext, [FromBody] Todo todo) + { + httpContext.Items.Add("body", todo); + } - Todo? deserializedRequestBody = null; + void TestImpliedFromBody(HttpContext httpContext, Todo myService) + { + httpContext.Items.Add("body", myService); + } - void TestAction([FromBody] Todo todo) - { - deserializedRequestBody = todo; + return new[] + { + new[] { (Action)TestExplicitFromBody }, + new[] { (Action)TestImpliedFromBody }, + }; } + } + + [Theory] + [MemberData(nameof(FromBodyActions))] + public async Task RequestDelegatePopulatesFromBodyParameter(Delegate action) + { + Todo originalTodo = new() + { + Name = "Write more tests!" + }; var httpContext = new DefaultHttpContext(); httpContext.Request.Headers["Content-Type"] = "application/json"; @@ -563,26 +572,24 @@ void TestAction([FromBody] Todo todo) var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo); httpContext.Request.Body = new MemoryStream(requestBodyBytes); - var requestDelegate = RequestDelegateFactory.Create((Action)TestAction); + var requestDelegate = RequestDelegateFactory.Create(action); await requestDelegate(httpContext); + var deserializedRequestBody = httpContext.Items["body"]; Assert.NotNull(deserializedRequestBody); - Assert.Equal(originalTodo.Name, deserializedRequestBody!.Name); + Assert.Equal(originalTodo.Name, ((Todo)deserializedRequestBody!).Name); } - [Fact] - public async Task RequestDelegateRejectsEmptyBodyGivenDefaultFromBodyParameter() + [Theory] + [MemberData(nameof(FromBodyActions))] + public async Task RequestDelegateRejectsEmptyBodyGivenFromBodyParameter(Delegate action) { - void TestAction([FromBody] Todo todo) - { - } - var httpContext = new DefaultHttpContext(); httpContext.Request.Headers["Content-Type"] = "application/json"; httpContext.Request.Headers["Content-Length"] = "0"; - var requestDelegate = RequestDelegateFactory.Create((Action)TestAction); + var requestDelegate = RequestDelegateFactory.Create(action); await Assert.ThrowsAsync(() => requestDelegate(httpContext)); } @@ -702,12 +709,16 @@ void TestAction([FromBody] Todo todo) [Fact] public void BuildRequestDelegateThrowsInvalidOperationExceptionGivenFromBodyOnMultipleParameters() { - void TestAction([FromBody] int value1, [FromBody] int value2) { } + void TestAttributedInvalidAction([FromBody] int value1, [FromBody] int value2) { } + void TestInferredInvalidAction(Todo value1, Todo value2) { } + void TestBothInvalidAction(Todo value1, [FromBody] int value2) { } - Assert.Throws(() => RequestDelegateFactory.Create((Action)TestAction)); + Assert.Throws(() => RequestDelegateFactory.Create((Action)TestAttributedInvalidAction)); + Assert.Throws(() => RequestDelegateFactory.Create((Action)TestInferredInvalidAction)); + Assert.Throws(() => RequestDelegateFactory.Create((Action)TestBothInvalidAction)); } - public static object[][] FromServiceParameter + public static object[][] FromServiceActions { get { @@ -716,7 +727,7 @@ void TestExplicitFromService(HttpContext httpContext, [FromService] MyService my httpContext.Items.Add("service", myService); } - void TestImpliedFromService(HttpContext httpContext, MyService myService) + void TestImpliedFromService(HttpContext httpContext, IMyService myService) { httpContext.Items.Add("service", myService); } @@ -730,13 +741,14 @@ void TestImpliedFromService(HttpContext httpContext, MyService myService) } [Theory] - [MemberData(nameof(FromServiceParameter))] + [MemberData(nameof(FromServiceActions))] public async Task RequestDelegatePopulatesParametersFromServiceWithAndWithoutAttribute(Delegate action) { var myOriginalService = new MyService(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(myOriginalService); + serviceCollection.AddSingleton(myOriginalService); var httpContext = new DefaultHttpContext(); httpContext.RequestServices = serviceCollection.BuildServiceProvider(); @@ -749,11 +761,11 @@ public async Task RequestDelegatePopulatesParametersFromServiceWithAndWithoutAtt } [Theory] - [MemberData(nameof(FromServiceParameter))] + [MemberData(nameof(FromServiceActions))] public async Task RequestDelegateRequiresServiceForAllFromServiceParameters(Delegate action) { var httpContext = new DefaultHttpContext(); - httpContext.RequestServices = (new ServiceCollection()).BuildServiceProvider(); + httpContext.RequestServices = new ServiceCollection().BuildServiceProvider(); var requestDelegate = RequestDelegateFactory.Create((Action)action); @@ -1058,7 +1070,11 @@ private class FromServiceAttribute : Attribute, IFromServiceMetadata { } - private class MyService + private interface IMyService + { + } + + private class MyService : IMyService { } diff --git a/src/Http/Routing/src/Builder/MapActionEndpointConventionBuilder.cs b/src/Http/Routing/src/Builder/MinimalActionEndpointConventionBuilder.cs similarity index 79% rename from src/Http/Routing/src/Builder/MapActionEndpointConventionBuilder.cs rename to src/Http/Routing/src/Builder/MinimalActionEndpointConventionBuilder.cs index 00697b4d87da..2f97505581e6 100644 --- a/src/Http/Routing/src/Builder/MapActionEndpointConventionBuilder.cs +++ b/src/Http/Routing/src/Builder/MinimalActionEndpointConventionBuilder.cs @@ -9,16 +9,16 @@ namespace Microsoft.AspNetCore.Builder /// /// Builds conventions that will be used for customization of MapAction instances. /// - public sealed class MapActionEndpointConventionBuilder : IEndpointConventionBuilder + public sealed class MinimalActionEndpointConventionBuilder : IEndpointConventionBuilder { private readonly List _endpointConventionBuilders; - internal MapActionEndpointConventionBuilder(IEndpointConventionBuilder endpointConventionBuilder) + internal MinimalActionEndpointConventionBuilder(IEndpointConventionBuilder endpointConventionBuilder) { _endpointConventionBuilders = new List() { endpointConventionBuilder }; } - internal MapActionEndpointConventionBuilder(List endpointConventionBuilders) + internal MinimalActionEndpointConventionBuilder(List endpointConventionBuilders) { _endpointConventionBuilders = endpointConventionBuilders; } diff --git a/src/Http/Routing/src/Builder/MapActionEndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs similarity index 92% rename from src/Http/Routing/src/Builder/MapActionEndpointRouteBuilderExtensions.cs rename to src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs index e071bae5ac81..9f52150dca3d 100644 --- a/src/Http/Routing/src/Builder/MapActionEndpointRouteBuilderExtensions.cs +++ b/src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Builder /// /// Provides extension methods for to define HTTP API endpoints. /// - public static class MapActionEndpointRouteBuilderExtensions + public static class MinmalActionEndpointRouteBuilderExtensions { // Avoid creating a new array every call private static readonly string[] GetVerb = new[] { "GET" }; @@ -30,7 +30,7 @@ public static class MapActionEndpointRouteBuilderExtensions /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - public static MapActionEndpointConventionBuilder MapGet( + public static MinimalActionEndpointConventionBuilder MapGet( this IEndpointRouteBuilder endpoints, string pattern, Delegate action) @@ -46,7 +46,7 @@ public static MapActionEndpointConventionBuilder MapGet( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - public static MapActionEndpointConventionBuilder MapPost( + public static MinimalActionEndpointConventionBuilder MapPost( this IEndpointRouteBuilder endpoints, string pattern, Delegate action) @@ -62,7 +62,7 @@ public static MapActionEndpointConventionBuilder MapPost( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that canaction be used to further customize the endpoint. - public static MapActionEndpointConventionBuilder MapPut( + public static MinimalActionEndpointConventionBuilder MapPut( this IEndpointRouteBuilder endpoints, string pattern, Delegate action) @@ -78,7 +78,7 @@ public static MapActionEndpointConventionBuilder MapPut( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - public static MapActionEndpointConventionBuilder MapDelete( + public static MinimalActionEndpointConventionBuilder MapDelete( this IEndpointRouteBuilder endpoints, string pattern, Delegate action) @@ -95,7 +95,7 @@ public static MapActionEndpointConventionBuilder MapDelete( /// The delegate executed when the endpoint is matched. /// HTTP methods that the endpoint will match. /// A that can be used to further customize the endpoint. - public static MapActionEndpointConventionBuilder MapMethods( + public static MinimalActionEndpointConventionBuilder MapMethods( this IEndpointRouteBuilder endpoints, string pattern, IEnumerable httpMethods, @@ -120,7 +120,7 @@ public static MapActionEndpointConventionBuilder MapMethods( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - public static MapActionEndpointConventionBuilder Map( + public static MinimalActionEndpointConventionBuilder Map( this IEndpointRouteBuilder endpoints, string pattern, Delegate action) @@ -136,7 +136,7 @@ public static MapActionEndpointConventionBuilder Map( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - public static MapActionEndpointConventionBuilder Map( + public static MinimalActionEndpointConventionBuilder Map( this IEndpointRouteBuilder endpoints, RoutePattern pattern, Delegate action) @@ -185,7 +185,7 @@ public static MapActionEndpointConventionBuilder Map( endpoints.DataSources.Add(dataSource); } - return new MapActionEndpointConventionBuilder(dataSource.AddEndpointBuilder(builder)); + return new MinimalActionEndpointConventionBuilder(dataSource.AddEndpointBuilder(builder)); } } } diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt index bc8b253e7bfb..a13d9b5db54c 100644 --- a/src/Http/Routing/src/PublicAPI.Unshipped.txt +++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt @@ -5,19 +5,19 @@ *REMOVED*Microsoft.AspNetCore.Routing.IRouteNameMetadata.RouteName.get -> string! *REMOVED*Microsoft.AspNetCore.Routing.RouteNameMetadata.RouteName.get -> string! *REMOVED*Microsoft.AspNetCore.Routing.RouteNameMetadata.RouteNameMetadata(string! routeName) -> void -Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder -Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder.Add(System.Action! convention) -> void -Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions +Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder +Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder.Add(System.Action! convention) -> void +Microsoft.AspNetCore.Builder.MinmalActionEndpointRouteBuilderExtensions Microsoft.AspNetCore.Routing.DataTokensMetadata.DataTokens.get -> System.Collections.Generic.IReadOnlyDictionary! Microsoft.AspNetCore.Routing.DataTokensMetadata.DataTokensMetadata(System.Collections.Generic.IReadOnlyDictionary! dataTokens) -> void Microsoft.AspNetCore.Routing.IDataTokensMetadata.DataTokens.get -> System.Collections.Generic.IReadOnlyDictionary! Microsoft.AspNetCore.Routing.IRouteNameMetadata.RouteName.get -> string? Microsoft.AspNetCore.Routing.RouteNameMetadata.RouteName.get -> string? Microsoft.AspNetCore.Routing.RouteNameMetadata.RouteNameMetadata(string? routeName) -> void -static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, Microsoft.AspNetCore.Routing.Patterns.RoutePattern! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder! -static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder! -static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapDelete(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder! -static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapGet(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder! -static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapMethods(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Collections.Generic.IEnumerable! httpMethods, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder! -static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapPost(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder! -static Microsoft.AspNetCore.Builder.MapActionEndpointRouteBuilderExtensions.MapPut(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MapActionEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.MinmalActionEndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, Microsoft.AspNetCore.Routing.Patterns.RoutePattern! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.MinmalActionEndpointRouteBuilderExtensions.Map(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.MinmalActionEndpointRouteBuilderExtensions.MapDelete(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.MinmalActionEndpointRouteBuilderExtensions.MapGet(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.MinmalActionEndpointRouteBuilderExtensions.MapMethods(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Collections.Generic.IEnumerable! httpMethods, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.MinmalActionEndpointRouteBuilderExtensions.MapPost(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.MinmalActionEndpointRouteBuilderExtensions.MapPut(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! action) -> Microsoft.AspNetCore.Builder.MinimalActionEndpointConventionBuilder! diff --git a/src/Http/Routing/test/FunctionalTests/MapActionTest.cs b/src/Http/Routing/test/FunctionalTests/MinimalActionTest.cs similarity index 98% rename from src/Http/Routing/test/FunctionalTests/MapActionTest.cs rename to src/Http/Routing/test/FunctionalTests/MinimalActionTest.cs index e627307eb7b3..14bc3d370c5b 100644 --- a/src/Http/Routing/test/FunctionalTests/MapActionTest.cs +++ b/src/Http/Routing/test/FunctionalTests/MinimalActionTest.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests { - public class MapActionTest + public class MinimalActionTest { [Fact] public async Task MapPost_FromBodyWorksWithJsonPayload() diff --git a/src/Http/Routing/test/UnitTests/Builder/MapActionEndpointRouteBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs similarity index 98% rename from src/Http/Routing/test/UnitTests/Builder/MapActionEndpointRouteBuilderExtensionsTest.cs rename to src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs index 37c66218c892..02542f3e8eb8 100644 --- a/src/Http/Routing/test/UnitTests/Builder/MapActionEndpointRouteBuilderExtensionsTest.cs +++ b/src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Builder { - public class MapActionEndpointDataSourceBuilderExtensionsTest + public class MinimalActionEndpointDataSourceBuilderExtensionsTest { private ModelEndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder) {