From e2494f10cf90d001c2600bd8d5aa98b4a05fcd7c Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 13 Jan 2023 17:04:46 +0800 Subject: [PATCH] [AOT] Enable analysis and annotate Http.Results --- eng/TrimmableProjects.props | 1 + src/Http/Http.Results/src/AcceptedAtRoute.cs | 18 +++- .../Http.Results/src/AcceptedAtRouteOfT.cs | 19 +++- src/Http/Http.Results/src/CreatedAtRoute.cs | 18 +++- .../Http.Results/src/CreatedAtRouteOfT.cs | 19 +++- .../Http.Results/src/HttpResultsHelper.cs | 8 ++ .../Microsoft.AspNetCore.Http.Results.csproj | 2 +- .../Http.Results/src/PublicAPI.Unshipped.txt | 10 ++ .../src/RedirectToRouteHttpResult.cs | 33 +++++- src/Http/Http.Results/src/Results.cs | 95 +++++++++++++++- .../Http.Results/src/ResultsOfT.Generated.cs | 11 +- src/Http/Http.Results/src/ResultsOfTHelper.cs | 31 +++++- .../src/RouteValueDictionaryTrimmerWarning.cs | 10 ++ src/Http/Http.Results/src/TypedResults.cs | 102 +++++++++++++++--- .../test/ResultsOfTHelperTests.cs | 87 +++++++++++++++ .../tools/ResultsOfTGenerator/Program.cs | 3 +- src/Tools/Tools.slnf | 3 +- 17 files changed, 436 insertions(+), 34 deletions(-) create mode 100644 src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs create mode 100644 src/Http/Http.Results/test/ResultsOfTHelperTests.cs diff --git a/eng/TrimmableProjects.props b/eng/TrimmableProjects.props index ffde1bd5b9bb..de2fe7826834 100644 --- a/eng/TrimmableProjects.props +++ b/eng/TrimmableProjects.props @@ -25,6 +25,7 @@ + diff --git a/src/Http/Http.Results/src/AcceptedAtRoute.cs b/src/Http/Http.Results/src/AcceptedAtRoute.cs index 55489db2d29c..7aafcb728683 100644 --- a/src/Http/Http.Results/src/AcceptedAtRoute.cs +++ b/src/Http/Http.Results/src/AcceptedAtRoute.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 System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; @@ -22,11 +23,24 @@ public sealed class AcceptedAtRoute : IResult, IEndpointMetadataProvider, IStatu /// provided. /// /// The route data to use for generating the URL. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] internal AcceptedAtRoute(object? routeValues) : this(routeName: null, routeValues: routeValues) { } + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] + internal AcceptedAtRoute(string? routeName, object? routeValues) + : this(routeName, new RouteValueDictionary(routeValues)) + { + } + /// /// Initializes a new instance of the class with the values /// provided. @@ -35,10 +49,10 @@ internal AcceptedAtRoute(object? routeValues) /// The route data to use for generating the URL. internal AcceptedAtRoute( string? routeName, - object? routeValues) + RouteValueDictionary routeValues) { RouteName = routeName; - RouteValues = new RouteValueDictionary(routeValues); + RouteValues = routeValues; } /// diff --git a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs index c270926e5223..034628c0db83 100644 --- a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/AcceptedAtRouteOfT.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 System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; @@ -24,11 +25,25 @@ public sealed class AcceptedAtRoute : IResult, IEndpointMetadataProvider /// /// The route data to use for generating the URL. /// The value to format in the entity body. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] internal AcceptedAtRoute(object? routeValues, TValue? value) : this(routeName: null, routeValues: routeValues, value: value) { } + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to format in the entity body. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] + internal AcceptedAtRoute(string? routeName, object? routeValues, TValue? value) + : this(routeName, new RouteValueDictionary(routeValues), value) + { + } + /// /// Initializes a new instance of the class with the values /// provided. @@ -38,12 +53,12 @@ internal AcceptedAtRoute(object? routeValues, TValue? value) /// The value to format in the entity body. internal AcceptedAtRoute( string? routeName, - object? routeValues, + RouteValueDictionary routeValues, TValue? value) { Value = value; RouteName = routeName; - RouteValues = new RouteValueDictionary(routeValues); + RouteValues = routeValues; HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } diff --git a/src/Http/Http.Results/src/CreatedAtRoute.cs b/src/Http/Http.Results/src/CreatedAtRoute.cs index 213576e24e52..095b99986217 100644 --- a/src/Http/Http.Results/src/CreatedAtRoute.cs +++ b/src/Http/Http.Results/src/CreatedAtRoute.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 System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; @@ -22,11 +23,24 @@ public sealed class CreatedAtRoute : IResult, IEndpointMetadataProvider, IStatus /// provided. /// /// The route data to use for generating the URL. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] internal CreatedAtRoute(object? routeValues) : this(routeName: null, routeValues: routeValues) { } + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] + internal CreatedAtRoute(string? routeName, object? routeValues) + : this(routeName, new RouteValueDictionary(routeValues)) + { + } + /// /// Initializes a new instance of the class with the values /// provided. @@ -35,10 +49,10 @@ internal CreatedAtRoute(object? routeValues) /// The route data to use for generating the URL. internal CreatedAtRoute( string? routeName, - object? routeValues) + RouteValueDictionary routeValues) { RouteName = routeName; - RouteValues = new RouteValueDictionary(routeValues); + RouteValues = routeValues; } /// diff --git a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs index 6d543d9b98f5..3114cdb82350 100644 --- a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/CreatedAtRouteOfT.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 System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; @@ -24,11 +25,25 @@ public sealed class CreatedAtRoute : IResult, IEndpointMetadataProvider, /// /// The route data to use for generating the URL. /// The value to format in the entity body. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] internal CreatedAtRoute(object? routeValues, TValue? value) : this(routeName: null, routeValues: routeValues, value: value) { } + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to format in the entity body. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] + internal CreatedAtRoute(string? routeName, object? routeValues, TValue? value) + : this(routeName, new RouteValueDictionary(routeValues), value) + { + } + /// /// Initializes a new instance of the class with the values /// provided. @@ -38,12 +53,12 @@ internal CreatedAtRoute(object? routeValues, TValue? value) /// The value to format in the entity body. internal CreatedAtRoute( string? routeName, - object? routeValues, + RouteValueDictionary routeValues, TValue? value) { Value = value; RouteName = routeName; - RouteValues = new RouteValueDictionary(routeValues); + RouteValues = routeValues; HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index cb7e1edf20a5..03dad23ef47c 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -34,10 +34,14 @@ public static Task WriteResultAsJsonAsync( // In this case the polymorphism is not // relevant and we don't need to box. +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. return httpContext.Response.WriteAsJsonAsync( value, options: jsonSerializerOptions, contentType: contentType); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code } var runtimeType = value.GetType(); @@ -48,11 +52,15 @@ public static Task WriteResultAsJsonAsync( // and avoid source generators issues. // https://github.com/dotnet/aspnetcore/issues/43894 // https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-polymorphism +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. return httpContext.Response.WriteAsJsonAsync( value, runtimeType, options: jsonSerializerOptions, contentType: contentType); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code } public static Task WriteResultAsContentAsync( diff --git a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj index 7808c52b5ec9..9081f2a81074 100644 --- a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj +++ b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj @@ -7,7 +7,7 @@ true aspnetcore false - enable + true Microsoft.AspNetCore.Http.Result diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt index bf63a31d1aa6..13503b61fd0d 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -152,7 +152,9 @@ Microsoft.AspNetCore.Http.HttpResults.Utf8ContentHttpResult.ExecuteAsync(Microso Microsoft.AspNetCore.Http.HttpResults.Utf8ContentHttpResult.ResponseContent.get -> System.ReadOnlyMemory Microsoft.AspNetCore.Http.HttpResults.Utf8ContentHttpResult.StatusCode.get -> int? static Microsoft.AspNetCore.Http.Results.Accepted(string? uri = null, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues, object? value = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName = null, object? routeValues = null, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.BadRequest(TValue? error) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Conflict(TValue? error) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Content(string? content, Microsoft.Net.Http.Headers.MediaTypeHeaderValue! contentType) -> Microsoft.AspNetCore.Http.IResult! @@ -166,14 +168,19 @@ static Microsoft.AspNetCore.Http.Results.Created(string? uri, object? value) -> static Microsoft.AspNetCore.Http.Results.Created(System.Uri? uri, object? value) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Created(string? uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Created(System.Uri? uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues, object? value = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(string? routeName = null, object? routeValues = null, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Json(TValue? data, System.Text.Json.JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.NotFound(TValue? value) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Ok(TValue? value) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary? routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Text(string? content, string? contentType = null, System.Text.Encoding? contentEncoding = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Text(string? content, string? contentType, System.Text.Encoding? contentEncoding) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Text(System.ReadOnlySpan utf8Content, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.UnprocessableEntity(TValue? error) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.TypedResults.AcceptedAtRoute(string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues) -> Microsoft.AspNetCore.Http.HttpResults.AcceptedAtRoute! +static Microsoft.AspNetCore.Http.TypedResults.AcceptedAtRoute(TValue? value, string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues) -> Microsoft.AspNetCore.Http.HttpResults.AcceptedAtRoute! static Microsoft.AspNetCore.Http.TypedResults.Content(string? content, string? contentType = null, System.Text.Encoding? contentEncoding = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.ContentHttpResult! static Microsoft.AspNetCore.Http.TypedResults.Content(string? content, string? contentType, System.Text.Encoding? contentEncoding) -> Microsoft.AspNetCore.Http.HttpResults.ContentHttpResult! static Microsoft.AspNetCore.Http.TypedResults.Created() -> Microsoft.AspNetCore.Http.HttpResults.Created! @@ -181,6 +188,9 @@ static Microsoft.AspNetCore.Http.TypedResults.Created(string? uri) -> Microsoft. static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri? uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! static Microsoft.AspNetCore.Http.TypedResults.Created(string? uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri? uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! +static Microsoft.AspNetCore.Http.TypedResults.CreatedAtRoute(string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues) -> Microsoft.AspNetCore.Http.HttpResults.CreatedAtRoute! +static Microsoft.AspNetCore.Http.TypedResults.CreatedAtRoute(TValue? value, string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues) -> Microsoft.AspNetCore.Http.HttpResults.CreatedAtRoute! +static Microsoft.AspNetCore.Http.TypedResults.RedirectToRoute(string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary? routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null) -> Microsoft.AspNetCore.Http.HttpResults.RedirectToRouteHttpResult! static Microsoft.AspNetCore.Http.TypedResults.Text(string? content, string? contentType = null, System.Text.Encoding? contentEncoding = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.ContentHttpResult! static Microsoft.AspNetCore.Http.TypedResults.Text(string? content, string? contentType, System.Text.Encoding? contentEncoding) -> Microsoft.AspNetCore.Http.HttpResults.ContentHttpResult! static Microsoft.AspNetCore.Http.TypedResults.Text(System.ReadOnlySpan utf8Content, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.Utf8ContentHttpResult! diff --git a/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs b/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs index 0daa608bc58d..475e8d8bdaaf 100644 --- a/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs +++ b/src/Http/Http.Results/src/RedirectToRouteHttpResult.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 System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -19,6 +20,7 @@ public sealed partial class RedirectToRouteHttpResult : IResult /// provided. /// /// The parameters for the route. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] internal RedirectToRouteHttpResult(object? routeValues) : this(routeName: null, routeValues: routeValues) { @@ -30,6 +32,7 @@ internal RedirectToRouteHttpResult(object? routeValues) /// /// The name of the route. /// The parameters for the route. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] internal RedirectToRouteHttpResult( string? routeName, object? routeValues) @@ -45,6 +48,7 @@ internal RedirectToRouteHttpResult( /// The parameters for the route. /// If set to true, makes the redirect permanent (301). /// Otherwise a temporary redirect is used (302). + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] internal RedirectToRouteHttpResult( string? routeName, object? routeValues, @@ -62,6 +66,7 @@ internal RedirectToRouteHttpResult( /// If set to true, makes the redirect permanent (301). /// Otherwise a temporary redirect is used (302). /// The fragment to add to the URL. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] internal RedirectToRouteHttpResult( string? routeName, object? routeValues, @@ -82,15 +87,41 @@ internal RedirectToRouteHttpResult( /// If set to true, make the temporary redirect (307) /// or permanent redirect (308) preserve the initial request method. /// The fragment to add to the URL. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] internal RedirectToRouteHttpResult( string? routeName, object? routeValues, bool permanent, bool preserveMethod, + string? fragment) : this( + routeName, + routeValues == null ? null : new RouteValueDictionary(routeValues), + permanent, + preserveMethod, + fragment) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the route. + /// The parameters for the route. + /// If set to true, makes the redirect permanent (301). + /// Otherwise a temporary redirect is used (302). + /// If set to true, make the temporary redirect (307) + /// or permanent redirect (308) preserve the initial request method. + /// The fragment to add to the URL. + internal RedirectToRouteHttpResult( + string? routeName, + RouteValueDictionary? routeValues, + bool permanent, + bool preserveMethod, string? fragment) { RouteName = routeName; - RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); + RouteValues = routeValues; PreserveMethod = preserveMethod; Permanent = permanent; Fragment = fragment; diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 40c283d7ec1b..7620e711a289 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Http; @@ -493,9 +494,38 @@ public static IResult LocalRedirect([StringSyntax(StringSyntaxAttribute.Uri, Uri /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. /// The fragment to add to the URL. /// The created for the response. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static IResult RedirectToRoute(string? routeName = null, object? routeValues = null, bool permanent = false, bool preserveMethod = false, string? fragment = null) => TypedResults.RedirectToRoute(routeName, routeValues, permanent, preserveMethod, fragment); + /// + /// Redirects to the specified route. + /// + /// + /// When and are set, sets the status code. + /// + /// + /// When is set, sets the status code. + /// + /// + /// When is set, sets the status code. + /// + /// + /// Otherwise, configures . + /// + /// + /// + /// The name of the route. + /// The parameters for a route. + /// Specifies whether the redirect should be permanent (301) or temporary (302). + /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. + /// The fragment to add to the URL. + /// The created for the response. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult RedirectToRoute(string? routeName, RouteValueDictionary? routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => TypedResults.RedirectToRoute(routeName, routeValues, permanent, preserveMethod, fragment); + /// /// Creates an object by specifying a . /// @@ -667,15 +697,24 @@ public static IResult ValidationProblem( problemDetails.Title = title ?? problemDetails.Title; + CopyExtensions(extensions, problemDetails); + + return TypedResults.Problem(problemDetails); + } + + private static void CopyExtensions(IDictionary? extensions, HttpValidationProblemDetails problemDetails) + { if (extensions is not null) { foreach (var extension in extensions) { +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. problemDetails.Extensions.Add(extension); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code } } - - return TypedResults.Problem(problemDetails); } /// @@ -728,6 +767,7 @@ public static IResult Created(Uri? uri, TValue? value) /// The route data to use for generating the URL. /// The value to be included in the HTTP response body. /// The created for the response. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static IResult CreatedAtRoute(string? routeName = null, object? routeValues = null, object? value = null) => CreatedAtRoute(routeName, routeValues, value); @@ -738,11 +778,36 @@ public static IResult CreatedAtRoute(string? routeName = null, object? routeValu /// The route data to use for generating the URL. /// The value to be included in the HTTP response body. /// The created for the response. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult CreatedAtRoute(string? routeName, RouteValueDictionary routeValues, object? value = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => CreatedAtRoute(routeName, routeValues, value); + + /// + /// Produces a response. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to be included in the HTTP response body. + /// The created for the response. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static IResult CreatedAtRoute(string? routeName = null, object? routeValues = null, TValue? value = default) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => value is null ? TypedResults.CreatedAtRoute(routeName, routeValues) : TypedResults.CreatedAtRoute(value, routeName, routeValues); + /// + /// Produces a response. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to be included in the HTTP response body. + /// The created for the response. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult CreatedAtRoute(string? routeName, RouteValueDictionary routeValues, TValue? value = default) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => value is null ? TypedResults.CreatedAtRoute(routeName, routeValues) : TypedResults.CreatedAtRoute(value, routeName, routeValues); + /// /// Produces a response. /// @@ -770,6 +835,7 @@ public static IResult Accepted(string? uri = null, TValue? value = defau /// The route data to use for generating the URL. /// The optional content value to format in the response body. /// The created for the response. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] #pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. public static IResult AcceptedAtRoute(string? routeName = null, object? routeValues = null, object? value = null) #pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. @@ -782,11 +848,36 @@ public static IResult AcceptedAtRoute(string? routeName = null, object? routeVal /// The route data to use for generating the URL. /// The optional content value to format in the response body. /// The created for the response. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult AcceptedAtRoute(string? routeName, RouteValueDictionary routeValues, object? value = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => AcceptedAtRoute(routeName, routeValues, value); + + /// + /// Produces a response. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The optional content value to format in the response body. + /// The created for the response. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static IResult AcceptedAtRoute(string? routeName = null, object? routeValues = null, TValue? value = default) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => value is null ? TypedResults.AcceptedAtRoute(routeName, routeValues) : TypedResults.AcceptedAtRoute(value, routeName, routeValues); + /// + /// Produces a response. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The optional content value to format in the response body. + /// The created for the response. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult AcceptedAtRoute(string? routeName, RouteValueDictionary routeValues, TValue? value = default) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => value is null ? TypedResults.AcceptedAtRoute(routeName, routeValues) : TypedResults.AcceptedAtRoute(value, routeName, routeValues); + /// /// Produces an empty result response, that when executed will do nothing. /// diff --git a/src/Http/Http.Results/src/ResultsOfT.Generated.cs b/src/Http/Http.Results/src/ResultsOfT.Generated.cs index 6034e656b573..3e4796d663ea 100644 --- a/src/Http/Http.Results/src/ResultsOfT.Generated.cs +++ b/src/Http/Http.Results/src/ResultsOfT.Generated.cs @@ -3,6 +3,7 @@ // This file is generated by a tool. See: src/Http/Http.Results/tools/ResultsOfTGenerator +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; @@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Http.HttpResults; /// /// The first result type. /// The second result type. -public sealed class Results : IResult, INestedHttpResult, IEndpointMetadataProvider +public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2> : IResult, INestedHttpResult, IEndpointMetadataProvider where TResult1 : IResult where TResult2 : IResult { @@ -83,7 +84,7 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi /// The first result type. /// The second result type. /// The third result type. -public sealed class Results : IResult, INestedHttpResult, IEndpointMetadataProvider +public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult3> : IResult, INestedHttpResult, IEndpointMetadataProvider where TResult1 : IResult where TResult2 : IResult where TResult3 : IResult @@ -155,7 +156,7 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi /// The second result type. /// The third result type. /// The fourth result type. -public sealed class Results : IResult, INestedHttpResult, IEndpointMetadataProvider +public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult3, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult4> : IResult, INestedHttpResult, IEndpointMetadataProvider where TResult1 : IResult where TResult2 : IResult where TResult3 : IResult @@ -236,7 +237,7 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi /// The third result type. /// The fourth result type. /// The fifth result type. -public sealed class Results : IResult, INestedHttpResult, IEndpointMetadataProvider +public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult3, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult4, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult5> : IResult, INestedHttpResult, IEndpointMetadataProvider where TResult1 : IResult where TResult2 : IResult where TResult3 : IResult @@ -326,7 +327,7 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi /// The fourth result type. /// The fifth result type. /// The sixth result type. -public sealed class Results : IResult, INestedHttpResult, IEndpointMetadataProvider +public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult3, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult4, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult5, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult6> : IResult, INestedHttpResult, IEndpointMetadataProvider where TResult1 : IResult where TResult2 : IResult where TResult3 : IResult diff --git a/src/Http/Http.Results/src/ResultsOfTHelper.cs b/src/Http/Http.Results/src/ResultsOfTHelper.cs index a68f55c6dfee..af8f9effa052 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; @@ -9,13 +12,37 @@ namespace Microsoft.AspNetCore.Http; internal static class ResultsOfTHelper { + public const DynamicallyAccessedMemberTypes RequireMethods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; private static readonly MethodInfo PopulateMetadataMethod = typeof(ResultsOfTHelper).GetMethod(nameof(PopulateMetadata), BindingFlags.Static | BindingFlags.NonPublic)!; - public static void PopulateMetadataIfTargetIsIEndpointMetadataProvider(MethodInfo method, EndpointBuilder builder) + public static void PopulateMetadataIfTargetIsIEndpointMetadataProvider<[DynamicallyAccessedMembers(RequireMethods)] TTarget>(MethodInfo method, EndpointBuilder builder) { if (typeof(IEndpointMetadataProvider).IsAssignableFrom(typeof(TTarget))) { - PopulateMetadataMethod.MakeGenericMethod(typeof(TTarget)).Invoke(null, new object[] { method, builder }); + var parameters = new object[] { method, builder }; + + if (RuntimeFeature.IsDynamicCodeSupported) + { + InvokeGenericPopulateMetadata(parameters); + } + else + { + // Prioritize explicit implementation. + var populateMetadataMethod = typeof(TTarget).GetMethod("Microsoft.AspNetCore.Http.Metadata.IEndpointMetadataProvider.PopulateMetadata", BindingFlags.Static | BindingFlags.NonPublic); + if (populateMetadataMethod is null) + { + populateMetadataMethod = typeof(TTarget).GetMethod("PopulateMetadata", BindingFlags.Static | BindingFlags.Public); + } + Debug.Assert(populateMetadataMethod != null, $"Couldn't find PopulateMetadata method on {typeof(TTarget)}."); + + populateMetadataMethod.Invoke(null, BindingFlags.DoNotWrapExceptions, binder: null, parameters, culture: null); + } + } + + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Validated with IsDynamicCodeSupported check.")] + static void InvokeGenericPopulateMetadata(object[] parameters) + { + PopulateMetadataMethod.MakeGenericMethod(typeof(TTarget)).Invoke(null, parameters); } } diff --git a/src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs b/src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs new file mode 100644 index 000000000000..020ad001f110 --- /dev/null +++ b/src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs @@ -0,0 +1,10 @@ +// 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.Routing; + +internal static class RouteValueDictionaryTrimmerWarning +{ + public const string Warning = "This API may perform reflection on supplied parameters which may be trimmed if not referenced directly. " + + "Consider using a different overload to avoid this issue."; +} diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index c716a69a6d66..4f5ccf4c8f14 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Http; @@ -602,7 +603,35 @@ public static RedirectHttpResult LocalRedirect([StringSyntax(StringSyntaxAttribu /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. /// The fragment to add to the URL. /// The created for the response. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static RedirectToRouteHttpResult RedirectToRoute(string? routeName = null, object? routeValues = null, bool permanent = false, bool preserveMethod = false, string? fragment = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => new( + routeName: routeName, + routeValues: routeValues, + permanent: permanent, + preserveMethod: preserveMethod, + fragment: fragment); + + /// + /// Redirects to the specified route. + /// + /// When and are set, sets the status code. + /// When is set, sets the status code. + /// When is set, sets the status code. + /// Otherwise, configures . + /// + /// + /// The name of the route. + /// The parameters for a route. + /// Specifies whether the redirect should be permanent (301) or temporary (302). + /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. + /// The fragment to add to the URL. + /// The created for the response. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static RedirectToRouteHttpResult RedirectToRoute(string? routeName, RouteValueDictionary? routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => new( routeName: routeName, routeValues: routeValues, @@ -727,15 +756,24 @@ public static ProblemHttpResult Problem( Type = type, }; + CopyExtensions(extensions, problemDetails); + + return new(problemDetails); + } + + private static void CopyExtensions(IDictionary? extensions, ProblemDetails problemDetails) + { if (extensions is not null) { foreach (var extension in extensions) { +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. problemDetails.Extensions.Add(extension); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code } } - - return new(problemDetails); } /// @@ -779,13 +817,7 @@ public static ValidationProblem ValidationProblem( problemDetails.Title = title ?? problemDetails.Title; - if (extensions is not null) - { - foreach (var extension in extensions) - { - problemDetails.Extensions.Add(extension); - } - } + CopyExtensions(extensions, problemDetails); return new(problemDetails); } @@ -849,9 +881,19 @@ public static Created Created(Uri? uri, TValue? value) /// The name of the route to use for generating the URL. /// The route data to use for generating the URL. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads public static CreatedAtRoute CreatedAtRoute(string? routeName = null, object? routeValues = null) -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads + => new(routeName, routeValues); + + /// + /// Produces a response. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The created for the response. + public static CreatedAtRoute CreatedAtRoute(string? routeName, RouteValueDictionary routeValues) => new(routeName, routeValues); /// @@ -862,11 +904,23 @@ public static CreatedAtRoute CreatedAtRoute(string? routeName = null, object? ro /// The route data to use for generating the URL. /// The value to be included in the HTTP response body. /// The created for the response. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static CreatedAtRoute CreatedAtRoute(TValue? value, string? routeName = null, object? routeValues = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => new(routeName, routeValues, value); + /// + /// Produces a response. + /// + /// The type of object that will be JSON serialized to the response body. + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to be included in the HTTP response body. + /// The created for the response. + public static CreatedAtRoute CreatedAtRoute(TValue? value, string? routeName, RouteValueDictionary routeValues) + => new(routeName, routeValues, value); + /// /// Produces a response. /// @@ -917,9 +971,19 @@ public static Accepted Accepted(Uri uri, TValue? value) /// The name of the route to use for generating the URL. /// The route data to use for generating the URL. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads public static AcceptedAtRoute AcceptedAtRoute(string? routeName = null, object? routeValues = null) -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads + => new(routeName, routeValues); + + /// + /// Produces a response. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The created for the response. + public static AcceptedAtRoute AcceptedAtRoute(string? routeName, RouteValueDictionary routeValues) => new(routeName, routeValues); /// @@ -930,11 +994,23 @@ public static AcceptedAtRoute AcceptedAtRoute(string? routeName = null, object? /// The route data to use for generating the URL. /// The value to be included in the HTTP response body. /// The created for the response. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static AcceptedAtRoute AcceptedAtRoute(TValue? value, string? routeName = null, object? routeValues = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => new(routeName, routeValues, value); + /// + /// Produces a response. + /// + /// The type of object that will be JSON serialized to the response body. + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to be included in the HTTP response body. + /// The created for the response. + public static AcceptedAtRoute AcceptedAtRoute(TValue? value, string? routeName, RouteValueDictionary routeValues) + => new(routeName, routeValues, value); + /// /// Produces an empty result response, that when executed will do nothing. /// diff --git a/src/Http/Http.Results/test/ResultsOfTHelperTests.cs b/src/Http/Http.Results/test/ResultsOfTHelperTests.cs new file mode 100644 index 000000000000..acfef104db7a --- /dev/null +++ b/src/Http/Http.Results/test/ResultsOfTHelperTests.cs @@ -0,0 +1,87 @@ +// 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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http.Metadata; + +namespace Microsoft.AspNetCore.Http.HttpResults; + +public class ResultsOfTHelperTests +{ + [Fact] + public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called() + { + var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); + var endpointBuilder = new TestEndpointBuilder(); + + ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( + methodInfo, + endpointBuilder); + + Assert.Single(endpointBuilder.Metadata); + } + + [Fact] + public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_ExplicitMethod_Called() + { + var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); + var endpointBuilder = new TestEndpointBuilder(); + + ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( + methodInfo, + endpointBuilder); + + Assert.Single(endpointBuilder.Metadata); + } + + [Fact] + public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_ExplicitAndPublicMethod_ExplicitCalled() + { + var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); + var endpointBuilder = new TestEndpointBuilder(); + + ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( + methodInfo, + endpointBuilder); + + Assert.Single(endpointBuilder.Metadata); + } + + private class TestEndpointBuilder : EndpointBuilder + { + public override Endpoint Build() + { + throw new NotImplementedException(); + } + } + + private class PublicMethodEndpointMetadataProvider : IEndpointMetadataProvider + { + public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + builder.Metadata.Add("Called"); + } + } + + private class ExplicitMethodEndpointMetadataProvider : IEndpointMetadataProvider + { + static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + builder.Metadata.Add("Called"); + } + } + + private class ExplicitAndPublicMethodEndpointMetadataProvider : IEndpointMetadataProvider + { + public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + throw new Exception("Shouldn't reach here."); + } + + static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + builder.Metadata.Add("Called"); + } + } +} diff --git a/src/Http/Http.Results/tools/ResultsOfTGenerator/Program.cs b/src/Http/Http.Results/tools/ResultsOfTGenerator/Program.cs index 6dd187576438..6755ce04712f 100644 --- a/src/Http/Http.Results/tools/ResultsOfTGenerator/Program.cs +++ b/src/Http/Http.Results/tools/ResultsOfTGenerator/Program.cs @@ -60,6 +60,7 @@ static void GenerateClassFile(string classFilePath, int typeArgCount, bool inter writer.WriteLine(); // Usings + writer.WriteLine("using System.Diagnostics.CodeAnalysis;"); writer.WriteLine("using System.Reflection;"); writer.WriteLine("using Microsoft.AspNetCore.Builder;"); writer.WriteLine("using Microsoft.AspNetCore.Http.Metadata;"); @@ -97,7 +98,7 @@ static void GenerateClassFile(string classFilePath, int typeArgCount, bool inter // Type args for (int j = 1; j <= i; j++) { - writer.Write($"TResult{j}"); + writer.Write($"[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult{j}"); if (j != i) { writer.Write(", "); diff --git a/src/Tools/Tools.slnf b/src/Tools/Tools.slnf index 5b6be32951d9..1d94de43ae44 100644 --- a/src/Tools/Tools.slnf +++ b/src/Tools/Tools.slnf @@ -33,6 +33,7 @@ "src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", "src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj", + "src\\Http\\Http.Results\\src\\Microsoft.AspNetCore.Http.Results.csproj", "src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj", "src\\Http\\Metadata\\src\\Microsoft.AspNetCore.Metadata.csproj", "src\\Http\\Routing.Abstractions\\src\\Microsoft.AspNetCore.Routing.Abstractions.csproj", @@ -115,4 +116,4 @@ "src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj" ] } -} +} \ No newline at end of file