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.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 55489db2d29c..c3f14deb4206 100644
--- a/src/Http/Http.Results/src/AcceptedAtRoute.cs
+++ b/src/Http/Http.Results/src/AcceptedAtRoute.cs
@@ -1,9 +1,11 @@
// 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;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -22,11 +24,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 +50,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..c9d85f1e9c7d 100644
--- a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs
+++ b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs
@@ -1,9 +1,11 @@
// 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;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -24,11 +26,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 +54,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..9948e811de16 100644
--- a/src/Http/Http.Results/src/CreatedAtRoute.cs
+++ b/src/Http/Http.Results/src/CreatedAtRoute.cs
@@ -1,9 +1,11 @@
// 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;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -22,11 +24,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 +50,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..f5b026979c5f 100644
--- a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs
+++ b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs
@@ -1,9 +1,11 @@
// 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;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -24,11 +26,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 +54,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..7aade3bb8ad0 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;
@@ -15,6 +16,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/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(
HttpContext httpContext,
ILogger logger,
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..3b5c22a26f99 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 @@
trueaspnetcorefalse
- enable
+ trueMicrosoft.AspNetCore.Http.Result
@@ -19,6 +19,7 @@
+
diff --git a/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs b/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs
index 0daa608bc58d..bd823fc9c13b 100644
--- a/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs
+++ b/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs
@@ -1,6 +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.CodeAnalysis;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -19,6 +21,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 +33,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 +49,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 +67,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 +88,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..6acc40b9e096 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.Net.Http.Headers;
@@ -493,6 +494,7 @@ 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);
@@ -667,6 +669,13 @@ 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)
@@ -674,8 +683,6 @@ public static IResult ValidationProblem(
problemDetails.Extensions.Add(extension);
}
}
-
- return TypedResults.Problem(problemDetails);
}
///
@@ -728,6 +735,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