diff --git a/src/Http/Http.Abstractions/src/Metadata/ApiEndpointMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/ApiEndpointMetadata.cs new file mode 100644 index 000000000000..815bf2c834c9 --- /dev/null +++ b/src/Http/Http.Abstractions/src/Metadata/ApiEndpointMetadata.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Http.Metadata; + +/// +/// Metadata that indicates the endpoint is intended for API clients. +/// When present, authentication handlers should prefer returning status codes over browser redirects. +/// +internal sealed class ApiEndpointMetadata : IApiEndpointMetadata +{ + /// + /// Singleton instance of . + /// + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } +} diff --git a/src/Http/Http.Abstractions/src/Metadata/IApiEndpointMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/IApiEndpointMetadata.cs new file mode 100644 index 000000000000..cadaafcae1a9 --- /dev/null +++ b/src/Http/Http.Abstractions/src/Metadata/IApiEndpointMetadata.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Http.Metadata; + +/// +/// Metadata that indicates the endpoint is an API intended for programmatic access rather than direct browser navigation. +/// When present, authentication handlers should prefer returning status codes over browser redirects. +/// +public interface IApiEndpointMetadata +{ +} diff --git a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj index d383efa6a20e..37e17a0f0f49 100644 --- a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj +++ b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj @@ -35,7 +35,6 @@ Microsoft.AspNetCore.Http.HttpResponse - @@ -61,5 +60,6 @@ Microsoft.AspNetCore.Http.HttpResponse + diff --git a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt index 3a2461306d54..2944a853cdf2 100644 --- a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ #nullable enable +Microsoft.AspNetCore.Http.Metadata.IApiEndpointMetadata Microsoft.AspNetCore.Http.Metadata.IDisableValidationMetadata Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.get -> string? Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.set -> void diff --git a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/RequestDelegateGenerator.cs b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/RequestDelegateGenerator.cs index f4e2ad0e8e33..f5e2abacaaf1 100644 --- a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/RequestDelegateGenerator.cs +++ b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/RequestDelegateGenerator.cs @@ -248,7 +248,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (hasFormBody) { - codeWriter.WriteLine(RequestDelegateGeneratorSources.AntiforgeryMetadataType); + codeWriter.WriteLine(RequestDelegateGeneratorSources.AntiforgeryMetadataClass); + } + + if (hasJsonBody || hasResponseMetadata) + { + codeWriter.WriteLine(RequestDelegateGeneratorSources.ApiEndpointMetadataClass); } if (hasFormBody || hasJsonBody || hasResponseMetadata) diff --git a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/RequestDelegateGeneratorSources.cs b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/RequestDelegateGeneratorSources.cs index d7c7a480446c..b626db274124 100644 --- a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/RequestDelegateGeneratorSources.cs +++ b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/RequestDelegateGeneratorSources.cs @@ -479,19 +479,39 @@ internal ParameterBindingMetadata( } """; - public static string AntiforgeryMetadataType = """ -file sealed class AntiforgeryMetadata : IAntiforgeryMetadata -{ - public static readonly IAntiforgeryMetadata ValidationRequired = new AntiforgeryMetadata(true); - - public AntiforgeryMetadata(bool requiresValidation) + public static string AntiforgeryMetadataClass = """ + file sealed class AntiforgeryMetadata : IAntiforgeryMetadata { - RequiresValidation = requiresValidation; + public static readonly IAntiforgeryMetadata ValidationRequired = new AntiforgeryMetadata(true); + + public AntiforgeryMetadata(bool requiresValidation) + { + RequiresValidation = requiresValidation; + } + + public bool RequiresValidation { get; } } +"""; - public bool RequiresValidation { get; } -} + public static string ApiEndpointMetadataClass = """ + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } """; + public static string GetGeneratedRouteBuilderExtensionsSource(string endpoints, string helperMethods, string helperTypes, ImmutableHashSet verbs) => $$""" {{SourceHeader}} diff --git a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs index 83790d44eb33..97fe2dc990ff 100644 --- a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs +++ b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs @@ -205,7 +205,7 @@ private static void EmitBuiltinResponseTypeMetadata(this Endpoint endpoint, Code return; } - if (!endpoint.Response.IsAwaitable && (response.HasNoResponse || response.IsIResult)) + if (response.HasNoResponse || response.IsIResult) { return; } @@ -215,13 +215,10 @@ private static void EmitBuiltinResponseTypeMetadata(this Endpoint endpoint, Code { codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(string), contentTypes: GeneratedMetadataConstants.PlaintextContentType));"); } - else if (response.IsAwaitable && response.ResponseType == null) - { - codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(void), contentTypes: GeneratedMetadataConstants.PlaintextContentType));"); - } else if (response.ResponseType is { } responseType) { codeWriter.WriteLine($$"""options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof({{responseType.ToDisplayString(EmitterConstants.DisplayFormatWithoutNullability)}}), contentTypes: GeneratedMetadataConstants.JsonContentType));"""); + codeWriter.WriteLine("ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder);"); } } @@ -339,13 +336,15 @@ public static void EmitJsonAcceptsMetadata(this Endpoint endpoint, CodeWriter co codeWriter.WriteLine("if (!serviceProviderIsService.IsService(type))"); codeWriter.StartBlock(); codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType));"); + codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance);"); codeWriter.WriteLine("break;"); codeWriter.EndBlock(); codeWriter.EndBlock(); } else { - codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes: GeneratedMetadataConstants.JsonContentType));"); + codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes: GeneratedMetadataConstants.JsonContentType));"); + codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance);"); } } diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs index 19965ef1fc31..aaebe4179806 100644 --- a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs +++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs @@ -401,7 +401,14 @@ private static Expression[] CreateArgumentsAndInferMetadata(MethodInfo methodInf InferAntiforgeryMetadata(factoryContext); } - PopulateBuiltInResponseTypeMetadata(methodInfo.ReturnType, factoryContext.EndpointBuilder); + // If this endpoint expects a JSON request body, we assume its an API endpoint not intended for browser navigation. + // When present, authentication handlers should prefer returning status codes over browser redirects. + if (factoryContext.JsonRequestBodyParameter is not null) + { + factoryContext.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); + } + + PopulateBuiltInResponseTypeMetadata(methodInfo.ReturnType, factoryContext); // Add metadata provided by the delegate return type and parameter types next, this will be more specific than inferred metadata from above EndpointMetadataPopulator.PopulateMetadata(methodInfo, factoryContext.EndpointBuilder, factoryContext.Parameters); @@ -1023,37 +1030,40 @@ private static Expression CreateParamCheckingResponseWritingMethodCall(Type retu return Expression.Block(localVariables, checkParamAndCallMethod); } - private static void PopulateBuiltInResponseTypeMetadata(Type returnType, EndpointBuilder builder) + private static void PopulateBuiltInResponseTypeMetadata(Type returnType, RequestDelegateFactoryContext factoryContext) { if (returnType.IsByRefLike) { throw GetUnsupportedReturnTypeException(returnType); } - var isAwaitable = false; if (CoercedAwaitableInfo.IsTypeAwaitable(returnType, out var coercedAwaitableInfo)) { returnType = coercedAwaitableInfo.AwaitableInfo.ResultType; - isAwaitable = true; } // Skip void returns and IResults. IResults might implement IEndpointMetadataProvider but otherwise we don't know what it might do. - if (!isAwaitable && (returnType == typeof(void) || typeof(IResult).IsAssignableFrom(returnType))) + if (returnType == typeof(void) || typeof(IResult).IsAssignableFrom(returnType)) { return; } + var builder = factoryContext.EndpointBuilder; + if (returnType == typeof(string)) { builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(type: typeof(string), statusCode: 200, PlaintextContentType)); } - else if (returnType == typeof(void)) - { - builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(returnType, statusCode: 200, PlaintextContentType)); - } else { builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(returnType, statusCode: 200, DefaultAcceptsAndProducesContentType)); + + if (factoryContext.JsonRequestBodyParameter is null) + { + // Since this endpoint responds with JSON, we assume its an API endpoint not intended for browser navigation, + // but we don't want to bother adding this metadata twice if we've already inferred it based on the expected JSON request body. + builder.Metadata.Add(ApiEndpointMetadata.Instance); + } } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs index 083da9bd7aed..7cee04fb8a0f 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs @@ -2533,7 +2533,7 @@ public void Create_AddJsonResponseType_AsMetadata() var @delegate = () => new object(); var result = RequestDelegateFactory.Create(@delegate); - var responseMetadata = Assert.IsAssignableFrom(Assert.Single(result.EndpointMetadata)); + var responseMetadata = Assert.Single(result.EndpointMetadata.OfType()); Assert.Equal("application/json", Assert.Single(responseMetadata.ContentTypes)); Assert.Equal(typeof(object), responseMetadata.Type); @@ -2545,7 +2545,7 @@ public void Create_AddPlaintextResponseType_AsMetadata() var @delegate = () => "Hello"; var result = RequestDelegateFactory.Create(@delegate); - var responseMetadata = Assert.IsAssignableFrom(Assert.Single(result.EndpointMetadata)); + var responseMetadata = Assert.Single(result.EndpointMetadata.OfType()); Assert.Equal("text/plain", Assert.Single(responseMetadata.ContentTypes)); Assert.Equal(typeof(string), responseMetadata.Type); @@ -2683,6 +2683,7 @@ public void Create_CombinesDefaultMetadata_AndMetadataFromReturnTypesImplementin // Assert Assert.Contains(result.EndpointMetadata, m => m is CustomEndpointMetadata { Source: MetadataSource.Caller }); + Assert.DoesNotContain(result.EndpointMetadata, m => m is IProducesResponseTypeMetadata); // Expecting '1' because only initial metadata will be in the metadata list when this metadata item is added Assert.Contains(result.EndpointMetadata, m => m is MetadataCountMetadata { Count: 1 }); } @@ -2705,9 +2706,9 @@ public void Create_CombinesDefaultMetadata_AndMetadataFromTaskWrappedReturnTypes // Assert Assert.Contains(result.EndpointMetadata, m => m is CustomEndpointMetadata { Source: MetadataSource.Caller }); - Assert.Contains(result.EndpointMetadata, m => m is ProducesResponseTypeMetadata { Type: { } type } && type == typeof(CountsDefaultEndpointMetadataResult)); - // Expecting the custom metadata and the implicit metadata associated with a Task-based return type to be inserted - Assert.Contains(result.EndpointMetadata, m => m is MetadataCountMetadata { Count: 2 }); + Assert.DoesNotContain(result.EndpointMetadata, m => m is IProducesResponseTypeMetadata); + // Expecting '1' because only initial metadata will be in the metadata list when this metadata item is added + Assert.Contains(result.EndpointMetadata, m => m is MetadataCountMetadata { Count: 1 }); } [Fact] @@ -2728,9 +2729,9 @@ public void Create_CombinesDefaultMetadata_AndMetadataFromValueTaskWrappedReturn // Assert Assert.Contains(result.EndpointMetadata, m => m is CustomEndpointMetadata { Source: MetadataSource.Caller }); - Assert.Contains(result.EndpointMetadata, m => m is ProducesResponseTypeMetadata { Type: { } type } && type == typeof(CountsDefaultEndpointMetadataResult)); - // Expecting the custom metadata nad hte implicit metadata associated with a Task-based return type to be inserted - Assert.Contains(result.EndpointMetadata, m => m is MetadataCountMetadata { Count: 2 }); + Assert.DoesNotContain(result.EndpointMetadata, m => m is IProducesResponseTypeMetadata); + // Expecting '1' because only initial metadata will be in the metadata list when this metadata item is added + Assert.Contains(result.EndpointMetadata, m => m is MetadataCountMetadata { Count: 1 }); } [Fact] @@ -2751,9 +2752,9 @@ public void Create_CombinesDefaultMetadata_AndMetadataFromFSharpAsyncWrappedRetu // Assert Assert.Contains(result.EndpointMetadata, m => m is CustomEndpointMetadata { Source: MetadataSource.Caller }); - Assert.Contains(result.EndpointMetadata, m => m is IProducesResponseTypeMetadata { Type: { } type } && type == typeof(CountsDefaultEndpointMetadataResult)); + Assert.DoesNotContain(result.EndpointMetadata, m => m is IProducesResponseTypeMetadata); // Expecting '1' because only initial metadata will be in the metadata list when this metadata item is added - Assert.Contains(result.EndpointMetadata, m => m is MetadataCountMetadata { Count: 2 }); + Assert.Contains(result.EndpointMetadata, m => m is MetadataCountMetadata { Count: 1 }); } [Fact] @@ -2824,14 +2825,16 @@ public void Create_CombinesAllMetadata_InCorrectOrder() m => Assert.True(m is AcceptsMetadata am && am.RequestType == typeof(AddsCustomParameterMetadata)), // Inferred ParameterBinding metadata m => Assert.True(m is IParameterBindingMetadata { Name: "param1" }), - // Inferred ProducesResopnseTypeMetadata from RDF for complex type + // Inferred IApiEndpointMetadata from RDF for complex request and response type + m => Assert.True(m is IApiEndpointMetadata), + // Inferred ProducesResponseTypeMetadata from RDF for complex type m => Assert.Equal(typeof(CountsDefaultEndpointMetadataPoco), ((IProducesResponseTypeMetadata)m).Type), // Metadata provided by parameters implementing IEndpointParameterMetadataProvider m => Assert.True(m is ParameterNameMetadata { Name: "param1" }), // Metadata provided by parameters implementing IEndpointMetadataProvider m => Assert.True(m is CustomEndpointMetadata { Source: MetadataSource.Parameter }), // Metadata provided by return type implementing IEndpointMetadataProvider - m => Assert.True(m is MetadataCountMetadata { Count: 6 })); + m => Assert.True(m is MetadataCountMetadata { Count: 7 })); } [Fact] diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/HandlesEndpointsWithAndWithoutDiagnostics.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/HandlesEndpointsWithAndWithoutDiagnostics.generated.txt index ae38db998901..18cf2ce8010b 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/HandlesEndpointsWithAndWithoutDiagnostics.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/HandlesEndpointsWithAndWithoutDiagnostics.generated.txt @@ -225,6 +225,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_NullableReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_NullableReturn.generated.txt index ddfeaf604670..04b1dc6c3bbe 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_NullableReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_NullableReturn.generated.txt @@ -363,6 +363,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_Snapshot.generated.txt index ab79dddfdc12..6c1240e6b8f3 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_Snapshot.generated.txt @@ -2225,6 +2225,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt index 28e8316f4a7e..830c81d1309b 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt @@ -412,6 +412,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_ComplexTypeArrayParam.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_ComplexTypeArrayParam.generated.txt index 3a8e7fe8928b..bd0ceac25240 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_ComplexTypeArrayParam.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_ComplexTypeArrayParam.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -259,6 +260,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_NullableStringArrayParam.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_NullableStringArrayParam.generated.txt index 6365d1485f43..e2c866e2c2bb 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_NullableStringArrayParam.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_NullableStringArrayParam.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -231,6 +232,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_StringArrayParam.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_StringArrayParam.generated.txt index 66c4504e000b..b2fc71b9158a 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_StringArrayParam.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitHeader_StringArrayParam.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -231,6 +232,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_ComplexTypeArrayParam.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_ComplexTypeArrayParam.generated.txt index 860627ce40b0..9aee726d2055 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_ComplexTypeArrayParam.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_ComplexTypeArrayParam.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -259,6 +260,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_IntArrayParam_Optional.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_IntArrayParam_Optional.generated.txt index f09d01273f7e..01d6808eaf6e 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_IntArrayParam_Optional.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_IntArrayParam_Optional.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -259,6 +260,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_IntArrayParam_Optional_QueryNotPresent.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_IntArrayParam_Optional_QueryNotPresent.generated.txt index f09d01273f7e..01d6808eaf6e 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_IntArrayParam_Optional_QueryNotPresent.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_IntArrayParam_Optional_QueryNotPresent.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -259,6 +260,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableIntArrayParam_Optional.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableIntArrayParam_Optional.generated.txt index 9075d188acda..40a1b29bb120 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableIntArrayParam_Optional.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableIntArrayParam_Optional.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32?[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -259,6 +260,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableIntArrayParam_Optional_QueryNotPresent.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableIntArrayParam_Optional_QueryNotPresent.generated.txt index 9075d188acda..40a1b29bb120 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableIntArrayParam_Optional_QueryNotPresent.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableIntArrayParam_Optional_QueryNotPresent.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32?[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -259,6 +260,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableStringArrayParam.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableStringArrayParam.generated.txt index 1d755d1dc5ad..9bebb6eea5ee 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableStringArrayParam.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_NullableStringArrayParam.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -230,6 +231,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam.generated.txt index e9d7ec91cc48..7931fdc136d0 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -230,6 +231,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam_Optional.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam_Optional.generated.txt index 3e4918ffd63d..757eb5f81f8d 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam_Optional.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam_Optional.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.String[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -230,6 +231,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam_Optional_QueryNotPresent.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam_Optional_QueryNotPresent.generated.txt index 3e4918ffd63d..757eb5f81f8d 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam_Optional_QueryNotPresent.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitQuery_StringArrayParam_Optional_QueryNotPresent.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.String[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -230,6 +231,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt index a82fa6fb9baf..2e18c631a26a 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt @@ -439,6 +439,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt index 794413cb79a9..3663350edf78 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt @@ -606,6 +606,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_ComplexTypeArrayParam.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_ComplexTypeArrayParam.generated.txt index 948327f881b7..07e79ab29820 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_ComplexTypeArrayParam.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_ComplexTypeArrayParam.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -266,6 +267,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_IntArrayParam_Optional.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_IntArrayParam_Optional.generated.txt index 682208c08c8c..e51b39e6f806 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_IntArrayParam_Optional.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_IntArrayParam_Optional.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -266,6 +267,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_IntArrayParam_Optional_QueryNotPresent.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_IntArrayParam_Optional_QueryNotPresent.generated.txt index 682208c08c8c..e51b39e6f806 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_IntArrayParam_Optional_QueryNotPresent.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_IntArrayParam_Optional_QueryNotPresent.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -266,6 +267,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableIntArrayParam_Optional.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableIntArrayParam_Optional.generated.txt index 82e1993b1b45..2e4b0d343e08 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableIntArrayParam_Optional.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableIntArrayParam_Optional.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32?[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -266,6 +267,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableIntArrayParam_Optional_QueryNotPresent.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableIntArrayParam_Optional_QueryNotPresent.generated.txt index 82e1993b1b45..2e4b0d343e08 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableIntArrayParam_Optional_QueryNotPresent.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableIntArrayParam_Optional_QueryNotPresent.generated.txt @@ -72,6 +72,7 @@ namespace Microsoft.AspNetCore.Http.Generated var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: true, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32?[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -266,6 +267,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam.generated.txt index af6f3f0d1c17..857501669994 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam.generated.txt @@ -81,12 +81,14 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -327,6 +329,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam_EmptyQueryValues.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam_EmptyQueryValues.generated.txt index af6f3f0d1c17..857501669994 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam_EmptyQueryValues.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam_EmptyQueryValues.generated.txt @@ -81,12 +81,14 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -327,6 +329,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam_QueryNotPresent.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam_QueryNotPresent.generated.txt index af6f3f0d1c17..857501669994 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam_QueryNotPresent.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_NullableStringArrayParam_QueryNotPresent.generated.txt @@ -81,12 +81,14 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -327,6 +329,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam.generated.txt index 3d2c9a682319..9c1fe26e5902 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam.generated.txt @@ -81,12 +81,14 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -327,6 +329,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam_Optional.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam_Optional.generated.txt index 5e6adff6d906..65ddf7109200 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam_Optional.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam_Optional.generated.txt @@ -81,12 +81,14 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.String[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -327,6 +329,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam_Optional_QueryNotPresent.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam_Optional_QueryNotPresent.generated.txt index 5e6adff6d906..65ddf7109200 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam_Optional_QueryNotPresent.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ImplicitQuery_StringArrayParam_Optional_QueryNotPresent.generated.txt @@ -81,12 +81,14 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: true)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.String[]), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -327,6 +329,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_JsonBodyOrService_HandlesBothJsonAndService.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_JsonBodyOrService_HandlesBothJsonAndService.generated.txt index 9a7acd7eb45f..bc9c6a59d734 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_JsonBodyOrService_HandlesBothJsonAndService.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_JsonBodyOrService_HandlesBothJsonAndService.generated.txt @@ -82,6 +82,7 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } @@ -345,6 +346,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt index 8ac1c767314e..560f0ec1f94a 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt @@ -234,6 +234,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt index 139a5739a234..0c26ae021468 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt @@ -267,6 +267,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt index ae38db998901..18cf2ce8010b 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt @@ -225,6 +225,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ReturnsString_Has_Metadata.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ReturnsString_Has_Metadata.generated.txt index ae38db998901..18cf2ce8010b 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ReturnsString_Has_Metadata.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ReturnsString_Has_Metadata.generated.txt @@ -225,6 +225,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ReturnsTodo_Has_Metadata.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ReturnsTodo_Has_Metadata.generated.txt index f1a99f9eb04b..808d55a96981 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ReturnsTodo_Has_Metadata.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ReturnsTodo_Has_Metadata.generated.txt @@ -70,6 +70,7 @@ namespace Microsoft.AspNetCore.Http.Generated Debug.Assert(options.EndpointBuilder != null, "EndpointBuilder not found."); options.EndpointBuilder.Metadata.Add(new System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.Http.RequestDelegateGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::Microsoft.AspNetCore.Http.Generators.Tests.Todo), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -218,6 +219,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt index dd910316c8f7..509930131549 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt @@ -266,6 +266,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt index e37a0fa0f25e..b1b71f8eecd5 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt @@ -266,6 +266,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt index 773c77bc8d20..b56621cb8c50 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt @@ -238,6 +238,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_TakesCustomMetadataEmitter_Has_Metadata.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_TakesCustomMetadataEmitter_Has_Metadata.generated.txt index 839b8eaa9374..72e03e06b74e 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_TakesCustomMetadataEmitter_Has_Metadata.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_TakesCustomMetadataEmitter_Has_Metadata.generated.txt @@ -81,6 +81,7 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } @@ -333,6 +334,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_Get_WithArrayQueryString_AndBody_ShouldUseQueryString.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_Get_WithArrayQueryString_AndBody_ShouldUseQueryString.generated.txt index 0daff798796f..7409065d2ce6 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_Get_WithArrayQueryString_AndBody_ShouldUseQueryString.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_Get_WithArrayQueryString_AndBody_ShouldUseQueryString.generated.txt @@ -81,6 +81,7 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } @@ -334,6 +335,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_PostAndGet_WithArrayQueryString_AndBody_ShouldUseQueryString.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_PostAndGet_WithArrayQueryString_AndBody_ShouldUseQueryString.generated.txt index 0daff798796f..7409065d2ce6 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_PostAndGet_WithArrayQueryString_AndBody_ShouldUseQueryString.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_PostAndGet_WithArrayQueryString_AndBody_ShouldUseQueryString.generated.txt @@ -81,6 +81,7 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } @@ -334,6 +335,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_PostAndPut_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_PostAndPut_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt index 0daff798796f..7409065d2ce6 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_PostAndPut_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_PostAndPut_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt @@ -81,6 +81,7 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } @@ -334,6 +335,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_Post_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_Post_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt index 0daff798796f..7409065d2ce6 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_Post_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapMethods_Post_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt @@ -81,6 +81,7 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } @@ -334,6 +335,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapPost_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapPost_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt index 8a2bc539262c..3fb5a66501a7 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapPost_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapPost_WithArrayQueryString_AndBody_ShouldUseBody.generated.txt @@ -81,6 +81,7 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } @@ -334,6 +335,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapPost_WithArrayQueryString_ShouldFail.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapPost_WithArrayQueryString_ShouldFail.generated.txt index 379c78383b45..f34ec56593e5 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapPost_WithArrayQueryString_ShouldFail.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapPost_WithArrayQueryString_ShouldFail.generated.txt @@ -81,12 +81,14 @@ namespace Microsoft.AspNetCore.Http.Generated if (!serviceProviderIsService.IsService(type)) { options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); break; } } var parameters = methodInfo.GetParameters(); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("p", parameters[0], hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(global::System.Int32), contentTypes: GeneratedMetadataConstants.JsonContentType)); + ApiEndpointMetadata.AddApiEndpointMetadataIfMissing(options.EndpointBuilder); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }; RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) => @@ -327,6 +329,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt index 79567d9d3a09..bf51a39927f5 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt @@ -414,6 +414,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt index 38e13d3e6d53..ffb90a91f7fc 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt @@ -428,6 +428,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/RequestDelegateValidateGeneratedFormCode.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/RequestDelegateValidateGeneratedFormCode.generated.txt index 905324b900eb..8119c31870ea 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/RequestDelegateValidateGeneratedFormCode.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/RequestDelegateValidateGeneratedFormCode.generated.txt @@ -373,17 +373,17 @@ namespace Microsoft.AspNetCore.Http.Generated } -file sealed class AntiforgeryMetadata : IAntiforgeryMetadata -{ - public static readonly IAntiforgeryMetadata ValidationRequired = new AntiforgeryMetadata(true); - - public AntiforgeryMetadata(bool requiresValidation) + file sealed class AntiforgeryMetadata : IAntiforgeryMetadata { - RequiresValidation = requiresValidation; - } + public static readonly IAntiforgeryMetadata ValidationRequired = new AntiforgeryMetadata(true); - public bool RequiresValidation { get; } -} + public AntiforgeryMetadata(bool requiresValidation) + { + RequiresValidation = requiresValidation; + } + + public bool RequiresValidation { get; } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/SupportsDifferentInterceptorsFromSameLocation.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/SupportsDifferentInterceptorsFromSameLocation.generated.txt index 5316ad22d597..fea0e359a92f 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/SupportsDifferentInterceptorsFromSameLocation.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/SupportsDifferentInterceptorsFromSameLocation.generated.txt @@ -388,6 +388,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/SupportsSameInterceptorsFromDifferentFiles.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/SupportsSameInterceptorsFromDifferentFiles.generated.txt index aad88a30d271..cafc9280c930 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/SupportsSameInterceptorsFromDifferentFiles.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/SupportsSameInterceptorsFromDifferentFiles.generated.txt @@ -257,6 +257,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/VerifyAsParametersBaseline.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/VerifyAsParametersBaseline.generated.txt index 30b4541a6f49..489a8b16b73a 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/VerifyAsParametersBaseline.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/VerifyAsParametersBaseline.generated.txt @@ -446,6 +446,7 @@ namespace Microsoft.AspNetCore.Http.Generated Debug.Assert(options.EndpointBuilder != null, "EndpointBuilder not found."); options.EndpointBuilder.Metadata.Add(new System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.Http.RequestDelegateGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")); options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("HttpContext", new PropertyAsParameterInfo(false, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParametersListWithImplicitFromBody)!.GetProperty("HttpContext")!, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParametersListWithImplicitFromBody).GetConstructor(new[] { typeof(Microsoft.AspNetCore.Http.HttpContext), typeof(Microsoft.AspNetCore.Http.Generators.Tests.TodoStruct) })?.GetParameters()[0]), hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("Todo", new PropertyAsParameterInfo(false, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParametersListWithImplicitFromBody)!.GetProperty("Todo")!, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParametersListWithImplicitFromBody).GetConstructor(new[] { typeof(Microsoft.AspNetCore.Http.HttpContext), typeof(Microsoft.AspNetCore.Http.Generators.Tests.TodoStruct) })?.GetParameters()[1]), hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(string), contentTypes: GeneratedMetadataConstants.PlaintextContentType)); @@ -567,6 +568,7 @@ namespace Microsoft.AspNetCore.Http.Generated Debug.Assert(options.EndpointBuilder != null, "EndpointBuilder not found."); options.EndpointBuilder.Metadata.Add(new System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.Http.RequestDelegateGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")); options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("HttpContext", new PropertyAsParameterInfo(false, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParametersListWithMetadataType)!.GetProperty("HttpContext")!, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParametersListWithMetadataType).GetConstructor(new[] { typeof(Microsoft.AspNetCore.Http.HttpContext), typeof(Microsoft.AspNetCore.Http.Generators.Tests.AddsCustomParameterMetadataAsProperty) })?.GetParameters()[0]), hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("Value", new PropertyAsParameterInfo(false, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParametersListWithMetadataType)!.GetProperty("Value")!, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParametersListWithMetadataType).GetConstructor(new[] { typeof(Microsoft.AspNetCore.Http.HttpContext), typeof(Microsoft.AspNetCore.Http.Generators.Tests.AddsCustomParameterMetadataAsProperty) })?.GetParameters()[1]), hasTryParse: false, hasBindAsync: false, isOptional: false)); var parameterInfos = methodInfo.GetParameters(); @@ -683,6 +685,7 @@ namespace Microsoft.AspNetCore.Http.Generated Debug.Assert(options.EndpointBuilder != null, "EndpointBuilder not found."); options.EndpointBuilder.Metadata.Add(new System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.Http.RequestDelegateGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")); options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes: GeneratedMetadataConstants.JsonContentType)); + options.EndpointBuilder.Metadata.Add(ApiEndpointMetadata.Instance); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("Todo", new PropertyAsParameterInfo(false, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParameterRecordStructWithJsonBodyOrService)!.GetProperty("Todo")!), hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("Service", new PropertyAsParameterInfo(false, typeof(Microsoft.AspNetCore.Http.Generators.Tests.ParameterRecordStructWithJsonBodyOrService)!.GetProperty("Service")!), hasTryParse: false, hasBindAsync: false, isOptional: false)); options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(string), contentTypes: GeneratedMetadataConstants.PlaintextContentType)); @@ -966,6 +969,22 @@ namespace Microsoft.AspNetCore.Http.Generated } + file sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static readonly ApiEndpointMetadata Instance = new(); + + private ApiEndpointMetadata() + { + } + + public static void AddApiEndpointMetadataIfMissing(EndpointBuilder builder) + { + if (!builder.Metadata.Any(m => m is IApiEndpointMetadata)) + { + builder.Metadata.Add(Instance); + } + } + } %GENERATEDCODEATTRIBUTE% file static class GeneratedMetadataConstants { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTests.Metadata.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTests.Metadata.cs index 68f0af004651..1f3412e54391 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTests.Metadata.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTests.Metadata.cs @@ -83,17 +83,14 @@ public async Task MapAction_ReturnsTaskOfString_Has_Metadata() } [Fact] - public async Task MapAction_ReturnsTask_ProducesInferredMetadata() + public async Task MapAction_ReturnsTask_ProducesNoInferredProducesResponseTypeMetadata() { var (_, compilation) = await RunGeneratorAsync(""" app.MapGet("/", Task () => Task.CompletedTask); """); var endpoint = GetEndpointFromCompilation(compilation); - var metadata = endpoint.Metadata.OfType().Single(); - Assert.Equal(200, metadata.StatusCode); - Assert.Equal("text/plain", metadata.ContentTypes.Single()); - Assert.Equal(typeof(void), metadata.Type); + Assert.Empty(endpoint.Metadata.OfType()); } [Fact] @@ -111,17 +108,14 @@ public async Task MapAction_ReturnsValueTaskOfString_Has_Metadata() } [Fact] - public async Task MapAction_ReturnsValueTask_ProducesInferredMetadata() + public async Task MapAction_ReturnsValueTask_ProducesNoInferredProducesResponseTypeMetadata() { var (_, compilation) = await RunGeneratorAsync(""" app.MapGet("/", ValueTask () => ValueTask.CompletedTask); """); var endpoint = GetEndpointFromCompilation(compilation); - var metadata = endpoint.Metadata.OfType().Single(); - Assert.Equal(200, metadata.StatusCode); - Assert.Equal("text/plain", metadata.ContentTypes.Single()); - Assert.Equal(typeof(void), metadata.Type); + Assert.Empty(endpoint.Metadata.OfType()); } [Fact] @@ -512,6 +506,11 @@ public async Task InferMetadata_ThenCreate_CombinesAllMetadata_InCorrectOrder() // Act var endpoint = GetEndpointFromCompilation(compilation); + // IApiEndpointMetadata is tricky to order consistently because it depends on whether AddsCustomParameterMetadata is registered + // as a service at runtime. However, the order of IApiEndpointMetadata is not significant since there's no way to override it + // other than removing it. + Assert.Single(endpoint.Metadata, m => m is IApiEndpointMetadata); + // Assert // NOTE: Depending on whether we are running under RDG or RDG, there are some generated types which // don't have equivalents in the opposite. The two examples here are NullableContextAttribute which @@ -525,7 +524,8 @@ m is not MethodInfo && m is not HttpMethodMetadata && m is not Attribute1 && m is not Attribute2 && - m is not IRouteDiagnosticsMetadata); + m is not IRouteDiagnosticsMetadata && + m is not IApiEndpointMetadata); Assert.Collection(filteredMetadata, // Inferred AcceptsMetadata from RDF for complex type diff --git a/src/Http/Http.Results/src/Accepted.cs b/src/Http/Http.Results/src/Accepted.cs index 5dbc56987c4c..d71fe9072c7e 100644 --- a/src/Http/Http.Results/src/Accepted.cs +++ b/src/Http/Http.Results/src/Accepted.cs @@ -82,5 +82,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status202Accepted, typeof(void))); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/AcceptedAtRoute.cs b/src/Http/Http.Results/src/AcceptedAtRoute.cs index 7472559549da..900ee507d9cf 100644 --- a/src/Http/Http.Results/src/AcceptedAtRoute.cs +++ b/src/Http/Http.Results/src/AcceptedAtRoute.cs @@ -109,5 +109,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status202Accepted, typeof(void))); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs index e9467dbe1e54..b8e4304a7237 100644 --- a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs @@ -123,5 +123,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status202Accepted, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/AcceptedOfT.cs b/src/Http/Http.Results/src/AcceptedOfT.cs index 6777d3549d58..346070714b43 100644 --- a/src/Http/Http.Results/src/AcceptedOfT.cs +++ b/src/Http/Http.Results/src/AcceptedOfT.cs @@ -101,5 +101,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status202Accepted, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/BadRequest.cs b/src/Http/Http.Results/src/BadRequest.cs index 9eef74aa41c2..0356afedc2c7 100644 --- a/src/Http/Http.Results/src/BadRequest.cs +++ b/src/Http/Http.Results/src/BadRequest.cs @@ -52,5 +52,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status400BadRequest, typeof(void))); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/BadRequestOfT.cs b/src/Http/Http.Results/src/BadRequestOfT.cs index 4b932786eb3b..b8fa51423d7e 100644 --- a/src/Http/Http.Results/src/BadRequestOfT.cs +++ b/src/Http/Http.Results/src/BadRequestOfT.cs @@ -66,5 +66,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status400BadRequest, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/Conflict.cs b/src/Http/Http.Results/src/Conflict.cs index da162ed5d207..f0067ba4aea1 100644 --- a/src/Http/Http.Results/src/Conflict.cs +++ b/src/Http/Http.Results/src/Conflict.cs @@ -52,5 +52,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status409Conflict, typeof(void))); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/ConflictOfT.cs b/src/Http/Http.Results/src/ConflictOfT.cs index bbfb3c6a503e..dda54d9dd5cc 100644 --- a/src/Http/Http.Results/src/ConflictOfT.cs +++ b/src/Http/Http.Results/src/ConflictOfT.cs @@ -66,5 +66,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status409Conflict, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/Created.cs b/src/Http/Http.Results/src/Created.cs index 7766d91a6f46..1959556280c9 100644 --- a/src/Http/Http.Results/src/Created.cs +++ b/src/Http/Http.Results/src/Created.cs @@ -82,5 +82,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status201Created, typeof(void))); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/CreatedAtRoute.cs b/src/Http/Http.Results/src/CreatedAtRoute.cs index 2fb70fa23b16..2ff79596ba36 100644 --- a/src/Http/Http.Results/src/CreatedAtRoute.cs +++ b/src/Http/Http.Results/src/CreatedAtRoute.cs @@ -109,5 +109,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status201Created, typeof(void))); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs index d13d6154413b..a2868afdc0a1 100644 --- a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs @@ -126,5 +126,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status201Created, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/CreatedOfT.cs b/src/Http/Http.Results/src/CreatedOfT.cs index 58d0638b218d..a12e52b9f69f 100644 --- a/src/Http/Http.Results/src/CreatedOfT.cs +++ b/src/Http/Http.Results/src/CreatedOfT.cs @@ -100,5 +100,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status201Created, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/InternalServerErrorOfT.cs b/src/Http/Http.Results/src/InternalServerErrorOfT.cs index 36ec9fed9512..c726debf67c0 100644 --- a/src/Http/Http.Results/src/InternalServerErrorOfT.cs +++ b/src/Http/Http.Results/src/InternalServerErrorOfT.cs @@ -66,5 +66,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status500InternalServerError, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/JsonHttpResultOfT.cs b/src/Http/Http.Results/src/JsonHttpResultOfT.cs index 4841e675ecfd..fea230609a20 100644 --- a/src/Http/Http.Results/src/JsonHttpResultOfT.cs +++ b/src/Http/Http.Results/src/JsonHttpResultOfT.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization.Metadata; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -13,7 +16,7 @@ namespace Microsoft.AspNetCore.Http.HttpResults; /// /// An action result which formats the given object as JSON. /// -public sealed partial class JsonHttpResult : IResult, IStatusCodeHttpResult, IValueHttpResult, IValueHttpResult, IContentTypeHttpResult +public sealed partial class JsonHttpResult : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult, IValueHttpResult, IValueHttpResult, IContentTypeHttpResult { /// /// Initializes a new instance of the class with the values. @@ -117,7 +120,7 @@ public Task ExecuteAsync(HttpContext httpContext) typedJsonTypeInfo, contentType: ContentType); } - + return httpContext.Response.WriteAsJsonAsync( Value, JsonTypeInfo, @@ -131,4 +134,9 @@ public Task ExecuteAsync(HttpContext httpContext) ContentType, JsonSerializerOptions); } + + static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + builder.Metadata.Add(ApiEndpointMetadata.Instance); + } } diff --git a/src/Http/Http.Results/src/NoContent.cs b/src/Http/Http.Results/src/NoContent.cs index 987a06fc4eee..35ffb3c56701 100644 --- a/src/Http/Http.Results/src/NoContent.cs +++ b/src/Http/Http.Results/src/NoContent.cs @@ -52,5 +52,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status204NoContent, typeof(void))); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/NotFoundOfT.cs b/src/Http/Http.Results/src/NotFoundOfT.cs index ad9c3a1032eb..d37a4ee9707c 100644 --- a/src/Http/Http.Results/src/NotFoundOfT.cs +++ b/src/Http/Http.Results/src/NotFoundOfT.cs @@ -65,5 +65,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status404NotFound, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/Ok.cs b/src/Http/Http.Results/src/Ok.cs index 0db060d4e6d4..837a0fa7513c 100644 --- a/src/Http/Http.Results/src/Ok.cs +++ b/src/Http/Http.Results/src/Ok.cs @@ -51,5 +51,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status200OK, typeof(void))); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/OkOfT.cs b/src/Http/Http.Results/src/OkOfT.cs index 108674fdc2ac..7f19598effe9 100644 --- a/src/Http/Http.Results/src/OkOfT.cs +++ b/src/Http/Http.Results/src/OkOfT.cs @@ -65,5 +65,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status200OK, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/ProblemHttpResult.cs b/src/Http/Http.Results/src/ProblemHttpResult.cs index 4fe63c4277cc..8ac29336f053 100644 --- a/src/Http/Http.Results/src/ProblemHttpResult.cs +++ b/src/Http/Http.Results/src/ProblemHttpResult.cs @@ -4,6 +4,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http.Metadata; namespace Microsoft.AspNetCore.Http.HttpResults; @@ -11,7 +14,7 @@ namespace Microsoft.AspNetCore.Http.HttpResults; /// An that on execution will write Problem Details /// HTTP API responses based on /// -public sealed class ProblemHttpResult : IResult, IStatusCodeHttpResult, IContentTypeHttpResult, IValueHttpResult, IValueHttpResult +public sealed class ProblemHttpResult : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult, IContentTypeHttpResult, IValueHttpResult, IValueHttpResult { /// /// Creates a new instance with @@ -69,4 +72,13 @@ await HttpResultsHelper.WriteResultAsJsonAsync( ContentType); } } + + /// + static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + ArgumentNullException.ThrowIfNull(method); + ArgumentNullException.ThrowIfNull(builder); + + builder.Metadata.Add(ApiEndpointMetadata.Instance); + } } diff --git a/src/Http/Http.Results/src/ServerSentEventsResult.cs b/src/Http/Http.Results/src/ServerSentEventsResult.cs index 448b2076daa6..a7951acd1554 100644 --- a/src/Http/Http.Results/src/ServerSentEventsResult.cs +++ b/src/Http/Http.Results/src/ServerSentEventsResult.cs @@ -105,5 +105,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status200OK, typeof(SseItem), contentTypes: ["text/event-stream"])); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/UnprocessableEntity.cs b/src/Http/Http.Results/src/UnprocessableEntity.cs index eef96dc9f513..49d3982720b8 100644 --- a/src/Http/Http.Results/src/UnprocessableEntity.cs +++ b/src/Http/Http.Results/src/UnprocessableEntity.cs @@ -52,5 +52,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status422UnprocessableEntity, typeof(void))); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/UnprocessableEntityOfT.cs b/src/Http/Http.Results/src/UnprocessableEntityOfT.cs index 475a0bc3ad2c..2d1ee494a271 100644 --- a/src/Http/Http.Results/src/UnprocessableEntityOfT.cs +++ b/src/Http/Http.Results/src/UnprocessableEntityOfT.cs @@ -66,5 +66,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(TValue), StatusCodes.Status422UnprocessableEntity, ContentTypeConstants.ApplicationJsonContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/src/ValidationProblem.cs b/src/Http/Http.Results/src/ValidationProblem.cs index ba738cfbcf3e..c4a539c9cd5f 100644 --- a/src/Http/Http.Results/src/ValidationProblem.cs +++ b/src/Http/Http.Results/src/ValidationProblem.cs @@ -77,5 +77,6 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi ArgumentNullException.ThrowIfNull(builder); builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(typeof(HttpValidationProblemDetails), StatusCodes.Status400BadRequest, ContentTypeConstants.ProblemDetailsContentTypes)); + builder.Metadata.Add(ApiEndpointMetadata.Instance); } } diff --git a/src/Http/Http.Results/test/AcceptedAtRouteOfTResultTests.cs b/src/Http/Http.Results/test/AcceptedAtRouteOfTResultTests.cs index 47b1569390d4..1d53452fa65e 100644 --- a/src/Http/Http.Results/test/AcceptedAtRouteOfTResultTests.cs +++ b/src/Http/Http.Results/test/AcceptedAtRouteOfTResultTests.cs @@ -134,6 +134,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status202Accepted, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs index 8f29f5a51344..15a93310e80c 100644 --- a/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs +++ b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs @@ -87,6 +87,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() var producesResponseTypeMetadata = builder.Metadata.OfType().Last(); Assert.Equal(StatusCodes.Status202Accepted, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(void), producesResponseTypeMetadata.Type); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/AcceptedOfTResultTests.cs b/src/Http/Http.Results/test/AcceptedOfTResultTests.cs index 853a57d4704f..2ee6e03193d4 100644 --- a/src/Http/Http.Results/test/AcceptedOfTResultTests.cs +++ b/src/Http/Http.Results/test/AcceptedOfTResultTests.cs @@ -75,6 +75,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status202Accepted, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/AcceptedResultTests.cs b/src/Http/Http.Results/test/AcceptedResultTests.cs index 7511b05fa249..171b45112174 100644 --- a/src/Http/Http.Results/test/AcceptedResultTests.cs +++ b/src/Http/Http.Results/test/AcceptedResultTests.cs @@ -44,6 +44,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() var producesResponseTypeMetadata = builder.Metadata.OfType().Last(); Assert.Equal(StatusCodes.Status202Accepted, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(void), producesResponseTypeMetadata.Type); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/BadRequestOfTResultTests.cs b/src/Http/Http.Results/test/BadRequestOfTResultTests.cs index 58e1e452e399..c930db6ea80a 100644 --- a/src/Http/Http.Results/test/BadRequestOfTResultTests.cs +++ b/src/Http/Http.Results/test/BadRequestOfTResultTests.cs @@ -118,6 +118,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status400BadRequest, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/BadRequestResultTests.cs b/src/Http/Http.Results/test/BadRequestResultTests.cs index e8f3a444de05..cca2a4d139ee 100644 --- a/src/Http/Http.Results/test/BadRequestResultTests.cs +++ b/src/Http/Http.Results/test/BadRequestResultTests.cs @@ -57,6 +57,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() var producesResponseTypeMetadata = builder.Metadata.OfType().Last(); Assert.Equal(StatusCodes.Status400BadRequest, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(void), producesResponseTypeMetadata.Type); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/ConflictOfTResultTests.cs b/src/Http/Http.Results/test/ConflictOfTResultTests.cs index 5e757e3b191c..be4e43d441df 100644 --- a/src/Http/Http.Results/test/ConflictOfTResultTests.cs +++ b/src/Http/Http.Results/test/ConflictOfTResultTests.cs @@ -96,6 +96,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status409Conflict, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/ConflictResultTests.cs b/src/Http/Http.Results/test/ConflictResultTests.cs index 88f64740aaea..2f639948eac6 100644 --- a/src/Http/Http.Results/test/ConflictResultTests.cs +++ b/src/Http/Http.Results/test/ConflictResultTests.cs @@ -58,6 +58,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() var producesResponseTypeMetadata = builder.Metadata.OfType().Last(); Assert.Equal(StatusCodes.Status409Conflict, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(void), producesResponseTypeMetadata.Type); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/CreatedAtRouteOfTResultTests.cs b/src/Http/Http.Results/test/CreatedAtRouteOfTResultTests.cs index dec5f3ceafbd..a55f7dd6ef9c 100644 --- a/src/Http/Http.Results/test/CreatedAtRouteOfTResultTests.cs +++ b/src/Http/Http.Results/test/CreatedAtRouteOfTResultTests.cs @@ -104,6 +104,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status201Created, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs b/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs index e91822afc79a..5a36fdcce055 100644 --- a/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs +++ b/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs @@ -85,6 +85,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() var producesResponseTypeMetadata = builder.Metadata.OfType().Last(); Assert.Equal(StatusCodes.Status201Created, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(void), producesResponseTypeMetadata.Type); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/CreatedOfTResultTests.cs b/src/Http/Http.Results/test/CreatedOfTResultTests.cs index 9e5f0637058c..b4b5747745a1 100644 --- a/src/Http/Http.Results/test/CreatedOfTResultTests.cs +++ b/src/Http/Http.Results/test/CreatedOfTResultTests.cs @@ -110,6 +110,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status201Created, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/CreatedResultTests.cs b/src/Http/Http.Results/test/CreatedResultTests.cs index 21b678d39301..af1c8a1179b8 100644 --- a/src/Http/Http.Results/test/CreatedResultTests.cs +++ b/src/Http/Http.Results/test/CreatedResultTests.cs @@ -75,6 +75,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() var producesResponseTypeMetadata = builder.Metadata.OfType().Last(); Assert.Equal(StatusCodes.Status201Created, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(void), producesResponseTypeMetadata.Type); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/InternalServerErrorOfTResultTests.cs b/src/Http/Http.Results/test/InternalServerErrorOfTResultTests.cs index d063b4e66a11..1ee5bd751053 100644 --- a/src/Http/Http.Results/test/InternalServerErrorOfTResultTests.cs +++ b/src/Http/Http.Results/test/InternalServerErrorOfTResultTests.cs @@ -118,6 +118,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status500InternalServerError, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/JsonResultTests.cs b/src/Http/Http.Results/test/JsonResultTests.cs index 1c2d6d7fb225..52d76c0a03c9 100644 --- a/src/Http/Http.Results/test/JsonResultTests.cs +++ b/src/Http/Http.Results/test/JsonResultTests.cs @@ -1,11 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Reflection; using System.Text; using System.Text.Json; using System.Text.Unicode; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Json; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -310,6 +315,24 @@ public void JsonResult_Implements_IValueHttpResultOfT_Correctly() Assert.Equal(value, result.Value); } + [Fact] + public void PopulateMetadata_AddsNonBrowserEndpointMetadata() + { + // Arrange + JsonHttpResult MyApi() { throw new NotImplementedException(); } + var metadata = new List(); + var builder = new RouteEndpointBuilder(requestDelegate: null, RoutePatternFactory.Parse("/"), order: 0); + + // Act + PopulateMetadata>(((Delegate)MyApi).GetMethodInfo(), builder); + + // Assert + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); + } + + private static void PopulateMetadata(MethodInfo method, EndpointBuilder builder) + where TResult : IEndpointMetadataProvider => TResult.PopulateMetadata(method, builder); + private static IServiceProvider CreateServices() { var services = new ServiceCollection(); diff --git a/src/Http/Http.Results/test/NoContentResultTests.cs b/src/Http/Http.Results/test/NoContentResultTests.cs index 31a7c8cbc6af..fc1cae4a95aa 100644 --- a/src/Http/Http.Results/test/NoContentResultTests.cs +++ b/src/Http/Http.Results/test/NoContentResultTests.cs @@ -54,6 +54,9 @@ public void PopulateMetadata_AddsResponseTypeMetadata() var producesResponseTypeMetadata = builder.Metadata.OfType().Last(); Assert.Equal(StatusCodes.Status204NoContent, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(void), producesResponseTypeMetadata.Type); + + // Assert ApiEndpointMetadata is added + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/NotFoundOfTResultTests.cs b/src/Http/Http.Results/test/NotFoundOfTResultTests.cs index 0aa82fdd4317..ec15a5c8a7e8 100644 --- a/src/Http/Http.Results/test/NotFoundOfTResultTests.cs +++ b/src/Http/Http.Results/test/NotFoundOfTResultTests.cs @@ -78,6 +78,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status404NotFound, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/OkOfTResultTests.cs b/src/Http/Http.Results/test/OkOfTResultTests.cs index 99ce735caa21..85693056570b 100644 --- a/src/Http/Http.Results/test/OkOfTResultTests.cs +++ b/src/Http/Http.Results/test/OkOfTResultTests.cs @@ -95,6 +95,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status200OK, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/OkResultTests.cs b/src/Http/Http.Results/test/OkResultTests.cs index fe5db61ff5d4..98cac07cbb60 100644 --- a/src/Http/Http.Results/test/OkResultTests.cs +++ b/src/Http/Http.Results/test/OkResultTests.cs @@ -56,6 +56,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() var producesResponseTypeMetadata = builder.Metadata.OfType().Last(); Assert.Equal(StatusCodes.Status200OK, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(void), producesResponseTypeMetadata.Type); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/UnprocessableEntityOfTResultTests.cs b/src/Http/Http.Results/test/UnprocessableEntityOfTResultTests.cs index 6ec306cbcaf1..8c8161f9955c 100644 --- a/src/Http/Http.Results/test/UnprocessableEntityOfTResultTests.cs +++ b/src/Http/Http.Results/test/UnprocessableEntityOfTResultTests.cs @@ -95,6 +95,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() Assert.Equal(StatusCodes.Status422UnprocessableEntity, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type); Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json"); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Http/Http.Results/test/UnprocessableEntityResultTests.cs b/src/Http/Http.Results/test/UnprocessableEntityResultTests.cs index 36edb8923abe..7931b226c065 100644 --- a/src/Http/Http.Results/test/UnprocessableEntityResultTests.cs +++ b/src/Http/Http.Results/test/UnprocessableEntityResultTests.cs @@ -57,6 +57,8 @@ public void PopulateMetadata_AddsResponseTypeMetadata() var producesResponseTypeMetadata = builder.Metadata.OfType().Last(); Assert.Equal(StatusCodes.Status422UnprocessableEntity, producesResponseTypeMetadata.StatusCode); Assert.Equal(typeof(void), producesResponseTypeMetadata.Type); + + Assert.Contains(builder.Metadata, m => m is IApiEndpointMetadata); } [Fact] diff --git a/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs b/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs index 89d45efc5e11..2f25045d3787 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs @@ -332,10 +332,9 @@ private static void AddSupportedResponseTypes( var defaultErrorType = errorMetadata?.Type ?? typeof(void); var contentTypes = new MediaTypeCollection(); - // If the return type is an IResult or an awaitable IResult, then we should treat it as a void return type - // since we can't infer anything without additional metadata. - if (typeof(IResult).IsAssignableFrom(responseType) || - producesResponseMetadata.Any(metadata => typeof(IResult).IsAssignableFrom(metadata.Type))) + // If the return type is an IResult or wrapped in a Task or ValueTask, then we should treat it as a void return type + // since we can't infer anything without additional metadata or requiring unreferenced code. + if (IsTaskOrValueTask(responseType) || typeof(IResult).IsAssignableFrom(responseType)) { responseType = typeof(void); } @@ -427,6 +426,23 @@ static bool TypesAreCompatible(Type? apiResponseType, Type? metadataType) return apiResponseType == metadataType || metadataType?.IsAssignableFrom(apiResponseType) == true; } + + static bool IsTaskOrValueTask(Type returnType) + { + // If this method did not need to be trim-safe, we would use CoercedAwaitableInfo.IsTypeAwaitable, but we cannot. + if (returnType.IsAssignableFrom(typeof(Task)) || returnType.IsAssignableFrom(typeof(ValueTask))) + { + return true; + } + + if (returnType.FullName is null) + { + return false; + } + + return returnType.FullName.StartsWith("System.Threading.Tasks.Task`1[", StringComparison.Ordinal) || + returnType.FullName.StartsWith("System.Threading.Tasks.ValueTask`1[", StringComparison.Ordinal); + } } private static ApiResponseType CreateDefaultApiResponseType(Type responseType) diff --git a/src/Mvc/Mvc.Core/src/ApiControllerAttribute.cs b/src/Mvc/Mvc.Core/src/ApiControllerAttribute.cs index a1b95b3ee827..f7179b0b447d 100644 --- a/src/Mvc/Mvc.Core/src/ApiControllerAttribute.cs +++ b/src/Mvc/Mvc.Core/src/ApiControllerAttribute.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc; @@ -17,6 +18,6 @@ namespace Microsoft.AspNetCore.Mvc; /// /// [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] -public class ApiControllerAttribute : ControllerAttribute, IApiBehaviorMetadata +public class ApiControllerAttribute : ControllerAttribute, IApiBehaviorMetadata, IApiEndpointMetadata { } diff --git a/src/Mvc/Mvc.Core/test/ApplicationModels/DefaultApplicationModelProviderTest.cs b/src/Mvc/Mvc.Core/test/ApplicationModels/DefaultApplicationModelProviderTest.cs index 19aea8a971b2..c8c6368fe7e8 100644 --- a/src/Mvc/Mvc.Core/test/ApplicationModels/DefaultApplicationModelProviderTest.cs +++ b/src/Mvc/Mvc.Core/test/ApplicationModels/DefaultApplicationModelProviderTest.cs @@ -3,6 +3,7 @@ using System.Reflection; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -1277,8 +1278,9 @@ public void AddReturnTypeMetadata_ExtractsMetadataFromReturnType() // Assert Assert.NotNull(selector.EndpointMetadata); - Assert.Single(selector.EndpointMetadata); - Assert.IsType(selector.EndpointMetadata.Single()); + Assert.Equal(2, selector.EndpointMetadata.Count); + Assert.Single(selector.EndpointMetadata.OfType()); + Assert.Single(selector.EndpointMetadata.OfType()); Assert.Equal(200, ((ProducesResponseTypeMetadata)selector.EndpointMetadata[0]).StatusCode); } diff --git a/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs b/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs index be47982a28dd..994d999aaf52 100644 --- a/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs +++ b/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Authentication.Cookies; @@ -41,7 +42,7 @@ public class CookieAuthenticationEvents /// public Func, Task> OnRedirectToLogin { get; set; } = context => { - if (IsAjaxRequest(context.Request)) + if (IsAjaxRequest(context.Request) || IsApiEndpoint(context.HttpContext)) { context.Response.Headers.Location = context.RedirectUri; context.Response.StatusCode = 401; @@ -58,7 +59,7 @@ public class CookieAuthenticationEvents /// public Func, Task> OnRedirectToAccessDenied { get; set; } = context => { - if (IsAjaxRequest(context.Request)) + if (IsAjaxRequest(context.Request) || IsApiEndpoint(context.HttpContext)) { context.Response.Headers.Location = context.RedirectUri; context.Response.StatusCode = 403; @@ -108,6 +109,12 @@ private static bool IsAjaxRequest(HttpRequest request) string.Equals(request.Headers.XRequestedWith, "XMLHttpRequest", StringComparison.Ordinal); } + private static bool IsApiEndpoint(HttpContext context) + { + var endpoint = context.GetEndpoint(); + return endpoint?.Metadata.GetMetadata() is not null; + } + /// /// Invoked to validate the principal. /// diff --git a/src/Security/Authentication/test/CookieTests.cs b/src/Security/Authentication/test/CookieTests.cs index cb65ba5a6bae..3dc0d638b8cf 100644 --- a/src/Security/Authentication/test/CookieTests.cs +++ b/src/Security/Authentication/test/CookieTests.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.DependencyInjection; @@ -96,6 +97,30 @@ public async Task AjaxChallengeRedirectTurnsInto200WithLocationHeader() Assert.StartsWith("http://example.com/Account/Login", responded.Single()); } + [Fact] + public async Task ApiEndpointChallengeReturns401WithLocationHeader() + { + using var host = await CreateHost(s => { }); + using var server = host.GetTestServer(); + var transaction = await SendAsync(server, "http://example.com/api/challenge"); + Assert.Equal(HttpStatusCode.Unauthorized, transaction.Response.StatusCode); + var responded = transaction.Response.Headers.GetValues("Location"); + Assert.Single(responded); + Assert.StartsWith("http://example.com/Account/Login", responded.Single()); + } + + [Fact] + public async Task ApiEndpointForbidReturns403WithLocationHeader() + { + using var host = await CreateHost(s => { }); + using var server = host.GetTestServer(); + var transaction = await SendAsync(server, "http://example.com/api/forbid"); + Assert.Equal(HttpStatusCode.Forbidden, transaction.Response.StatusCode); + var responded = transaction.Response.Headers.GetValues("Location"); + Assert.Single(responded); + Assert.StartsWith("http://example.com/Account/AccessDenied", responded.Single()); + } + [Fact] public async Task ProtectedCustomRequestShouldRedirectToCustomRedirectUri() { @@ -1879,8 +1904,28 @@ private static async Task CreateHostWithServices(Action + { + var apiRouteGroup = endpoints.MapGroup("/api").WithMetadata(new TestApiEndpointMetadata()); + // Add endpoints with IApiEndpointMetadata + apiRouteGroup.MapGet("/challenge", async context => + { + await context.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme); + }); + + apiRouteGroup.MapGet("/forbid", async context => + { + await context.ForbidAsync(CookieAuthenticationDefaults.AuthenticationScheme); + }); + }); }) - .ConfigureServices(configureServices)) + .ConfigureServices(services => + { + services.AddRoutingCore(); + configureServices(services); + })) .Build(); await host.StartAsync(); @@ -1949,4 +1994,8 @@ private class Transaction public string ResponseText { get; set; } public XElement ResponseElement { get; set; } } + + private class TestApiEndpointMetadata : IApiEndpointMetadata + { + } } diff --git a/src/SignalR/common/Http.Connections/src/ConnectionEndpointRouteBuilderExtensions.cs b/src/SignalR/common/Http.Connections/src/ConnectionEndpointRouteBuilderExtensions.cs index 8ed01395df6c..f1aad8649e82 100644 --- a/src/SignalR/common/Http.Connections/src/ConnectionEndpointRouteBuilderExtensions.cs +++ b/src/SignalR/common/Http.Connections/src/ConnectionEndpointRouteBuilderExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Http.Connections.Internal; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.Http.Timeouts; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; @@ -125,6 +126,9 @@ public static ConnectionEndpointRouteBuilder MapConnections(this IEndpointRouteB { e.Metadata.Add(data); } + + // Add IApiEndpointMetadata to indicate this is a non-browser endpoint (SignalR) + e.Metadata.Add(ApiEndpointMetadata.Instance); }); return new ConnectionEndpointRouteBuilder(compositeConventionBuilder); @@ -155,4 +159,9 @@ public void Finally(Action finalConvention) } } } + + private sealed class ApiEndpointMetadata : IApiEndpointMetadata + { + public static ApiEndpointMetadata Instance { get; } = new(); + } }