From f43fa5b9f8a7099f796eac6230ba6d126d55e8a4 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 13 Jan 2023 17:04:46 +0800 Subject: [PATCH 01/23] [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 157ef8226525..a467315ca664 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -4,6 +4,8 @@ *REMOVED*static Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult.Instance.get -> Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult! Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task! (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) +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, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult.Instance.get -> Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult! (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) static Microsoft.AspNetCore.Http.Results.Created() -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(string! uri, object? value) -> Microsoft.AspNetCore.Http.IResult! @@ -12,11 +14,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, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues, TValue? value = default(TValue?)) -> 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.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.Created() -> Microsoft.AspNetCore.Http.HttpResults.Created! static Microsoft.AspNetCore.Http.TypedResults.Created(string? uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! 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! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! 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 From ce44659441b8a071af2fc35fcb0d3ba5d5b9b30a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 17 Jan 2023 14:03:07 +0800 Subject: [PATCH 02/23] Swap suppressions from pragmas to attributes --- src/Http/Http.Results/src/HttpResultsHelper.cs | 11 +++-------- src/Http/Http.Results/src/Results.cs | 7 +++---- src/Http/Http.Results/src/TypedResults.cs | 7 +++---- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index 03dad23ef47c..205cd12b6683 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -15,6 +15,9 @@ internal static partial class HttpResultsHelper internal const string DefaultContentType = "text/plain; charset=utf-8"; private static readonly Encoding DefaultEncoding = Encoding.UTF8; + // Remove once https://github.com/dotnet/aspnetcore/pull/45886 is done. + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] public static Task WriteResultAsJsonAsync( HttpContext httpContext, ILogger logger, @@ -34,14 +37,10 @@ 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(); @@ -52,15 +51,11 @@ 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/Results.cs b/src/Http/Http.Results/src/Results.cs index 7620e711a289..41e73214bb2b 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -702,17 +702,16 @@ public static IResult ValidationProblem( return TypedResults.Problem(problemDetails); } + // Remove once https://github.com/dotnet/aspnetcore/pull/45886 is done. + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] 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 } } } diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index 4f5ccf4c8f14..90fc56aa49ed 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -761,17 +761,16 @@ public static ProblemHttpResult Problem( return new(problemDetails); } + // Remove once https://github.com/dotnet/aspnetcore/pull/45886 is done. + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] 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 } } } From 65da7500d2fc141dd9c4f6b47d52072351eeb4f8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 17 Jan 2023 14:10:03 +0800 Subject: [PATCH 03/23] Fix build --- src/Http/Http.Results/src/HttpResultsHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index 205cd12b6683..65d5188adbe7 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.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.Text; using System.Text.Json; using Microsoft.AspNetCore.Internal; From c8cb358e0bb5e8fc5175fef3b1b4b85793709704 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 17 Jan 2023 14:26:08 +0800 Subject: [PATCH 04/23] Fix build --- src/Http/Http.Results/src/TypedResults.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index 90fc56aa49ed..5e0a5968ec7f 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -904,9 +904,9 @@ public static CreatedAtRoute CreatedAtRoute(string? routeName, RouteValueDiction /// 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 +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads 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 +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads => new(routeName, routeValues, value); /// @@ -994,9 +994,9 @@ public static AcceptedAtRoute AcceptedAtRoute(string? routeName, RouteValueDicti /// 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 +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads 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 +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads => new(routeName, routeValues, value); /// From 65aa2fe8ce2ed5e28cb73234a1385d63f415c681 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 20 Jan 2023 12:47:37 +0800 Subject: [PATCH 05/23] Generic overloads --- .../test/RouteValueDictionaryTests.cs | 58 +++++++++ src/Http/Http.Results/src/AcceptedAtRoute.cs | 2 +- .../Http.Results/src/AcceptedAtRouteOfT.cs | 2 +- src/Http/Http.Results/src/CreatedAtRoute.cs | 2 +- .../Http.Results/src/CreatedAtRouteOfT.cs | 2 +- .../Http.Results/src/HttpResultsHelper.cs | 3 + .../Http.Results/src/PublicAPI.Unshipped.txt | 22 ++-- src/Http/Http.Results/src/Results.cs | 18 +-- src/Http/Http.Results/src/TypedResults.cs | 32 +++-- src/Http/Http.Results/test/ResultsTests.cs | 113 +++++++++++++++++- .../Http.Results/test/TypedResultsTests.cs | 78 ++++++++++++ 11 files changed, 295 insertions(+), 37 deletions(-) diff --git a/src/Http/Http.Abstractions/test/RouteValueDictionaryTests.cs b/src/Http/Http.Abstractions/test/RouteValueDictionaryTests.cs index 029768ef6662..f155b7701f81 100644 --- a/src/Http/Http.Abstractions/test/RouteValueDictionaryTests.cs +++ b/src/Http/Http.Abstractions/test/RouteValueDictionaryTests.cs @@ -358,6 +358,57 @@ public void CreateFromObject_MixedCaseThrows() Assert.Equal(message, exception.Message, ignoreCase: true); } + [Fact] + public void CreateFromObject_Struct_ReadValues() + { + // Arrange + var obj = new StructAddress() { City = "Singapore" }; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.NotNull(dict._propertyStorage); + AssertEmptyArrayStorage(dict); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("City", kvp.Key); Assert.Equal("Singapore", kvp.Value); }, + kvp => { Assert.Equal("State", kvp.Key); Assert.Null(kvp.Value); }); + } + + [Fact] + public void CreateFromObject_NullableStruct_ReadValues() + { + // Arrange + StructAddress? obj = new StructAddress() { City = "Singapore" }; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.NotNull(dict._propertyStorage); + AssertEmptyArrayStorage(dict); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("City", kvp.Key); Assert.Equal("Singapore", kvp.Value); }, + kvp => { Assert.Equal("State", kvp.Key); Assert.Null(kvp.Value); }); + } + + [Fact] + public void CreateFromObject_NullStruct_ReadValues() + { + // Arrange + StructAddress? obj = null; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.Null(dict._propertyStorage); + AssertEmptyArrayStorage(dict); + Assert.Empty(dict); + } + // Our comparer is hardcoded to be OrdinalIgnoreCase no matter what. [Fact] public void Comparer_IsOrdinalIgnoreCase() @@ -2164,4 +2215,11 @@ private class Address public string? State { get; set; } } + + private struct StructAddress + { + public string? City { get; set; } + + public string? State { get; set; } + } } diff --git a/src/Http/Http.Results/src/AcceptedAtRoute.cs b/src/Http/Http.Results/src/AcceptedAtRoute.cs index 7aafcb728683..07caaed589a9 100644 --- a/src/Http/Http.Results/src/AcceptedAtRoute.cs +++ b/src/Http/Http.Results/src/AcceptedAtRoute.cs @@ -52,7 +52,7 @@ internal AcceptedAtRoute( RouteValueDictionary routeValues) { RouteName = routeName; - RouteValues = routeValues; + RouteValues = HttpResultsHelper.EnsureValue(routeValues); } /// diff --git a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs index 034628c0db83..ff5186856bea 100644 --- a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs @@ -58,7 +58,7 @@ internal AcceptedAtRoute( { Value = value; RouteName = routeName; - RouteValues = routeValues; + RouteValues = HttpResultsHelper.EnsureValue(routeValues); HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } diff --git a/src/Http/Http.Results/src/CreatedAtRoute.cs b/src/Http/Http.Results/src/CreatedAtRoute.cs index 095b99986217..9c334582fd24 100644 --- a/src/Http/Http.Results/src/CreatedAtRoute.cs +++ b/src/Http/Http.Results/src/CreatedAtRoute.cs @@ -52,7 +52,7 @@ internal CreatedAtRoute( RouteValueDictionary routeValues) { RouteName = routeName; - RouteValues = routeValues; + RouteValues = HttpResultsHelper.EnsureValue(routeValues); } /// diff --git a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs index 3114cdb82350..da93f6c3ca6b 100644 --- a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs @@ -58,7 +58,7 @@ internal CreatedAtRoute( { Value = value; RouteName = routeName; - RouteValues = routeValues; + RouteValues = HttpResultsHelper.EnsureValue(routeValues); HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index 65d5188adbe7..456bda81d93f 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -6,6 +6,7 @@ using System.Text.Json; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -146,6 +147,8 @@ public static void ApplyProblemDetailsDefaultsIfNeeded(object? value, int? statu } } + public static RouteValueDictionary EnsureValue(RouteValueDictionary? values) => values ?? new RouteValueDictionary(); + internal static partial class Log { [LoggerMessage(1, LogLevel.Information, diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt index a467315ca664..62740674e093 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -4,9 +4,9 @@ *REMOVED*static Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult.Instance.get -> Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult! Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task! (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) -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, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult.Instance.get -> Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult! (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) +static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, TRouteValues routeValues, object? value = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, TRouteValues routeValues, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Created() -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(string! uri, object? value) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(System.Uri! uri, object? value) -> Microsoft.AspNetCore.Http.IResult! @@ -14,22 +14,22 @@ 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, Microsoft.AspNetCore.Routing.RouteValueDictionary! routeValues, TValue? value = default(TValue?)) -> 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.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.Results.CreatedAtRoute(string? routeName, TRouteValues routeValues, object? value = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(string? routeName, TRouteValues routeValues, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName, TRouteValues routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.TypedResults.AcceptedAtRoute(string? routeName, TRouteValues routeValues) -> Microsoft.AspNetCore.Http.HttpResults.AcceptedAtRoute! +static Microsoft.AspNetCore.Http.TypedResults.AcceptedAtRoute(TValue? value, string? routeName, TRouteValues routeValues) -> Microsoft.AspNetCore.Http.HttpResults.AcceptedAtRoute! static Microsoft.AspNetCore.Http.TypedResults.Created() -> Microsoft.AspNetCore.Http.HttpResults.Created! static Microsoft.AspNetCore.Http.TypedResults.Created(string? uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! 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.CreatedAtRoute(string? routeName, TRouteValues routeValues) -> Microsoft.AspNetCore.Http.HttpResults.CreatedAtRoute! +static Microsoft.AspNetCore.Http.TypedResults.CreatedAtRoute(TValue? value, string? routeName, TRouteValues routeValues) -> Microsoft.AspNetCore.Http.HttpResults.CreatedAtRoute! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! -*REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! \ No newline at end of file +*REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! +static Microsoft.AspNetCore.Http.TypedResults.RedirectToRoute(string? routeName, TRouteValues routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null) -> Microsoft.AspNetCore.Http.HttpResults.RedirectToRouteHttpResult! \ No newline at end of file diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 41e73214bb2b..30d035b47e25 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -522,7 +522,7 @@ public static IResult RedirectToRoute(string? routeName = null, object? routeVal /// 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) + public static IResult RedirectToRoute(string? routeName, TRouteValues 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); @@ -768,7 +768,7 @@ public static IResult Created(Uri? uri, TValue? value) /// 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); + => CreatedAtRoute(routeName, routeValues, value); /// /// Produces a response. @@ -778,9 +778,9 @@ public static IResult CreatedAtRoute(string? routeName = null, object? routeValu /// 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) + public static IResult CreatedAtRoute(string? routeName, TRouteValues routeValues, object? value = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => CreatedAtRoute(routeName, routeValues, value); + => CreatedAtRoute(routeName, routeValues, value); /// /// Produces a response. @@ -803,7 +803,7 @@ public static IResult CreatedAtRoute(string? routeName = null, object? r /// 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) + public static IResult CreatedAtRoute(string? routeName, TRouteValues 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); @@ -838,7 +838,7 @@ public static IResult Accepted(string? uri = null, TValue? value = defau #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. - => AcceptedAtRoute(routeName, routeValues, value); + => AcceptedAtRoute(routeName, routeValues, value); /// /// Produces a response. @@ -848,9 +848,9 @@ public static IResult AcceptedAtRoute(string? routeName = null, object? routeVal /// 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) + public static IResult AcceptedAtRoute(string? routeName, TRouteValues routeValues, object? value = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => AcceptedAtRoute(routeName, routeValues, value); + => AcceptedAtRoute(routeName, routeValues, value); /// /// Produces a response. @@ -873,7 +873,7 @@ public static IResult AcceptedAtRoute(string? routeName = null, object? /// 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) + public static IResult AcceptedAtRoute(string? routeName, TRouteValues 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); diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index 5e0a5968ec7f..348aaa99d995 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -623,6 +623,7 @@ public static RedirectToRouteHttpResult RedirectToRoute(string? routeName = null /// Otherwise, configures . /// /// + /// The route values type. /// The name of the route. /// The parameters for a route. /// Specifies whether the redirect should be permanent (301) or temporary (302). @@ -630,11 +631,11 @@ public static RedirectToRouteHttpResult RedirectToRoute(string? routeName = null /// 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) + public static RedirectToRouteHttpResult RedirectToRoute(string? routeName, TRouteValues 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, + routeValues: routeValues == null ? null : CreateRouteValues(routeValues), permanent: permanent, preserveMethod: preserveMethod, fragment: fragment); @@ -889,11 +890,19 @@ public static CreatedAtRoute CreatedAtRoute(string? routeName = null, object? ro /// /// Produces a response. /// + /// The route values type. /// 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); + public static CreatedAtRoute CreatedAtRoute<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TRouteValues>(string? routeName, TRouteValues routeValues) + => new(routeName, CreateRouteValues(routeValues)); + + [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCodeAttribute", + Justification = "DynamicallyAccessedMemberTypes.All forces all properties to be available. All is required instead of just properties because inherited interface properties must be available.")] + internal static RouteValueDictionary CreateRouteValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TRouteValues>(TRouteValues values) + { + return values != null ? new RouteValueDictionary(values) : new RouteValueDictionary(); + } /// /// Produces a response. @@ -913,12 +922,13 @@ public static CreatedAtRoute CreatedAtRoute(TValue? value, strin /// Produces a response. /// /// The type of object that will be JSON serialized to the response body. + /// The route values type. /// 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); + public static CreatedAtRoute CreatedAtRoute(TValue? value, string? routeName, TRouteValues routeValues) + => new(routeName, CreateRouteValues(routeValues), value); /// /// Produces a response. @@ -979,11 +989,12 @@ public static AcceptedAtRoute AcceptedAtRoute(string? routeName = null, object? /// /// Produces a response. /// + /// The route values type. /// 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); + public static AcceptedAtRoute AcceptedAtRoute(string? routeName, TRouteValues routeValues) + => new(routeName, CreateRouteValues(routeValues)); /// /// Produces a response. @@ -1003,12 +1014,13 @@ public static AcceptedAtRoute AcceptedAtRoute(TValue? value, str /// Produces a response. /// /// The type of object that will be JSON serialized to the response body. + /// The route values type. /// 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); + public static AcceptedAtRoute AcceptedAtRoute(TValue? value, string? routeName, TRouteValues routeValues) + => new(routeName, CreateRouteValues(routeValues), value); /// /// Produces an empty result response, that when executed will do nothing. diff --git a/src/Http/Http.Results/test/ResultsTests.cs b/src/Http/Http.Results/test/ResultsTests.cs index 2220232e5739..7b5aedb6b92f 100644 --- a/src/Http/Http.Results/test/ResultsTests.cs +++ b/src/Http/Http.Results/test/ResultsTests.cs @@ -92,6 +92,24 @@ public void AcceptedAtRoute_WithRouteNameAndRouteValuesAndValue_ResultHasCorrect Assert.Equal(value, result.Value); } + [Fact] + public void AcceptedAtRoute_WithRouteNameAndRouteValueDictionaryAndValue_ResultHasCorrectValues() + { + // Arrange + var routeName = "routeName"; + var routeValues = new RouteValueDictionary { ["foo"] = 123 }; + object value = new { }; + + // Act + var result = Results.AcceptedAtRoute(routeName, routeValues, value) as AcceptedAtRoute; + + // Assert + Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); + Assert.Equal(routeName, result.RouteName); + Assert.Equal(routeValues, result.RouteValues); + Assert.Equal(value, result.Value); + } + [Fact] public void AcceptedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorrectValues() { @@ -110,6 +128,24 @@ public void AcceptedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorr Assert.Equal(value, result.Value); } + [Fact] + public void AcceptedAtRouteOfT_WithRouteNameAndRouteValueDictionaryAndValue_ResultHasCorrectValues() + { + // Arrange + var routeName = "routeName"; + var routeValues = new RouteValueDictionary { ["foo"] = 123 }; + var value = new Todo(1); + + // Act + var result = Results.AcceptedAtRoute(routeName, routeValues, value) as AcceptedAtRoute; + + // Assert + Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); + Assert.Equal(routeName, result.RouteName); + Assert.Equal(routeValues, result.RouteValues); + Assert.Equal(value, result.Value); + } + [Fact] public void AcceptedAtRoute_WithRouteNameAndRouteValues_ResultHasCorrectValues() { @@ -640,6 +676,24 @@ public void CreatedAtRoute_WithRouteNameAndRouteValuesAndValue_ResultHasCorrectV Assert.Equal(value, result.Value); } + [Fact] + public void CreatedAtRoute_WithRouteNameAndRouteValueDictionaryAndValue_ResultHasCorrectValues() + { + // Arrange + var routeName = "routeName"; + var routeValues = new RouteValueDictionary { ["foo"] = 123 }; + object value = new { }; + + // Act + var result = Results.CreatedAtRoute(routeName, routeValues, value) as CreatedAtRoute; + + // Assert + Assert.Equal(StatusCodes.Status201Created, result.StatusCode); + Assert.Equal(routeName, result.RouteName); + Assert.Equal(routeValues, result.RouteValues); + Assert.Equal(value, result.Value); + } + [Fact] public void CreatedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorrectValues() { @@ -658,6 +712,24 @@ public void CreatedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorre Assert.Equal(value, result.Value); } + [Fact] + public void CreatedAtRouteOfT_WithRouteNameAndRouteValueDictionaryAndValue_ResultHasCorrectValues() + { + // Arrange + var routeName = "routeName"; + var routeValues = new RouteValueDictionary { ["foo"] = 123 }; + var value = new Todo(1); + + // Act + var result = Results.CreatedAtRoute(routeName, routeValues, value) as CreatedAtRoute; + + // Assert + Assert.Equal(StatusCodes.Status201Created, result.StatusCode); + Assert.Equal(routeName, result.RouteName); + Assert.Equal(routeValues, result.RouteValues); + Assert.Equal(value, result.Value); + } + [Fact] public void CreatedAtRoute_WithRouteNameAndValue_ResultHasCorrectValues() { @@ -666,7 +738,7 @@ public void CreatedAtRoute_WithRouteNameAndValue_ResultHasCorrectValues() object value = new { }; // Act - var result = Results.CreatedAtRoute(routeName, null, value) as CreatedAtRoute; + var result = Results.CreatedAtRoute(routeName, (object)null, value) as CreatedAtRoute; // Assert Assert.Equal(StatusCodes.Status201Created, result.StatusCode); @@ -683,7 +755,7 @@ public void CreatedAtRouteOfT_WithRouteNameAndValue_ResultHasCorrectValues() var value = new Todo(1); // Act - var result = Results.CreatedAtRoute(routeName, null, value) as CreatedAtRoute; + var result = Results.CreatedAtRoute(routeName, (object)null, value) as CreatedAtRoute; // Assert Assert.Equal(StatusCodes.Status201Created, result.StatusCode); @@ -699,7 +771,7 @@ public void CreatedAtRoute_WithRouteName_ResultHasCorrectValues() var routeName = "routeName"; // Act - var result = Results.CreatedAtRoute(routeName, null, null) as CreatedAtRoute; + var result = Results.CreatedAtRoute(routeName, (object)null, null) as CreatedAtRoute; // Assert Assert.Equal(StatusCodes.Status201Created, result.StatusCode); @@ -1164,6 +1236,25 @@ public void RedirectToRoute_WithRouteNameAndRouteValuesAndFragment_ResultHasCorr Assert.Equal(fragment, result.Fragment); } + [Fact] + public void RedirectToRoute_WithRouteNameAndRouteValueDictionaryAndFragment_ResultHasCorrectValues() + { + // Arrange + var routeName = "routeName"; + var routeValues = new RouteValueDictionary { ["foo"] = 123 }; + var fragment = "test"; + + // Act + var result = Results.RedirectToRoute(routeName, routeValues, true, true, fragment) as RedirectToRouteHttpResult; + + // Assert + Assert.Equal(routeName, result.RouteName); + Assert.Equal(routeValues, result.RouteValues); + Assert.True(result.Permanent); + Assert.True(result.PreserveMethod); + Assert.Equal(fragment, result.Fragment); + } + [Fact] public void RedirectToRoute_WithNoArgs_ResultHasCorrectValues() { @@ -1175,6 +1266,17 @@ public void RedirectToRoute_WithNoArgs_ResultHasCorrectValues() Assert.Null(result.RouteValues); } + [Fact] + public void RedirectToRoute_WithNoArgs_RouteValueDictionary_ResultHasCorrectValues() + { + // Act + var result = Results.RedirectToRoute(null, (RouteValueDictionary)null) as RedirectToRouteHttpResult; + + // Assert + Assert.Null(result.RouteName); + Assert.Null(result.RouteValues); + } + [Fact] public void StatusCode_ResultHasCorrectValues() { @@ -1330,6 +1432,8 @@ private static string GetMemberName(Expression expression) { (() => Results.Accepted(null, null), typeof(Accepted)), (() => Results.Accepted(null, new()), typeof(Accepted)), + (() => Results.AcceptedAtRoute("routeName", (object)null, null), typeof(AcceptedAtRoute)), + (() => Results.AcceptedAtRoute("routeName", (object)null, new()), typeof(AcceptedAtRoute)), (() => Results.AcceptedAtRoute("routeName", null, null), typeof(AcceptedAtRoute)), (() => Results.AcceptedAtRoute("routeName", null, new()), typeof(AcceptedAtRoute)), (() => Results.BadRequest(null), typeof(BadRequest)), @@ -1343,6 +1447,8 @@ private static string GetMemberName(Expression expression) (() => Results.Created("/path", null), typeof(Created)), (() => Results.Created(), typeof(Created)), (() => Results.Created("/path", new()), typeof(Created)), + (() => Results.CreatedAtRoute("routeName", (object)null, null), typeof(CreatedAtRoute)), + (() => Results.CreatedAtRoute("routeName", (object)null, new()), typeof(CreatedAtRoute)), (() => Results.CreatedAtRoute("routeName", null, null), typeof(CreatedAtRoute)), (() => Results.CreatedAtRoute("routeName", null, new()), typeof(CreatedAtRoute)), (() => Results.Empty, typeof(EmptyHttpResult)), @@ -1364,6 +1470,7 @@ private static string GetMemberName(Expression expression) (() => Results.Text("content", null, null, null), typeof(ContentHttpResult)), (() => Results.Redirect("/path", false, false), typeof(RedirectHttpResult)), (() => Results.LocalRedirect("/path", false, false), typeof(RedirectHttpResult)), + (() => Results.RedirectToRoute("routeName", (object)null, false, false, null), typeof(RedirectToRouteHttpResult)), (() => Results.RedirectToRoute("routeName", null, false, false, null), typeof(RedirectToRouteHttpResult)), (() => Results.SignIn(new(), null, null), typeof(SignInHttpResult)), (() => Results.SignOut(new(), null), typeof(SignOutHttpResult)), diff --git a/src/Http/Http.Results/test/TypedResultsTests.cs b/src/Http/Http.Results/test/TypedResultsTests.cs index 067af3f2d560..4945702f2c5a 100644 --- a/src/Http/Http.Results/test/TypedResultsTests.cs +++ b/src/Http/Http.Results/test/TypedResultsTests.cs @@ -147,6 +147,38 @@ public void AcceptedAtRoute_WithNoArgs_ResultHasCorrectValues() Assert.NotNull(result.RouteValues); } + [Fact] + public void AcceptedAtRoute_WithRouteValues_ResultHasCorrectValues() + { + // Arrange + var routeValues = new { foo = 123 }; + + // Act + var result = TypedResults.AcceptedAtRoute(routeValues: routeValues); + + // Assert + Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); + Assert.Null(result.RouteName); + Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); + } + + [Fact] + public void AcceptedAtRoute_WithRouteValuesAndValue_ResultHasCorrectValues() + { + // Arrange + var routeValues = new { foo = 123 }; + var value = new { }; + + // Act + var result = TypedResults.AcceptedAtRoute(value: value, routeValues: routeValues); + + // Assert + Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); + Assert.Null(result.RouteName); + Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); + Assert.Equal(value, result.Value); + } + [Fact] public void BadRequest_WithValue_ResultHasCorrectValues() { @@ -687,6 +719,38 @@ public void CreatedAtRoute_WithNoArgs_ResultHasCorrectValues() Assert.Equal(new RouteValueDictionary(), result.RouteValues); } + [Fact] + public void CreatedAtRoute_WithRouteValues_ResultHasCorrectValues() + { + // Arrange + var routeValues = new { foo = 123 }; + + // Act + var result = TypedResults.CreatedAtRoute(routeValues: routeValues); + + // Assert + Assert.Equal(StatusCodes.Status201Created, result.StatusCode); + Assert.Null(result.RouteName); + Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); + } + + [Fact] + public void CreatedAtRoute_WithRouteValuesAndValue_ResultHasCorrectValues() + { + // Arrange + var routeValues = new { foo = 123 }; + var value = new { }; + + // Act + var result = TypedResults.CreatedAtRoute(value: value, routeValues: routeValues); + + // Assert + Assert.Equal(StatusCodes.Status201Created, result.StatusCode); + Assert.Null(result.RouteName); + Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); + Assert.Equal(value, result.Value); + } + [Fact] public void Empty_IsEmptyInstance() { @@ -1093,6 +1157,20 @@ public void RedirectToRoute_WithNoArgs_ResultHasCorrectValues() Assert.Null(result.RouteValues); } + [Fact] + public void RedirectToRoute_WithRouteValues_ResultHasCorrectValues() + { + // Arrange + var routeValues = new { foo = 123 }; + + // Act + var result = TypedResults.RedirectToRoute(routeValues: routeValues); + + // Assert + Assert.Null(result.RouteName); + Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); + } + [Fact] public void StatusCode_ResultHasCorrectValues() { From fbfa83e7799ad37864e17bb8f9d68b9899d25a5e Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 20 Jan 2023 13:21:07 +0800 Subject: [PATCH 06/23] Remove public API changes --- .../Http.Results/src/PublicAPI.Unshipped.txt | 11 -- src/Http/Http.Results/src/Results.cs | 80 +----------- src/Http/Http.Results/src/TypedResults.cs | 77 ------------ src/Http/Http.Results/test/ResultsTests.cs | 115 +----------------- .../Http.Results/test/TypedResultsTests.cs | 78 ------------ 5 files changed, 6 insertions(+), 355 deletions(-) diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt index 62740674e093..9b4e949b4b25 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -5,8 +5,6 @@ Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task! (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) static Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult.Instance.get -> Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult! (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) -static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, TRouteValues routeValues, object? value = null) -> Microsoft.AspNetCore.Http.IResult! -static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, TRouteValues routeValues, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Created() -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(string! uri, object? value) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(System.Uri! uri, object? value) -> Microsoft.AspNetCore.Http.IResult! @@ -14,22 +12,13 @@ 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, TRouteValues routeValues, object? value = null) -> Microsoft.AspNetCore.Http.IResult! -static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(string? routeName, TRouteValues routeValues, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! -static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName, TRouteValues routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null) -> Microsoft.AspNetCore.Http.IResult! -static Microsoft.AspNetCore.Http.TypedResults.AcceptedAtRoute(string? routeName, TRouteValues routeValues) -> Microsoft.AspNetCore.Http.HttpResults.AcceptedAtRoute! -static Microsoft.AspNetCore.Http.TypedResults.AcceptedAtRoute(TValue? value, string? routeName, TRouteValues routeValues) -> Microsoft.AspNetCore.Http.HttpResults.AcceptedAtRoute! static Microsoft.AspNetCore.Http.TypedResults.Created() -> Microsoft.AspNetCore.Http.HttpResults.Created! static Microsoft.AspNetCore.Http.TypedResults.Created(string? uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! 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, TRouteValues routeValues) -> Microsoft.AspNetCore.Http.HttpResults.CreatedAtRoute! -static Microsoft.AspNetCore.Http.TypedResults.CreatedAtRoute(TValue? value, string? routeName, TRouteValues routeValues) -> Microsoft.AspNetCore.Http.HttpResults.CreatedAtRoute! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! -*REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! -static Microsoft.AspNetCore.Http.TypedResults.RedirectToRoute(string? routeName, TRouteValues routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null) -> Microsoft.AspNetCore.Http.HttpResults.RedirectToRouteHttpResult! \ No newline at end of file diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 30d035b47e25..a7d3b45edfb8 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -498,34 +498,6 @@ public static IResult LocalRedirect([StringSyntax(StringSyntaxAttribute.Uri, Uri 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, TRouteValues 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 . /// @@ -768,19 +740,7 @@ public static IResult Created(Uri? uri, TValue? value) /// 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); - - /// - /// 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, TRouteValues routeValues, object? value = null) -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => CreatedAtRoute(routeName, routeValues, value); + => CreatedAtRoute(routeName, routeValues, value); /// /// Produces a response. @@ -795,18 +755,6 @@ public static IResult CreatedAtRoute(string? routeName = null, object? r #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, TRouteValues 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. /// @@ -838,19 +786,7 @@ public static IResult Accepted(string? uri = null, TValue? value = defau #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. - => 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. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static IResult AcceptedAtRoute(string? routeName, TRouteValues routeValues, object? value = null) -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => AcceptedAtRoute(routeName, routeValues, value); + => AcceptedAtRoute(routeName, routeValues, value); /// /// Produces a response. @@ -865,18 +801,6 @@ public static IResult AcceptedAtRoute(string? routeName = null, object? #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, TRouteValues 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/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index 348aaa99d995..9b8e9f5795d7 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -614,32 +614,6 @@ public static RedirectToRouteHttpResult RedirectToRoute(string? routeName = null 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 route values type. - /// 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, TRouteValues 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 == null ? null : CreateRouteValues(routeValues), - permanent: permanent, - preserveMethod: preserveMethod, - fragment: fragment); - /// /// Creates a object by specifying a . /// @@ -887,23 +861,6 @@ public static CreatedAtRoute CreatedAtRoute(string? routeName = null, object? ro #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 route values type. - /// 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<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TRouteValues>(string? routeName, TRouteValues routeValues) - => new(routeName, CreateRouteValues(routeValues)); - - [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCodeAttribute", - Justification = "DynamicallyAccessedMemberTypes.All forces all properties to be available. All is required instead of just properties because inherited interface properties must be available.")] - internal static RouteValueDictionary CreateRouteValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TRouteValues>(TRouteValues values) - { - return values != null ? new RouteValueDictionary(values) : new RouteValueDictionary(); - } - /// /// Produces a response. /// @@ -918,18 +875,6 @@ public static CreatedAtRoute CreatedAtRoute(TValue? value, strin #pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads => new(routeName, routeValues, value); - /// - /// Produces a response. - /// - /// The type of object that will be JSON serialized to the response body. - /// The route values type. - /// 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, TRouteValues routeValues) - => new(routeName, CreateRouteValues(routeValues), value); - /// /// Produces a response. /// @@ -986,16 +931,6 @@ public static AcceptedAtRoute AcceptedAtRoute(string? routeName = null, object? #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 route values type. - /// 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, TRouteValues routeValues) - => new(routeName, CreateRouteValues(routeValues)); - /// /// Produces a response. /// @@ -1010,18 +945,6 @@ public static AcceptedAtRoute AcceptedAtRoute(TValue? value, str #pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads => new(routeName, routeValues, value); - /// - /// Produces a response. - /// - /// The type of object that will be JSON serialized to the response body. - /// The route values type. - /// 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, TRouteValues routeValues) - => new(routeName, CreateRouteValues(routeValues), value); - /// /// Produces an empty result response, that when executed will do nothing. /// diff --git a/src/Http/Http.Results/test/ResultsTests.cs b/src/Http/Http.Results/test/ResultsTests.cs index 7b5aedb6b92f..d87b8d2c7e68 100644 --- a/src/Http/Http.Results/test/ResultsTests.cs +++ b/src/Http/Http.Results/test/ResultsTests.cs @@ -92,24 +92,6 @@ public void AcceptedAtRoute_WithRouteNameAndRouteValuesAndValue_ResultHasCorrect Assert.Equal(value, result.Value); } - [Fact] - public void AcceptedAtRoute_WithRouteNameAndRouteValueDictionaryAndValue_ResultHasCorrectValues() - { - // Arrange - var routeName = "routeName"; - var routeValues = new RouteValueDictionary { ["foo"] = 123 }; - object value = new { }; - - // Act - var result = Results.AcceptedAtRoute(routeName, routeValues, value) as AcceptedAtRoute; - - // Assert - Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); - Assert.Equal(routeName, result.RouteName); - Assert.Equal(routeValues, result.RouteValues); - Assert.Equal(value, result.Value); - } - [Fact] public void AcceptedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorrectValues() { @@ -128,24 +110,6 @@ public void AcceptedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorr Assert.Equal(value, result.Value); } - [Fact] - public void AcceptedAtRouteOfT_WithRouteNameAndRouteValueDictionaryAndValue_ResultHasCorrectValues() - { - // Arrange - var routeName = "routeName"; - var routeValues = new RouteValueDictionary { ["foo"] = 123 }; - var value = new Todo(1); - - // Act - var result = Results.AcceptedAtRoute(routeName, routeValues, value) as AcceptedAtRoute; - - // Assert - Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); - Assert.Equal(routeName, result.RouteName); - Assert.Equal(routeValues, result.RouteValues); - Assert.Equal(value, result.Value); - } - [Fact] public void AcceptedAtRoute_WithRouteNameAndRouteValues_ResultHasCorrectValues() { @@ -676,24 +640,6 @@ public void CreatedAtRoute_WithRouteNameAndRouteValuesAndValue_ResultHasCorrectV Assert.Equal(value, result.Value); } - [Fact] - public void CreatedAtRoute_WithRouteNameAndRouteValueDictionaryAndValue_ResultHasCorrectValues() - { - // Arrange - var routeName = "routeName"; - var routeValues = new RouteValueDictionary { ["foo"] = 123 }; - object value = new { }; - - // Act - var result = Results.CreatedAtRoute(routeName, routeValues, value) as CreatedAtRoute; - - // Assert - Assert.Equal(StatusCodes.Status201Created, result.StatusCode); - Assert.Equal(routeName, result.RouteName); - Assert.Equal(routeValues, result.RouteValues); - Assert.Equal(value, result.Value); - } - [Fact] public void CreatedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorrectValues() { @@ -712,24 +658,6 @@ public void CreatedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorre Assert.Equal(value, result.Value); } - [Fact] - public void CreatedAtRouteOfT_WithRouteNameAndRouteValueDictionaryAndValue_ResultHasCorrectValues() - { - // Arrange - var routeName = "routeName"; - var routeValues = new RouteValueDictionary { ["foo"] = 123 }; - var value = new Todo(1); - - // Act - var result = Results.CreatedAtRoute(routeName, routeValues, value) as CreatedAtRoute; - - // Assert - Assert.Equal(StatusCodes.Status201Created, result.StatusCode); - Assert.Equal(routeName, result.RouteName); - Assert.Equal(routeValues, result.RouteValues); - Assert.Equal(value, result.Value); - } - [Fact] public void CreatedAtRoute_WithRouteNameAndValue_ResultHasCorrectValues() { @@ -738,7 +666,7 @@ public void CreatedAtRoute_WithRouteNameAndValue_ResultHasCorrectValues() object value = new { }; // Act - var result = Results.CreatedAtRoute(routeName, (object)null, value) as CreatedAtRoute; + var result = Results.CreatedAtRoute(routeName, null, value) as CreatedAtRoute; // Assert Assert.Equal(StatusCodes.Status201Created, result.StatusCode); @@ -755,7 +683,7 @@ public void CreatedAtRouteOfT_WithRouteNameAndValue_ResultHasCorrectValues() var value = new Todo(1); // Act - var result = Results.CreatedAtRoute(routeName, (object)null, value) as CreatedAtRoute; + var result = Results.CreatedAtRoute(routeName, null, value) as CreatedAtRoute; // Assert Assert.Equal(StatusCodes.Status201Created, result.StatusCode); @@ -771,7 +699,7 @@ public void CreatedAtRoute_WithRouteName_ResultHasCorrectValues() var routeName = "routeName"; // Act - var result = Results.CreatedAtRoute(routeName, (object)null, null) as CreatedAtRoute; + var result = Results.CreatedAtRoute(routeName, null, null) as CreatedAtRoute; // Assert Assert.Equal(StatusCodes.Status201Created, result.StatusCode); @@ -809,7 +737,7 @@ public void Json_WithAllArgs_ResultHasCorrectValues() var options = new JsonSerializerOptions(); var contentType = "application/custom+json"; var statusCode = StatusCodes.Status208AlreadyReported; - + // Act var result = Results.Json(data, options, contentType, statusCode) as JsonHttpResult; @@ -1236,25 +1164,6 @@ public void RedirectToRoute_WithRouteNameAndRouteValuesAndFragment_ResultHasCorr Assert.Equal(fragment, result.Fragment); } - [Fact] - public void RedirectToRoute_WithRouteNameAndRouteValueDictionaryAndFragment_ResultHasCorrectValues() - { - // Arrange - var routeName = "routeName"; - var routeValues = new RouteValueDictionary { ["foo"] = 123 }; - var fragment = "test"; - - // Act - var result = Results.RedirectToRoute(routeName, routeValues, true, true, fragment) as RedirectToRouteHttpResult; - - // Assert - Assert.Equal(routeName, result.RouteName); - Assert.Equal(routeValues, result.RouteValues); - Assert.True(result.Permanent); - Assert.True(result.PreserveMethod); - Assert.Equal(fragment, result.Fragment); - } - [Fact] public void RedirectToRoute_WithNoArgs_ResultHasCorrectValues() { @@ -1266,17 +1175,6 @@ public void RedirectToRoute_WithNoArgs_ResultHasCorrectValues() Assert.Null(result.RouteValues); } - [Fact] - public void RedirectToRoute_WithNoArgs_RouteValueDictionary_ResultHasCorrectValues() - { - // Act - var result = Results.RedirectToRoute(null, (RouteValueDictionary)null) as RedirectToRouteHttpResult; - - // Assert - Assert.Null(result.RouteName); - Assert.Null(result.RouteValues); - } - [Fact] public void StatusCode_ResultHasCorrectValues() { @@ -1432,8 +1330,6 @@ private static string GetMemberName(Expression expression) { (() => Results.Accepted(null, null), typeof(Accepted)), (() => Results.Accepted(null, new()), typeof(Accepted)), - (() => Results.AcceptedAtRoute("routeName", (object)null, null), typeof(AcceptedAtRoute)), - (() => Results.AcceptedAtRoute("routeName", (object)null, new()), typeof(AcceptedAtRoute)), (() => Results.AcceptedAtRoute("routeName", null, null), typeof(AcceptedAtRoute)), (() => Results.AcceptedAtRoute("routeName", null, new()), typeof(AcceptedAtRoute)), (() => Results.BadRequest(null), typeof(BadRequest)), @@ -1447,8 +1343,6 @@ private static string GetMemberName(Expression expression) (() => Results.Created("/path", null), typeof(Created)), (() => Results.Created(), typeof(Created)), (() => Results.Created("/path", new()), typeof(Created)), - (() => Results.CreatedAtRoute("routeName", (object)null, null), typeof(CreatedAtRoute)), - (() => Results.CreatedAtRoute("routeName", (object)null, new()), typeof(CreatedAtRoute)), (() => Results.CreatedAtRoute("routeName", null, null), typeof(CreatedAtRoute)), (() => Results.CreatedAtRoute("routeName", null, new()), typeof(CreatedAtRoute)), (() => Results.Empty, typeof(EmptyHttpResult)), @@ -1470,7 +1364,6 @@ private static string GetMemberName(Expression expression) (() => Results.Text("content", null, null, null), typeof(ContentHttpResult)), (() => Results.Redirect("/path", false, false), typeof(RedirectHttpResult)), (() => Results.LocalRedirect("/path", false, false), typeof(RedirectHttpResult)), - (() => Results.RedirectToRoute("routeName", (object)null, false, false, null), typeof(RedirectToRouteHttpResult)), (() => Results.RedirectToRoute("routeName", null, false, false, null), typeof(RedirectToRouteHttpResult)), (() => Results.SignIn(new(), null, null), typeof(SignInHttpResult)), (() => Results.SignOut(new(), null), typeof(SignOutHttpResult)), diff --git a/src/Http/Http.Results/test/TypedResultsTests.cs b/src/Http/Http.Results/test/TypedResultsTests.cs index 4945702f2c5a..067af3f2d560 100644 --- a/src/Http/Http.Results/test/TypedResultsTests.cs +++ b/src/Http/Http.Results/test/TypedResultsTests.cs @@ -147,38 +147,6 @@ public void AcceptedAtRoute_WithNoArgs_ResultHasCorrectValues() Assert.NotNull(result.RouteValues); } - [Fact] - public void AcceptedAtRoute_WithRouteValues_ResultHasCorrectValues() - { - // Arrange - var routeValues = new { foo = 123 }; - - // Act - var result = TypedResults.AcceptedAtRoute(routeValues: routeValues); - - // Assert - Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); - Assert.Null(result.RouteName); - Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); - } - - [Fact] - public void AcceptedAtRoute_WithRouteValuesAndValue_ResultHasCorrectValues() - { - // Arrange - var routeValues = new { foo = 123 }; - var value = new { }; - - // Act - var result = TypedResults.AcceptedAtRoute(value: value, routeValues: routeValues); - - // Assert - Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); - Assert.Null(result.RouteName); - Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); - Assert.Equal(value, result.Value); - } - [Fact] public void BadRequest_WithValue_ResultHasCorrectValues() { @@ -719,38 +687,6 @@ public void CreatedAtRoute_WithNoArgs_ResultHasCorrectValues() Assert.Equal(new RouteValueDictionary(), result.RouteValues); } - [Fact] - public void CreatedAtRoute_WithRouteValues_ResultHasCorrectValues() - { - // Arrange - var routeValues = new { foo = 123 }; - - // Act - var result = TypedResults.CreatedAtRoute(routeValues: routeValues); - - // Assert - Assert.Equal(StatusCodes.Status201Created, result.StatusCode); - Assert.Null(result.RouteName); - Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); - } - - [Fact] - public void CreatedAtRoute_WithRouteValuesAndValue_ResultHasCorrectValues() - { - // Arrange - var routeValues = new { foo = 123 }; - var value = new { }; - - // Act - var result = TypedResults.CreatedAtRoute(value: value, routeValues: routeValues); - - // Assert - Assert.Equal(StatusCodes.Status201Created, result.StatusCode); - Assert.Null(result.RouteName); - Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); - Assert.Equal(value, result.Value); - } - [Fact] public void Empty_IsEmptyInstance() { @@ -1157,20 +1093,6 @@ public void RedirectToRoute_WithNoArgs_ResultHasCorrectValues() Assert.Null(result.RouteValues); } - [Fact] - public void RedirectToRoute_WithRouteValues_ResultHasCorrectValues() - { - // Arrange - var routeValues = new { foo = 123 }; - - // Act - var result = TypedResults.RedirectToRoute(routeValues: routeValues); - - // Assert - Assert.Null(result.RouteName); - Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); - } - [Fact] public void StatusCode_ResultHasCorrectValues() { From d3940688458b7cadab0ce5e11bcab91c77f997f2 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 20 Jan 2023 13:32:28 +0800 Subject: [PATCH 07/23] Fix build --- src/Http/Http.Results/src/PublicAPI.Unshipped.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt index 9b4e949b4b25..22cbf5420124 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -22,3 +22,4 @@ static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri? uri, T *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! +*REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! From 094258a48f1bb6598350396188964863dcfc4aba Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 20 Jan 2023 13:36:49 +0800 Subject: [PATCH 08/23] Clean up --- src/Http/Http.Results/src/TypedResults.cs | 32 ----------------------- 1 file changed, 32 deletions(-) diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index 9b8e9f5795d7..934461e203e6 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -140,9 +140,7 @@ public static ContentHttpResult Text(string? content, string? contentType, Encod /// The content type (MIME type). /// The status code to return. /// The created object for the response. -#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. public static Utf8ContentHttpResult Text(ReadOnlySpan utf8Content, string? contentType = null, int? statusCode = null) -#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. => new Utf8ContentHttpResult(utf8Content, contentType, statusCode); /// @@ -160,9 +158,7 @@ public static Utf8ContentHttpResult Text(ReadOnlySpan utf8Content, string? /// The content encoding. /// The status code to return. /// The created object for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static ContentHttpResult Text(string? content, string? contentType = null, Encoding? contentEncoding = null, int? statusCode = null) -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters { MediaTypeHeaderValue? mediaTypeHeaderValue = null; if (contentType is not null) @@ -217,9 +213,7 @@ public static JsonHttpResult Json(TValue? data, JsonSerializerOp /// The of when the file was last modified. /// The associated with the file. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static FileContentHttpResult File( -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters byte[] fileContents, string? contentType = null, string? fileDownloadName = null, @@ -254,9 +248,7 @@ public static FileContentHttpResult File( /// The of when the file was last modified. /// The associated with the file. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static FileContentHttpResult Bytes( -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters byte[] contents, string? contentType = null, string? fileDownloadName = null, @@ -289,9 +281,7 @@ public static FileContentHttpResult Bytes( /// The of when the file was last modified. /// The associated with the file. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static FileContentHttpResult Bytes( -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters ReadOnlyMemory contents, string? contentType = null, string? fileDownloadName = null, @@ -328,9 +318,7 @@ public static FileContentHttpResult Bytes( /// and perform conditional requests. /// Set to true to enable range requests processing. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static FileStreamHttpResult File( -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters Stream fileStream, string? contentType = null, string? fileDownloadName = null, @@ -371,9 +359,7 @@ public static FileStreamHttpResult File( /// and perform conditional requests. /// Set to true to enable range requests processing. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static FileStreamHttpResult Stream( -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters Stream stream, string? contentType = null, string? fileDownloadName = null, @@ -411,9 +397,7 @@ public static FileStreamHttpResult Stream( /// and perform conditional requests. /// Set to true to enable range requests processing. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static FileStreamHttpResult Stream( -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters PipeReader pipeReader, string? contentType = null, string? fileDownloadName = null, @@ -447,9 +431,7 @@ public static FileStreamHttpResult Stream( /// The to be configure the ETag response header /// and perform conditional requests. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static PushStreamHttpResult Stream( -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters Func streamWriterCallback, string? contentType = null, string? fileDownloadName = null, @@ -480,9 +462,7 @@ public static PushStreamHttpResult Stream( /// The associated with the file. /// Set to true to enable range requests processing. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static PhysicalFileHttpResult PhysicalFile( -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters string path, string? contentType = null, string? fileDownloadName = null, @@ -518,9 +498,7 @@ public static PhysicalFileHttpResult PhysicalFile( /// The associated with the file. /// Set to true to enable range requests processing. /// The created for the response. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static VirtualFileHttpResult VirtualFile( -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters string path, string? contentType = null, string? fileDownloadName = null, @@ -604,9 +582,7 @@ public static RedirectHttpResult LocalRedirect([StringSyntax(StringSyntaxAttribu /// 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, @@ -856,9 +832,7 @@ public static Created Created(Uri? uri, TValue? value) /// The route data to use for generating the URL. /// 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 CreatedAtRoute CreatedAtRoute(string? routeName = null, object? routeValues = null) -#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads => new(routeName, routeValues); /// @@ -870,9 +844,7 @@ public static CreatedAtRoute CreatedAtRoute(string? routeName = null, object? ro /// The value to be included in the HTTP 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 CreatedAtRoute CreatedAtRoute(TValue? value, string? routeName = null, object? routeValues = null) -#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads => new(routeName, routeValues, value); /// @@ -926,9 +898,7 @@ public static Accepted Accepted(Uri uri, TValue? value) /// The route data to use for generating the URL. /// 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 AcceptedAtRoute AcceptedAtRoute(string? routeName = null, object? routeValues = null) -#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads => new(routeName, routeValues); /// @@ -940,9 +910,7 @@ public static AcceptedAtRoute AcceptedAtRoute(string? routeName = null, object? /// The value to be included in the HTTP 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 AcceptedAtRoute AcceptedAtRoute(TValue? value, string? routeName = null, object? routeValues = null) -#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads => new(routeName, routeValues, value); /// From 29cf972d89314544e1b03145ff65a3e88ebb1c61 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 20 Jan 2023 15:59:38 +0800 Subject: [PATCH 09/23] Fix warning? --- src/Http/Http.Results/src/ResultsOfTHelper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Http/Http.Results/src/ResultsOfTHelper.cs b/src/Http/Http.Results/src/ResultsOfTHelper.cs index af8f9effa052..a0ba20292caa 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -40,6 +40,8 @@ internal static class ResultsOfTHelper } [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Validated with IsDynamicCodeSupported check.")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:MakeGenericMethod", + Justification = "PopulateMetadataMethod calls a public static method. TTarget is annotated to include public methods.")] static void InvokeGenericPopulateMetadata(object[] parameters) { PopulateMetadataMethod.MakeGenericMethod(typeof(TTarget)).Invoke(null, parameters); From 9cbc6870748c1404f74227e023e5b7097c61efcf Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 20 Jan 2023 22:00:21 +0800 Subject: [PATCH 10/23] PR feedback --- src/Http/Http.Results/src/ResultsOfTHelper.cs | 1 + src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs | 2 +- src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Http/Http.Results/src/ResultsOfTHelper.cs b/src/Http/Http.Results/src/ResultsOfTHelper.cs index a0ba20292caa..b8c01cdd81d7 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -39,6 +39,7 @@ internal static class ResultsOfTHelper } } + // TODO: Remove IL3050 suppress when https://github.com/dotnet/linker/issues/2715 is complete. [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Validated with IsDynamicCodeSupported check.")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:MakeGenericMethod", Justification = "PopulateMetadataMethod calls a public static method. TTarget is annotated to include public methods.")] diff --git a/src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs b/src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs index 020ad001f110..1e04c55c0a50 100644 --- a/src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs +++ b/src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs @@ -6,5 +6,5 @@ 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."; + "Initialize a RouteValueDictionary with route values to avoid this issue."; } diff --git a/src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs b/src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs index 020ad001f110..1e04c55c0a50 100644 --- a/src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs +++ b/src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs @@ -6,5 +6,5 @@ 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."; + "Initialize a RouteValueDictionary with route values to avoid this issue."; } From 31608d887b1cd909fff468c986e8e70f2fc89fe0 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 21 Jan 2023 08:54:21 +0800 Subject: [PATCH 11/23] React to dependencies and PR feedback --- src/Http/Http.Results/src/AcceptedAtRoute.cs | 3 ++- src/Http/Http.Results/src/AcceptedAtRouteOfT.cs | 3 ++- src/Http/Http.Results/src/CreatedAtRoute.cs | 3 ++- src/Http/Http.Results/src/CreatedAtRouteOfT.cs | 3 ++- .../src/Microsoft.AspNetCore.Http.Results.csproj | 1 + src/Http/Http.Results/src/PublicAPI.Unshipped.txt | 2 +- src/Http/Http.Results/src/RedirectToRouteHttpResult.cs | 1 + src/Http/Http.Results/src/Results.cs | 4 +--- src/Http/Http.Results/src/TypedResults.cs | 4 +--- src/Http/Http.Results/test/ResultsTests.cs | 8 ++++---- .../src/LinkGeneratorEndpointNameAddressExtensions.cs | 1 + .../src/LinkGeneratorRouteValuesAddressExtensions.cs | 1 + .../Routing/src/Microsoft.AspNetCore.Routing.csproj | 1 + src/Http/Routing/src/Patterns/RoutePatternFactory.cs | 1 + .../Routing/src/RouteValueDictionaryTrimmerWarning.cs | 10 ---------- .../RouteValueDictionaryTrimmerWarning.cs | 2 +- 16 files changed, 22 insertions(+), 26 deletions(-) delete mode 100644 src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs rename src/{Http/Http.Results/src => Shared}/RouteValueDictionaryTrimmerWarning.cs (91%) diff --git a/src/Http/Http.Results/src/AcceptedAtRoute.cs b/src/Http/Http.Results/src/AcceptedAtRoute.cs index 07caaed589a9..f2ef26966846 100644 --- a/src/Http/Http.Results/src/AcceptedAtRoute.cs +++ b/src/Http/Http.Results/src/AcceptedAtRoute.cs @@ -5,6 +5,7 @@ using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -52,7 +53,7 @@ internal AcceptedAtRoute( RouteValueDictionary routeValues) { RouteName = routeName; - RouteValues = HttpResultsHelper.EnsureValue(routeValues); + RouteValues = routeValues ?? new RouteValueDictionary(); } /// diff --git a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs index ff5186856bea..516010c02af0 100644 --- a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs @@ -5,6 +5,7 @@ using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -58,7 +59,7 @@ internal AcceptedAtRoute( { Value = value; RouteName = routeName; - RouteValues = HttpResultsHelper.EnsureValue(routeValues); + RouteValues = routeValues ?? new RouteValueDictionary(); HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } diff --git a/src/Http/Http.Results/src/CreatedAtRoute.cs b/src/Http/Http.Results/src/CreatedAtRoute.cs index 9c334582fd24..9ccc8a21cbf4 100644 --- a/src/Http/Http.Results/src/CreatedAtRoute.cs +++ b/src/Http/Http.Results/src/CreatedAtRoute.cs @@ -5,6 +5,7 @@ using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -52,7 +53,7 @@ internal CreatedAtRoute( RouteValueDictionary routeValues) { RouteName = routeName; - RouteValues = HttpResultsHelper.EnsureValue(routeValues); + RouteValues = routeValues ?? new RouteValueDictionary(); } /// diff --git a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs index da93f6c3ca6b..1f399aa2935a 100644 --- a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs @@ -5,6 +5,7 @@ using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -58,7 +59,7 @@ internal CreatedAtRoute( { Value = value; RouteName = routeName; - RouteValues = HttpResultsHelper.EnsureValue(routeValues); + RouteValues = routeValues ?? new RouteValueDictionary(); HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } 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 9081f2a81074..3b5c22a26f99 100644 --- a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj +++ b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt index 22cbf5420124..157ef8226525 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -22,4 +22,4 @@ static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri? uri, T *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! -*REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! +*REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! \ No newline at end of file diff --git a/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs b/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs index 475e8d8bdaaf..bd823fc9c13b 100644 --- a/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs +++ b/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index a7d3b45edfb8..7a395964ba9f 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; @@ -674,9 +675,6 @@ public static IResult ValidationProblem( return TypedResults.Problem(problemDetails); } - // Remove once https://github.com/dotnet/aspnetcore/pull/45886 is done. - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] private static void CopyExtensions(IDictionary? extensions, HttpValidationProblemDetails problemDetails) { if (extensions is not null) diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index 934461e203e6..8455fc26193c 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; @@ -712,9 +713,6 @@ public static ProblemHttpResult Problem( return new(problemDetails); } - // Remove once https://github.com/dotnet/aspnetcore/pull/45886 is done. - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] - [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] private static void CopyExtensions(IDictionary? extensions, ProblemDetails problemDetails) { if (extensions is not null) diff --git a/src/Http/Http.Results/test/ResultsTests.cs b/src/Http/Http.Results/test/ResultsTests.cs index d87b8d2c7e68..37b6d2378493 100644 --- a/src/Http/Http.Results/test/ResultsTests.cs +++ b/src/Http/Http.Results/test/ResultsTests.cs @@ -586,7 +586,7 @@ public void Created_WithNullStringUriAndValue_SetsLocationNull() //Arrange object value = new { }; - // Act + // Act var result = Results.Created(default(string), value) as Created; //Assert @@ -600,7 +600,7 @@ public void Created_WithEmptyStringUriAndValue_SetsLocationEmpty() //Arrange object value = new { }; - // Act + // Act var result = Results.Created(string.Empty, value) as Created; //Assert @@ -614,7 +614,7 @@ public void Created_WithNullUriAndValue_SetsLocationNull() //Arrange object value = new { }; - // Act + // Act var result = Results.Created(default(Uri), value) as Created; //Assert @@ -737,7 +737,7 @@ public void Json_WithAllArgs_ResultHasCorrectValues() var options = new JsonSerializerOptions(); var contentType = "application/custom+json"; var statusCode = StatusCodes.Status208AlreadyReported; - + // Act var result = Results.Json(data, options, contentType, statusCode) as JsonHttpResult; diff --git a/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs b/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs index c875e522dcf4..be520e200ac6 100644 --- a/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs +++ b/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Internal; namespace Microsoft.AspNetCore.Routing; diff --git a/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs b/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs index 4a26f2036a86..6871eed9dd1b 100644 --- a/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs +++ b/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Internal; namespace Microsoft.AspNetCore.Routing; diff --git a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj index bfa84d35edb5..068c308b367c 100644 --- a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj +++ b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj @@ -33,6 +33,7 @@ + diff --git a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs index e88f09aba12e..0a87d2a7b434 100644 --- a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs +++ b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Routing.Constraints; namespace Microsoft.AspNetCore.Routing.Patterns; diff --git a/src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs b/src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs deleted file mode 100644 index 1e04c55c0a50..000000000000 --- a/src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs +++ /dev/null @@ -1,10 +0,0 @@ -// 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. " + - "Initialize a RouteValueDictionary with route values to avoid this issue."; -} diff --git a/src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs b/src/Shared/RouteValueDictionaryTrimmerWarning.cs similarity index 91% rename from src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs rename to src/Shared/RouteValueDictionaryTrimmerWarning.cs index 1e04c55c0a50..67b7d866b006 100644 --- a/src/Http/Http.Results/src/RouteValueDictionaryTrimmerWarning.cs +++ b/src/Shared/RouteValueDictionaryTrimmerWarning.cs @@ -1,7 +1,7 @@ // 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; +namespace Microsoft.AspNetCore.Internal; internal static class RouteValueDictionaryTrimmerWarning { From 3da754a98cc4a330ad4f01142cd7f0fc5cb377b7 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 21 Jan 2023 09:02:19 +0800 Subject: [PATCH 12/23] Clean up --- src/Http/Http.Results/src/HttpResultsHelper.cs | 2 -- src/Http/Http.Results/test/ResultsTests.cs | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index 456bda81d93f..fcf295ddcd87 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -147,8 +147,6 @@ public static void ApplyProblemDetailsDefaultsIfNeeded(object? value, int? statu } } - public static RouteValueDictionary EnsureValue(RouteValueDictionary? values) => values ?? new RouteValueDictionary(); - internal static partial class Log { [LoggerMessage(1, LogLevel.Information, diff --git a/src/Http/Http.Results/test/ResultsTests.cs b/src/Http/Http.Results/test/ResultsTests.cs index 37b6d2378493..2220232e5739 100644 --- a/src/Http/Http.Results/test/ResultsTests.cs +++ b/src/Http/Http.Results/test/ResultsTests.cs @@ -586,7 +586,7 @@ public void Created_WithNullStringUriAndValue_SetsLocationNull() //Arrange object value = new { }; - // Act + // Act var result = Results.Created(default(string), value) as Created; //Assert @@ -600,7 +600,7 @@ public void Created_WithEmptyStringUriAndValue_SetsLocationEmpty() //Arrange object value = new { }; - // Act + // Act var result = Results.Created(string.Empty, value) as Created; //Assert @@ -614,7 +614,7 @@ public void Created_WithNullUriAndValue_SetsLocationNull() //Arrange object value = new { }; - // Act + // Act var result = Results.Created(default(Uri), value) as Created; //Assert From 5e670bcfcb6b3909182bd320a079ad5773745e2e Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 24 Jan 2023 09:11:13 +0800 Subject: [PATCH 13/23] Fix build --- src/Http/Http.Results/src/HttpResultsHelper.cs | 1 - src/Http/Http.Results/src/Results.cs | 1 - src/Http/Http.Results/src/TypedResults.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index fcf295ddcd87..65d5188adbe7 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -6,7 +6,6 @@ using System.Text.Json; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 7a395964ba9f..6acc40b9e096 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Http; diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index 8455fc26193c..9093a346ec25 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Http; From d7b1956a42e9213fcae968cdd037159fbe89dff0 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 24 Jan 2023 11:48:31 +0800 Subject: [PATCH 14/23] Revert ResultsOfT --- .../Http.Results/src/ResultsOfT.Generated.cs | 11 +++--- src/Http/Http.Results/src/ResultsOfTHelper.cs | 35 +++---------------- .../tools/ResultsOfTGenerator/Program.cs | 3 +- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/src/Http/Http.Results/src/ResultsOfT.Generated.cs b/src/Http/Http.Results/src/ResultsOfT.Generated.cs index 3e4796d663ea..6034e656b573 100644 --- a/src/Http/Http.Results/src/ResultsOfT.Generated.cs +++ b/src/Http/Http.Results/src/ResultsOfT.Generated.cs @@ -3,7 +3,6 @@ // 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; @@ -21,7 +20,7 @@ namespace Microsoft.AspNetCore.Http.HttpResults; /// /// The first result type. /// The second result type. -public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2> : IResult, INestedHttpResult, IEndpointMetadataProvider +public sealed class Results : IResult, INestedHttpResult, IEndpointMetadataProvider where TResult1 : IResult where TResult2 : IResult { @@ -84,7 +83,7 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi /// The first result type. /// The second result type. /// The third result type. -public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult3> : IResult, INestedHttpResult, IEndpointMetadataProvider +public sealed class Results : IResult, INestedHttpResult, IEndpointMetadataProvider where TResult1 : IResult where TResult2 : IResult where TResult3 : IResult @@ -156,7 +155,7 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi /// The second result type. /// The third result type. /// The fourth result type. -public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult3, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult4> : IResult, INestedHttpResult, IEndpointMetadataProvider +public sealed class Results : IResult, INestedHttpResult, IEndpointMetadataProvider where TResult1 : IResult where TResult2 : IResult where TResult3 : IResult @@ -237,7 +236,7 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi /// The third result type. /// The fourth result type. /// The fifth result type. -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 +public sealed class Results : IResult, INestedHttpResult, IEndpointMetadataProvider where TResult1 : IResult where TResult2 : IResult where TResult3 : IResult @@ -327,7 +326,7 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi /// The fourth result type. /// The fifth result type. /// The sixth result type. -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 +public sealed class Results : 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 b8c01cdd81d7..75eec0583514 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -1,10 +1,8 @@ // 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; @@ -12,40 +10,15 @@ 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<[DynamicallyAccessedMembers(RequireMethods)] TTarget>(MethodInfo method, EndpointBuilder builder) + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Only called from unsafe code.")] + [UnconditionalSuppressMessage("Trimmer", "IL2060", Justification = "Only called from unsafe code.")] + public static void PopulateMetadataIfTargetIsIEndpointMetadataProvider(MethodInfo method, EndpointBuilder builder) { if (typeof(IEndpointMetadataProvider).IsAssignableFrom(typeof(TTarget))) { - 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); - } - } - - // TODO: Remove IL3050 suppress when https://github.com/dotnet/linker/issues/2715 is complete. - [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Validated with IsDynamicCodeSupported check.")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:MakeGenericMethod", - Justification = "PopulateMetadataMethod calls a public static method. TTarget is annotated to include public methods.")] - static void InvokeGenericPopulateMetadata(object[] parameters) - { - PopulateMetadataMethod.MakeGenericMethod(typeof(TTarget)).Invoke(null, parameters); + PopulateMetadataMethod.MakeGenericMethod(typeof(TTarget)).Invoke(null, new object[] { method, builder }); } } diff --git a/src/Http/Http.Results/tools/ResultsOfTGenerator/Program.cs b/src/Http/Http.Results/tools/ResultsOfTGenerator/Program.cs index 6755ce04712f..6dd187576438 100644 --- a/src/Http/Http.Results/tools/ResultsOfTGenerator/Program.cs +++ b/src/Http/Http.Results/tools/ResultsOfTGenerator/Program.cs @@ -60,7 +60,6 @@ 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;"); @@ -98,7 +97,7 @@ static void GenerateClassFile(string classFilePath, int typeArgCount, bool inter // Type args for (int j = 1; j <= i; j++) { - writer.Write($"[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult{j}"); + writer.Write($"TResult{j}"); if (j != i) { writer.Write(", "); From 1c60cba810a4d2e0bbcf9994f45b29b809e04043 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 24 Jan 2023 12:16:33 +0800 Subject: [PATCH 15/23] PR feedback --- src/Http/Http.Results/src/AcceptedAtRoute.cs | 2 +- src/Http/Http.Results/src/AcceptedAtRouteOfT.cs | 2 +- src/Http/Http.Results/src/CreatedAtRoute.cs | 2 +- src/Http/Http.Results/src/CreatedAtRouteOfT.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Http/Http.Results/src/AcceptedAtRoute.cs b/src/Http/Http.Results/src/AcceptedAtRoute.cs index f2ef26966846..c3f14deb4206 100644 --- a/src/Http/Http.Results/src/AcceptedAtRoute.cs +++ b/src/Http/Http.Results/src/AcceptedAtRoute.cs @@ -53,7 +53,7 @@ internal AcceptedAtRoute( RouteValueDictionary routeValues) { RouteName = routeName; - RouteValues = routeValues ?? new RouteValueDictionary(); + RouteValues = routeValues; } /// diff --git a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs index 516010c02af0..c9d85f1e9c7d 100644 --- a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs @@ -59,7 +59,7 @@ internal AcceptedAtRoute( { Value = value; RouteName = routeName; - RouteValues = routeValues ?? new RouteValueDictionary(); + 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 9ccc8a21cbf4..9948e811de16 100644 --- a/src/Http/Http.Results/src/CreatedAtRoute.cs +++ b/src/Http/Http.Results/src/CreatedAtRoute.cs @@ -53,7 +53,7 @@ internal CreatedAtRoute( RouteValueDictionary routeValues) { RouteName = routeName; - RouteValues = routeValues ?? new RouteValueDictionary(); + RouteValues = routeValues; } /// diff --git a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs index 1f399aa2935a..f5b026979c5f 100644 --- a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs @@ -59,7 +59,7 @@ internal CreatedAtRoute( { Value = value; RouteName = routeName; - RouteValues = routeValues ?? new RouteValueDictionary(); + RouteValues = routeValues; HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } From 27b1d06e4e42238b8b5cd0600cf6cf6abb55315b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 26 Jan 2023 11:20:42 +0800 Subject: [PATCH 16/23] Update comment --- src/Http/Http.Results/src/HttpResultsHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index 65d5188adbe7..7aade3bb8ad0 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -16,7 +16,7 @@ internal static partial class HttpResultsHelper internal const string DefaultContentType = "text/plain; charset=utf-8"; private static readonly Encoding DefaultEncoding = Encoding.UTF8; - // Remove once https://github.com/dotnet/aspnetcore/pull/45886 is done. + // Remove once https://github.com/dotnet/aspnetcore/pull/46008 is done. [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] public static Task WriteResultAsJsonAsync( From 30adf95bfd4317ca1379d2c5e17af3ea6287ee0a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 26 Jan 2023 13:19:56 +0800 Subject: [PATCH 17/23] Revert "Revert ResultsOfT" This reverts commit d7b1956a42e9213fcae968cdd037159fbe89dff0. --- .../Http.Results/src/ResultsOfT.Generated.cs | 11 +++--- src/Http/Http.Results/src/ResultsOfTHelper.cs | 35 ++++++++++++++++--- .../tools/ResultsOfTGenerator/Program.cs | 3 +- 3 files changed, 39 insertions(+), 10 deletions(-) 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 75eec0583514..b8c01cdd81d7 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -1,8 +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; @@ -10,15 +12,40 @@ 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)!; - [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Only called from unsafe code.")] - [UnconditionalSuppressMessage("Trimmer", "IL2060", Justification = "Only called from unsafe code.")] - 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); + } + } + + // TODO: Remove IL3050 suppress when https://github.com/dotnet/linker/issues/2715 is complete. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Validated with IsDynamicCodeSupported check.")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:MakeGenericMethod", + Justification = "PopulateMetadataMethod calls a public static method. TTarget is annotated to include public methods.")] + static void InvokeGenericPopulateMetadata(object[] parameters) + { + PopulateMetadataMethod.MakeGenericMethod(typeof(TTarget)).Invoke(null, parameters); } } 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(", "); From 797bae2831eed424d0b72ded0f8e5d53318c7136 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 26 Jan 2023 13:38:17 +0800 Subject: [PATCH 18/23] Add comment --- src/Http/Http.Results/src/ResultsOfTHelper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Http/Http.Results/src/ResultsOfTHelper.cs b/src/Http/Http.Results/src/ResultsOfTHelper.cs index b8c01cdd81d7..a770aac4b09d 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -15,6 +15,8 @@ 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)!; + // TODO: Improve calling static interface method with reflection + // https://github.com/dotnet/aspnetcore/issues/46267 public static void PopulateMetadataIfTargetIsIEndpointMetadataProvider<[DynamicallyAccessedMembers(RequireMethods)] TTarget>(MethodInfo method, EndpointBuilder builder) { if (typeof(IEndpointMetadataProvider).IsAssignableFrom(typeof(TTarget))) From c392990df24e91657184cc6ba8bdf7efe75344dc Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 27 Jan 2023 07:13:39 +0800 Subject: [PATCH 19/23] Update src/Http/Http.Results/src/ResultsOfTHelper.cs Co-authored-by: Eric Erhardt --- src/Http/Http.Results/src/ResultsOfTHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Http.Results/src/ResultsOfTHelper.cs b/src/Http/Http.Results/src/ResultsOfTHelper.cs index a770aac4b09d..91ffb96bb569 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -44,7 +44,7 @@ internal static class ResultsOfTHelper // TODO: Remove IL3050 suppress when https://github.com/dotnet/linker/issues/2715 is complete. [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Validated with IsDynamicCodeSupported check.")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:MakeGenericMethod", - Justification = "PopulateMetadataMethod calls a public static method. TTarget is annotated to include public methods.")] + Justification = "The call to MakeGenericMethod is safe due to the fact that PopulateMetadata does not have a DynamicallyAccessMembers attribute and TTarget is annotated to preserve all methods to preserve the PopulateMetadata method.")] static void InvokeGenericPopulateMetadata(object[] parameters) { PopulateMetadataMethod.MakeGenericMethod(typeof(TTarget)).Invoke(null, parameters); From 8fa56ac38b55783327fc3f3e58a9f77958004db5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 27 Jan 2023 07:33:51 +0800 Subject: [PATCH 20/23] Add tests for ResultsOfTHelper when IsDynamicCodeSupported is false --- .../test/ResultsOfTHelperTests.cs | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/src/Http/Http.Results/test/ResultsOfTHelperTests.cs b/src/Http/Http.Results/test/ResultsOfTHelperTests.cs index acfef104db7a..ee19b9ca618d 100644 --- a/src/Http/Http.Results/test/ResultsOfTHelperTests.cs +++ b/src/Http/Http.Results/test/ResultsOfTHelperTests.cs @@ -4,48 +4,77 @@ using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Metadata; +using Microsoft.AspNetCore.Testing; +using Microsoft.DotNet.RemoteExecutor; namespace Microsoft.AspNetCore.Http.HttpResults; public class ResultsOfTHelperTests { - [Fact] - public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called() + [ConditionalTheory] + [RemoteExecutionSupported] + [InlineData(true)] + [InlineData(false)] + public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called(bool isDynamicCodeSupported) { - var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); - var endpointBuilder = new TestEndpointBuilder(); + var options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", isDynamicCodeSupported.ToString()); - ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( - methodInfo, - endpointBuilder); + using var remoteHandle = RemoteExecutor.Invoke(static () => + { + var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); + var endpointBuilder = new TestEndpointBuilder(); + + ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( + methodInfo, + endpointBuilder); - Assert.Single(endpointBuilder.Metadata); + Assert.Single(endpointBuilder.Metadata); + }, options); } - [Fact] - public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_ExplicitMethod_Called() + [ConditionalTheory] + [RemoteExecutionSupported] + [InlineData(true)] + [InlineData(false)] + public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_ExplicitMethod_Called(bool isDynamicCodeSupported) { - var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); - var endpointBuilder = new TestEndpointBuilder(); + var options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", isDynamicCodeSupported.ToString()); + + using var remoteHandle = RemoteExecutor.Invoke(static () => + { + var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); + var endpointBuilder = new TestEndpointBuilder(); - ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( - methodInfo, - endpointBuilder); + ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( + methodInfo, + endpointBuilder); - Assert.Single(endpointBuilder.Metadata); + Assert.Single(endpointBuilder.Metadata); + }, options); } - [Fact] - public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_ExplicitAndPublicMethod_ExplicitCalled() + [ConditionalTheory] + [RemoteExecutionSupported] + [InlineData(true)] + [InlineData(false)] + public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_ExplicitAndPublicMethod_ExplicitCalled(bool isDynamicCodeSupported) { - var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); - var endpointBuilder = new TestEndpointBuilder(); + var options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", isDynamicCodeSupported.ToString()); + + using var remoteHandle = RemoteExecutor.Invoke(static () => + { + var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); + var endpointBuilder = new TestEndpointBuilder(); - ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( - methodInfo, - endpointBuilder); + ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( + methodInfo, + endpointBuilder); - Assert.Single(endpointBuilder.Metadata); + Assert.Single(endpointBuilder.Metadata); + }, options); } private class TestEndpointBuilder : EndpointBuilder From 0958a0ef0ac4caa021999178e36ac4e1dcc5e49f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 27 Jan 2023 10:40:27 +0800 Subject: [PATCH 21/23] Add test --- src/Http/Http.Results/src/ResultsOfTHelper.cs | 6 ++ .../test/ResultsOfTHelperTests.cs | 63 +++++++++++++------ 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/Http/Http.Results/src/ResultsOfTHelper.cs b/src/Http/Http.Results/src/ResultsOfTHelper.cs index 91ffb96bb569..74a1e5560270 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -35,6 +35,12 @@ internal static class ResultsOfTHelper { populateMetadataMethod = typeof(TTarget).GetMethod("PopulateMetadata", BindingFlags.Static | BindingFlags.Public); } + // Method won't be found if it is from a default interface implementation. + // Improve with https://github.com/dotnet/aspnetcore/issues/46267 + if (populateMetadataMethod is null) + { + throw new InvalidOperationException($"Couldn't populate metadata for {typeof(TTarget).Name}."); + } Debug.Assert(populateMetadataMethod != null, $"Couldn't find PopulateMetadata method on {typeof(TTarget)}."); populateMetadataMethod.Invoke(null, BindingFlags.DoNotWrapExceptions, binder: null, parameters, culture: null); diff --git a/src/Http/Http.Results/test/ResultsOfTHelperTests.cs b/src/Http/Http.Results/test/ResultsOfTHelperTests.cs index ee19b9ca618d..90b5b1d3a977 100644 --- a/src/Http/Http.Results/test/ResultsOfTHelperTests.cs +++ b/src/Http/Http.Results/test/ResultsOfTHelperTests.cs @@ -22,14 +22,9 @@ public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Cal using var remoteHandle = RemoteExecutor.Invoke(static () => { - var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); - var endpointBuilder = new TestEndpointBuilder(); + var metadata = GetMetadata(); - ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( - methodInfo, - endpointBuilder); - - Assert.Single(endpointBuilder.Metadata); + Assert.Single(metadata); }, options); } @@ -44,14 +39,9 @@ public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_ExplicitMethod_C using var remoteHandle = RemoteExecutor.Invoke(static () => { - var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); - var endpointBuilder = new TestEndpointBuilder(); - - ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( - methodInfo, - endpointBuilder); + var metadata = GetMetadata(); - Assert.Single(endpointBuilder.Metadata); + Assert.Single(metadata); }, options); } @@ -66,17 +56,38 @@ public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_ExplicitAndPubli using var remoteHandle = RemoteExecutor.Invoke(static () => { - var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(PopulateMetadataIfTargetIsIEndpointMetadataProvider_PublicMethod_Called)); - var endpointBuilder = new TestEndpointBuilder(); + var metadata = GetMetadata(); - ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( - methodInfo, - endpointBuilder); + Assert.Single(metadata); + }, options); + } - Assert.Single(endpointBuilder.Metadata); + [ConditionalFact] + [RemoteExecutionSupported] + public void PopulateMetadataIfTargetIsIEndpointMetadataProvider_DefaultInterfaceMethod_NoDynamicCode_Throws() + { + var options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", false.ToString()); + + using var remoteHandle = RemoteExecutor.Invoke(static () => + { + // Improve with https://github.com/dotnet/aspnetcore/issues/46267 + Assert.Throws(() => GetMetadata()); }, options); } + private static IList GetMetadata() + { + var methodInfo = typeof(ResultsOfTHelperTests).GetMethod(nameof(GetMetadata), BindingFlags.NonPublic | BindingFlags.Static); + var endpointBuilder = new TestEndpointBuilder(); + + ResultsOfTHelper.PopulateMetadataIfTargetIsIEndpointMetadataProvider( + methodInfo, + endpointBuilder); + + return endpointBuilder.Metadata; + } + private class TestEndpointBuilder : EndpointBuilder { public override Endpoint Build() @@ -113,4 +124,16 @@ static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, Endpoi builder.Metadata.Add("Called"); } } + + private interface IMyEndpointMetadataProvider : IEndpointMetadataProvider + { + static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + builder.Metadata.Add("Called"); + } + } + + private class DefaultInterfaceMethodEndpointMetadataProvider : IMyEndpointMetadataProvider + { + } } From cf0ead149136057888306af4198540b32f48bca2 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 27 Jan 2023 10:41:45 +0800 Subject: [PATCH 22/23] Improve error message --- src/Http/Http.Results/src/ResultsOfTHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Http.Results/src/ResultsOfTHelper.cs b/src/Http/Http.Results/src/ResultsOfTHelper.cs index 74a1e5560270..13a13a46f5d1 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -39,7 +39,7 @@ internal static class ResultsOfTHelper // Improve with https://github.com/dotnet/aspnetcore/issues/46267 if (populateMetadataMethod is null) { - throw new InvalidOperationException($"Couldn't populate metadata for {typeof(TTarget).Name}."); + throw new InvalidOperationException($"Couldn't populate metadata for {typeof(TTarget).Name}. PopulateMetadata must by on the type. A default interface implementation isn't supported with AOT."); } Debug.Assert(populateMetadataMethod != null, $"Couldn't find PopulateMetadata method on {typeof(TTarget)}."); From 4dc6ac7820a794db10b963bde384b0d81e31e4f6 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 27 Jan 2023 10:51:31 +0800 Subject: [PATCH 23/23] Improve error message --- src/Http/Http.Results/src/ResultsOfTHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Http.Results/src/ResultsOfTHelper.cs b/src/Http/Http.Results/src/ResultsOfTHelper.cs index 13a13a46f5d1..71eefb826714 100644 --- a/src/Http/Http.Results/src/ResultsOfTHelper.cs +++ b/src/Http/Http.Results/src/ResultsOfTHelper.cs @@ -39,7 +39,7 @@ internal static class ResultsOfTHelper // Improve with https://github.com/dotnet/aspnetcore/issues/46267 if (populateMetadataMethod is null) { - throw new InvalidOperationException($"Couldn't populate metadata for {typeof(TTarget).Name}. PopulateMetadata must by on the type. A default interface implementation isn't supported with AOT."); + throw new InvalidOperationException($"Couldn't populate metadata for {typeof(TTarget).Name}. PopulateMetadata must by defined on the result type. A default interface implementation isn't supported with AOT."); } Debug.Assert(populateMetadataMethod != null, $"Couldn't find PopulateMetadata method on {typeof(TTarget)}.");