diff --git a/eng/TrimmableProjects.props b/eng/TrimmableProjects.props index 1d9abef1fe58..c2dc91c7263d 100644 --- a/eng/TrimmableProjects.props +++ b/eng/TrimmableProjects.props @@ -19,6 +19,7 @@ + diff --git a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj index a0018a4018c8..e5e1ce15a3d9 100644 --- a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj +++ b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj @@ -8,7 +8,6 @@ true aspnetcore false - enable true diff --git a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs index d8990104cb12..d0946793248e 100644 --- a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs +++ b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.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.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -18,6 +19,8 @@ namespace Microsoft.AspNetCore.Builder; /// public static class EndpointRouteBuilderExtensions { + internal const string MapEndpointTrimmerWarning = "This API may perform reflection on the supplied delegate and its parameters. These types may be trimmed if not directly referenced."; + // Avoid creating a new array every call private static readonly string[] GetVerb = new[] { HttpMethods.Get }; private static readonly string[] PostVerb = new[] { HttpMethods.Post }; @@ -33,6 +36,7 @@ public static class EndpointRouteBuilderExtensions /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(EndpointRouteBuilderExtensions.MapEndpointTrimmerWarning)] public static IEndpointConventionBuilder MapGet( this IEndpointRouteBuilder endpoints, string pattern, @@ -220,6 +224,7 @@ public static IEndpointConventionBuilder Map( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder MapGet( this IEndpointRouteBuilder endpoints, string pattern, @@ -236,6 +241,7 @@ public static RouteHandlerBuilder MapGet( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder MapPost( this IEndpointRouteBuilder endpoints, string pattern, @@ -252,6 +258,7 @@ public static RouteHandlerBuilder MapPost( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder MapPut( this IEndpointRouteBuilder endpoints, string pattern, @@ -268,6 +275,7 @@ public static RouteHandlerBuilder MapPut( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder MapDelete( this IEndpointRouteBuilder endpoints, string pattern, @@ -284,6 +292,7 @@ public static RouteHandlerBuilder MapDelete( /// The route pattern. /// The executed when the endpoint is matched. /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder MapPatch( this IEndpointRouteBuilder endpoints, string pattern, @@ -301,6 +310,7 @@ public static RouteHandlerBuilder MapPatch( /// The delegate executed when the endpoint is matched. /// HTTP methods that the endpoint will match. /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder MapMethods( this IEndpointRouteBuilder endpoints, string pattern, @@ -348,6 +358,7 @@ static bool ShouldDisableInferredBody(string method) /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder Map( this IEndpointRouteBuilder endpoints, string pattern, @@ -364,6 +375,7 @@ public static RouteHandlerBuilder Map( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder Map( this IEndpointRouteBuilder endpoints, RoutePattern pattern, @@ -391,6 +403,7 @@ public static RouteHandlerBuilder Map( /// {*path:nonfile}. The order of the registered endpoint will be int.MaxValue. /// /// + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder MapFallback(this IEndpointRouteBuilder endpoints, Delegate handler) { if (endpoints == null) @@ -427,6 +440,7 @@ public static RouteHandlerBuilder MapFallback(this IEndpointRouteBuilder endpoin /// to exclude requests for static files. /// /// + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] public static RouteHandlerBuilder MapFallback( this IEndpointRouteBuilder endpoints, string pattern, @@ -453,6 +467,7 @@ public static RouteHandlerBuilder MapFallback( return conventionBuilder; } + [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] private static RouteHandlerBuilder Map( this IEndpointRouteBuilder endpoints, RoutePattern pattern, @@ -515,7 +530,11 @@ private static RouteHandlerBuilder Map( } var routeHandlerBuilder = new RouteHandlerBuilder(dataSource.AddEndpointBuilder(builder)); - routeHandlerBuilder.Add(endpointBuilder => + routeHandlerBuilder.Add(RouteHandlerBuilderConvention); + + [UnconditionalSuppressMessage("Trimmer", "IL2026", Justification = "We surface a RequireUnreferencedCode in the call to enclosing Map method. " + + "The trimmer is unable to infer this on the nested lambda.")] + void RouteHandlerBuilderConvention(EndpointBuilder endpointBuilder) { var options = new RequestDelegateFactoryOptions { @@ -545,7 +564,7 @@ private static RouteHandlerBuilder Map( } } endpointBuilder.RequestDelegate = filteredRequestDelegateResult.RequestDelegate; - }); + } return routeHandlerBuilder; } diff --git a/src/Http/Routing/src/DefaultInlineConstraintResolver.cs b/src/Http/Routing/src/DefaultInlineConstraintResolver.cs index f065ef9b1cfe..ad7ba5948ab3 100644 --- a/src/Http/Routing/src/DefaultInlineConstraintResolver.cs +++ b/src/Http/Routing/src/DefaultInlineConstraintResolver.cs @@ -34,7 +34,7 @@ public DefaultInlineConstraintResolver(IOptions routeOptions, ISer throw new ArgumentNullException(nameof(serviceProvider)); } - _inlineConstraintMap = routeOptions.Value.ConstraintMap; + _inlineConstraintMap = routeOptions.Value.TrimmerSafeConstraintMap; _serviceProvider = serviceProvider; } diff --git a/src/Http/Routing/src/DefaultParameterPolicyFactory.cs b/src/Http/Routing/src/DefaultParameterPolicyFactory.cs index f9c9636b35e0..965549816bbf 100644 --- a/src/Http/Routing/src/DefaultParameterPolicyFactory.cs +++ b/src/Http/Routing/src/DefaultParameterPolicyFactory.cs @@ -43,7 +43,7 @@ public override IParameterPolicy Create(RoutePatternParameterPart? parameter, st } var parameterPolicy = ParameterPolicyActivator.ResolveParameterPolicy( - _options.ConstraintMap, + _options.TrimmerSafeConstraintMap, _serviceProvider, inlineText, out var parameterPolicyKey); @@ -51,9 +51,9 @@ public override IParameterPolicy Create(RoutePatternParameterPart? parameter, st if (parameterPolicy == null) { throw new InvalidOperationException(Resources.FormatRoutePattern_ConstraintReferenceNotFound( - parameterPolicyKey, - typeof(RouteOptions), - nameof(RouteOptions.ConstraintMap))); + parameterPolicyKey, + typeof(RouteOptions), + nameof(RouteOptions.ConstraintMap))); } if (parameterPolicy is IRouteConstraint constraint) diff --git a/src/Http/Routing/src/EndpointRoutingMiddleware.cs b/src/Http/Routing/src/EndpointRoutingMiddleware.cs index dadfb4530130..ff2b382fdc85 100644 --- a/src/Http/Routing/src/EndpointRoutingMiddleware.cs +++ b/src/Http/Routing/src/EndpointRoutingMiddleware.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Matching; @@ -96,14 +97,21 @@ private Task SetRoutingAndContinue(HttpContext httpContext) // Raise an event if the route matched if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey)) { - // We're just going to send the HttpContext since it has all of the relevant information - _diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext); + Write(_diagnosticListener, httpContext); } Log.MatchSuccess(_logger, endpoint); } return _next(httpContext); + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", + Justification = "The values being passed into Write are being consumed by the application already.")] + static void Write(DiagnosticListener diagnosticListener, HttpContext httpContext) + { + // We're just going to send the HttpContext since it has all of the relevant information + diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext); + } } // Initialization is async to avoid blocking threads while reflection and things diff --git a/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs b/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs index f493a45752d2..3ede96df1e54 100644 --- a/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs +++ b/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs @@ -27,7 +27,7 @@ public static class LinkGeneratorEndpointNameAddressExtensions /// names from RouteOptions. /// /// A URI with an absolute path, or null. - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static string? GetPathByName( this LinkGenerator generator, HttpContext httpContext, @@ -66,9 +66,12 @@ public static class LinkGeneratorEndpointNameAddressExtensions /// Generates a URI with an absolute path based on the provided values. /// /// The . + /// The associated with the current request. /// The endpoint name. Used to resolve endpoints. /// The route values. Used to expand parameters in the route template. Optional. - /// An optional URI path base. Prepended to the path in the resulting URI. + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// /// An optional URI fragment. Appended to the resulting URI. /// /// An optional . Settings on provided object override the settings with matching @@ -76,6 +79,54 @@ public static class LinkGeneratorEndpointNameAddressExtensions /// /// A URI with an absolute path, or null. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? GetPathByName( + this LinkGenerator generator, + HttpContext httpContext, + string endpointName, + RouteValueDictionary? values = default, + PathString? pathBase = default, + FragmentString fragment = default, + LinkOptions? options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + if (endpointName == null) + { + throw new ArgumentNullException(nameof(endpointName)); + } + + return generator.GetPathByAddress( + httpContext, + endpointName, + values ?? new(), + ambientValues: null, + pathBase, + fragment, + options); + } + + /// + /// Generates a URI with an absolute path based on the provided values. + /// + /// The . + /// The endpoint name. Used to resolve endpoints. + /// The route values. Used to expand parameters in the route template. Optional. + /// An optional URI path base. Prepended to the path in the resulting URI. + /// An optional URI fragment. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null. + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static string? GetPathByName( this LinkGenerator generator, string endpointName, @@ -97,6 +148,41 @@ public static class LinkGeneratorEndpointNameAddressExtensions return generator.GetPathByAddress(endpointName, new RouteValueDictionary(values), pathBase, fragment, options); } + /// + /// Generates a URI with an absolute path based on the provided values. + /// + /// The . + /// The endpoint name. Used to resolve endpoints. + /// The route values. Used to expand parameters in the route template. Optional. + /// An optional URI path base. Prepended to the path in the resulting URI. + /// An optional URI fragment. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null. + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? GetPathByName( + this LinkGenerator generator, + string endpointName, + RouteValueDictionary? values = default, + PathString pathBase = default, + FragmentString fragment = default, + LinkOptions? options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (endpointName == null) + { + throw new ArgumentNullException(nameof(endpointName)); + } + + return generator.GetPathByAddress(endpointName, values ?? new(), pathBase, fragment, options); + } + /// /// Generates an absolute URI based on the provided values. /// @@ -128,7 +214,7 @@ public static class LinkGeneratorEndpointNameAddressExtensions /// your deployment environment. /// /// - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static string? GetUriByName( this LinkGenerator generator, HttpContext httpContext, @@ -167,6 +253,76 @@ public static class LinkGeneratorEndpointNameAddressExtensions options); } + /// + /// Generates an absolute URI based on the provided values. + /// + /// The . + /// The associated with the current request. + /// The endpoint name. Used to resolve endpoints. + /// The route values. Used to expand parameters in the route template. Optional. + /// + /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of will be used. + /// + /// + /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value will be used. + /// See the remarks section for details about the security implications of the . + /// + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// + /// An optional URI fragment. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? GetUriByName( + this LinkGenerator generator, + HttpContext httpContext, + string endpointName, + RouteValueDictionary? values = default, + string? scheme = default, + HostString? host = default, + PathString? pathBase = default, + FragmentString fragment = default, + LinkOptions? options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + if (endpointName == null) + { + throw new ArgumentNullException(nameof(endpointName)); + } + + return generator.GetUriByAddress( + httpContext, + endpointName, + values ?? new(), + ambientValues: null, + scheme, + host, + pathBase, + fragment, + options); + } + /// /// Generates an absolute URI based on the provided values. /// @@ -193,7 +349,7 @@ public static class LinkGeneratorEndpointNameAddressExtensions /// your deployment environment. /// /// - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static string? GetUriByName( this LinkGenerator generator, string endpointName, @@ -226,4 +382,64 @@ public static class LinkGeneratorEndpointNameAddressExtensions return generator.GetUriByAddress(endpointName, new RouteValueDictionary(values), scheme, host, pathBase, fragment, options); } + + /// + /// Generates an absolute URI based on the provided values. + /// + /// The . + /// The endpoint name. Used to resolve endpoints. + /// The route values. Used to expand parameters in the route template. + /// The URI scheme, applied to the resulting URI. + /// + /// The URI host/authority, applied to the resulting URI. + /// See the remarks section for details about the security implications of the . + /// + /// An optional URI path base. Prepended to the path in the resulting URI. + /// An optional URI fragment. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// An absolute URI, or null. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? GetUriByName( + this LinkGenerator generator, + string endpointName, + RouteValueDictionary values, + string scheme, + HostString host, + PathString pathBase = default, + FragmentString fragment = default, + LinkOptions? options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (endpointName == null) + { + throw new ArgumentNullException(nameof(endpointName)); + } + + if (string.IsNullOrEmpty(scheme)) + { + throw new ArgumentException("A scheme must be provided.", nameof(scheme)); + } + + if (!host.HasValue) + { + throw new ArgumentException("A host must be provided.", nameof(host)); + } + + return generator.GetUriByAddress(endpointName, values, scheme, host, pathBase, fragment, options); + } } diff --git a/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs b/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs index fe4be092481e..c83ab68d1e4e 100644 --- a/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs +++ b/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs @@ -27,7 +27,7 @@ public static class LinkGeneratorRouteValuesAddressExtensions /// names from RouteOptions. /// /// A URI with an absolute path, or null. - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static string? GetPathByRouteValues( this LinkGenerator generator, HttpContext httpContext, @@ -47,6 +47,53 @@ public static class LinkGeneratorRouteValuesAddressExtensions throw new ArgumentNullException(nameof(httpContext)); } + var address = CreateAddress(httpContext, routeName, new(values)); + return generator.GetPathByAddress( + httpContext, + address, + address.ExplicitValues, + address.AmbientValues, + pathBase, + fragment, + options); + } + + /// + /// Generates a URI with an absolute path based on the provided values. + /// + /// The . + /// The associated with the current request. + /// The route name. Used to resolve endpoints. Optional. + /// The route values. Used to resolve endpoints and expand parameters in the route template. Optional. + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// + /// An optional URI fragment. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null. + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? GetPathByRouteValues( + this LinkGenerator generator, + HttpContext httpContext, + string? routeName, + RouteValueDictionary? values = default, + PathString? pathBase = default, + FragmentString fragment = default, + LinkOptions? options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + var address = CreateAddress(httpContext, routeName, values); return generator.GetPathByAddress( httpContext, @@ -71,7 +118,7 @@ public static class LinkGeneratorRouteValuesAddressExtensions /// names from RouteOptions. /// /// A URI with an absolute path, or null. - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static string? GetPathByRouteValues( this LinkGenerator generator, string? routeName, @@ -85,6 +132,37 @@ public static class LinkGeneratorRouteValuesAddressExtensions throw new ArgumentNullException(nameof(generator)); } + var address = CreateAddress(httpContext: null, routeName, new(values)); + return generator.GetPathByAddress(address, address.ExplicitValues, pathBase, fragment, options); + } + + /// + /// Generates a URI with an absolute path based on the provided values. + /// + /// The . + /// The route name. Used to resolve endpoints. Optional. + /// The route values. Used to resolve endpoints and expand parameters in the route template. Optional. + /// An optional URI path base. Prepended to the path in the resulting URI. + /// An optional URI fragment. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null. + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? GetPathByRouteValues( + this LinkGenerator generator, + string? routeName, + RouteValueDictionary? values = default, + PathString pathBase = default, + FragmentString fragment = default, + LinkOptions? options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + var address = CreateAddress(httpContext: null, routeName, values); return generator.GetPathByAddress(address, address.ExplicitValues, pathBase, fragment, options); } @@ -120,7 +198,7 @@ public static class LinkGeneratorRouteValuesAddressExtensions /// your deployment environment. /// /// - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static string? GetUriByRouteValues( this LinkGenerator generator, HttpContext httpContext, @@ -142,6 +220,72 @@ public static class LinkGeneratorRouteValuesAddressExtensions throw new ArgumentNullException(nameof(httpContext)); } + var address = CreateAddress(httpContext, routeName, new(values)); + return generator.GetUriByAddress( + httpContext, + address, + address.ExplicitValues, + address.AmbientValues, + scheme, + host, + pathBase, + fragment, + options); + } + + /// + /// Generates an absolute URI based on the provided values. + /// + /// The . + /// The associated with the current request. + /// The route name. Used to resolve endpoints. Optional. + /// The route values. Used to resolve endpoints and expand parameters in the route template. Optional. + /// + /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of will be used. + /// + /// + /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value will be used. + /// See the remarks section for details about the security implications of the . + /// + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// + /// An optional URI fragment. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? GetUriByRouteValues( + this LinkGenerator generator, + HttpContext httpContext, + string? routeName, + RouteValueDictionary? values = default, + string? scheme = default, + HostString? host = default, + PathString? pathBase = default, + FragmentString fragment = default, + LinkOptions? options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + var address = CreateAddress(httpContext, routeName, values); return generator.GetUriByAddress( httpContext, @@ -181,7 +325,7 @@ public static class LinkGeneratorRouteValuesAddressExtensions /// your deployment environment. /// /// - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static string? GetUriByRouteValues( this LinkGenerator generator, string? routeName, @@ -197,16 +341,62 @@ public static class LinkGeneratorRouteValuesAddressExtensions throw new ArgumentNullException(nameof(generator)); } + var address = CreateAddress(httpContext: null, routeName, new(values)); + return generator.GetUriByAddress(address, address.ExplicitValues, scheme, host, pathBase, fragment, options); + } + + /// + /// Generates an absolute URI based on the provided values. + /// + /// The . + /// The route name. Used to resolve endpoints. Optional. + /// The route values. Used to resolve endpoints and expand parameters in the route template. + /// The URI scheme, applied to the resulting URI. + /// + /// The URI host/authority, applied to the resulting URI. + /// See the remarks section for details about the security implications of the . + /// + /// An optional URI path base. Prepended to the path in the resulting URI. + /// An optional URI fragment. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// An absolute URI, or null. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? GetUriByRouteValues( + this LinkGenerator generator, + string? routeName, + RouteValueDictionary values, + string scheme, + HostString host, + PathString pathBase = default, + FragmentString fragment = default, + LinkOptions? options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + var address = CreateAddress(httpContext: null, routeName, values); return generator.GetUriByAddress(address, address.ExplicitValues, scheme, host, pathBase, fragment, options); } - private static RouteValuesAddress CreateAddress(HttpContext? httpContext, string? routeName, object? values) + private static RouteValuesAddress CreateAddress(HttpContext? httpContext, string? routeName, RouteValueDictionary? values) { return new RouteValuesAddress() { AmbientValues = DefaultLinkGenerator.GetAmbientValues(httpContext), - ExplicitValues = new RouteValueDictionary(values), + ExplicitValues = values ?? new(), RouteName = routeName, }; } diff --git a/src/Http/Routing/src/MapRouteRouteBuilderExtensions.cs b/src/Http/Routing/src/MapRouteRouteBuilderExtensions.cs index e5c0b1892b8b..3e608cc6aa09 100644 --- a/src/Http/Routing/src/MapRouteRouteBuilderExtensions.cs +++ b/src/Http/Routing/src/MapRouteRouteBuilderExtensions.cs @@ -3,6 +3,7 @@ #nullable enable +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.Extensions.DependencyInjection; @@ -21,6 +22,7 @@ public static class MapRouteRouteBuilderExtensions /// The name of the route. /// The URL pattern of the route. /// A reference to this instance after the operation has completed. + [RequiresUnreferencedCode("This API may perform reflection on supplied parameter which may be trimmed if not referenced directly.")] public static IRouteBuilder MapRoute( this IRouteBuilder routeBuilder, string? name, @@ -41,6 +43,7 @@ public static IRouteBuilder MapRoute( /// and values of the default values. /// /// A reference to this instance after the operation has completed. + [RequiresUnreferencedCode("This API may perform reflection on supplied parameter which may be trimmed if not referenced directly.")] public static IRouteBuilder MapRoute( this IRouteBuilder routeBuilder, string? name, @@ -66,6 +69,7 @@ public static IRouteBuilder MapRoute( /// of the constraints. /// /// A reference to this instance after the operation has completed. + [RequiresUnreferencedCode("This API may perform reflection on supplied parameter which may be trimmed if not referenced directly.")] public static IRouteBuilder MapRoute( this IRouteBuilder routeBuilder, string? name, @@ -96,6 +100,7 @@ public static IRouteBuilder MapRoute( /// of the data tokens. /// /// A reference to this instance after the operation has completed. + [RequiresUnreferencedCode("This API may perform reflection on supplied parameter which may be trimmed if not referenced directly.")] public static IRouteBuilder MapRoute( this IRouteBuilder routeBuilder, string? name, diff --git a/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs b/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs index 325b8c216ad8..90c3bbd93f33 100644 --- a/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs +++ b/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs @@ -477,7 +477,7 @@ private class Labels public Label ReturnNotAscii { get; set; } } - private class Methods + private sealed class Methods { // Caching because the methods won't change, if we're being called once we're likely to // be called again. @@ -485,28 +485,30 @@ private class Methods private Methods() { - // Can't use GetMethod because the parameter is a generic method parameters. - Add = typeof(Unsafe) - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where(m => m.Name == nameof(Unsafe.Add)) - .Where(m => m.GetGenericArguments().Length == 1) - .Where(m => m.GetParameters().Length == 2) - .FirstOrDefault() + Add = typeof(Unsafe).GetMethod( + nameof(Unsafe.Add), + genericParameterCount: 1, + BindingFlags.Public | BindingFlags.Static, + binder: null, + types: new[] { Type.MakeGenericMethodParameter(0).MakeByRefType(), typeof(int), }, + modifiers: null) ?.MakeGenericMethod(typeof(byte)); - if (Add == null) + + if (Add is null) { throw new InvalidOperationException("Failed to find Unsafe.Add{T}(ref T, int)"); } - // Can't use GetMethod because the parameter is a generic method parameters. - As = typeof(Unsafe) - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where(m => m.Name == nameof(Unsafe.As)) - .Where(m => m.GetGenericArguments().Length == 2) - .Where(m => m.GetParameters().Length == 1) - .FirstOrDefault() - ?.MakeGenericMethod(typeof(char), typeof(byte)); - if (Add == null) + As = typeof(Unsafe).GetMethod( + nameof(Unsafe.As), + genericParameterCount: 2, + BindingFlags.Public | BindingFlags.Static, + binder: null, + types: new[] { Type.MakeGenericMethodParameter(0).MakeByRefType(), }, + modifiers: null) + ?.MakeGenericMethod(typeof(char), typeof(byte)); + + if (As is null) { throw new InvalidOperationException("Failed to find Unsafe.As{TFrom, TTo}(ref TFrom)"); } @@ -522,16 +524,15 @@ private Methods() throw new InvalidOperationException("Failed to find MemoryExtensions.AsSpan(string, int, int)"); } - // Can't use GetMethod because the parameter is a generic method parameters. - GetReference = typeof(MemoryMarshal) - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where(m => m.Name == nameof(MemoryMarshal.GetReference)) - .Where(m => m.GetGenericArguments().Length == 1) - .Where(m => m.GetParameters().Length == 1) - // Disambiguate between ReadOnlySpan<> and Span<> - this method is overloaded. - .Where(m => m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(ReadOnlySpan<>)) - .FirstOrDefault() + GetReference = typeof(MemoryMarshal).GetMethod( + nameof(MemoryMarshal.GetReference), + genericParameterCount: 1, + BindingFlags.Public | BindingFlags.Static, + binder: null, + types: new[] { typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), }, + modifiers: null) ?.MakeGenericMethod(typeof(char)); + if (GetReference == null) { throw new InvalidOperationException("Failed to find MemoryMarshal.GetReference{T}(ReadOnlySpan{T})"); diff --git a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj index c11ec20e5a8f..0e1f47d2ab67 100644 --- a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj +++ b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj @@ -13,7 +13,7 @@ true aspnetcore;routing false - enable + true diff --git a/src/Http/Routing/src/ParameterPolicyActivator.cs b/src/Http/Routing/src/ParameterPolicyActivator.cs index c843c34634b0..7ce467f9a059 100644 --- a/src/Http/Routing/src/ParameterPolicyActivator.cs +++ b/src/Http/Routing/src/ParameterPolicyActivator.cs @@ -86,6 +86,7 @@ public static T ResolveParameterPolicy( } [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2006:UnrecognizedReflectionPattern", Justification = "This type comes from the ConstraintMap.")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070", Justification = "We ensure the constructor is preserved when the constraint map is added.")] private static IParameterPolicy CreateParameterPolicy(IServiceProvider serviceProvider, Type parameterPolicyType, string argumentString) { ConstructorInfo activationConstructor = null; diff --git a/src/Http/Routing/src/Patterns/DefaultRoutePatternTransformer.cs b/src/Http/Routing/src/Patterns/DefaultRoutePatternTransformer.cs index de56b5e06c2c..84e425b55a96 100644 --- a/src/Http/Routing/src/Patterns/DefaultRoutePatternTransformer.cs +++ b/src/Http/Routing/src/Patterns/DefaultRoutePatternTransformer.cs @@ -3,6 +3,8 @@ #nullable disable +using System.Diagnostics.CodeAnalysis; + namespace Microsoft.AspNetCore.Routing.Patterns; internal class DefaultRoutePatternTransformer : RoutePatternTransformer @@ -19,6 +21,8 @@ public DefaultRoutePatternTransformer(ParameterPolicyFactory policyFactory) _policyFactory = policyFactory; } + [RequiresUnreferencedCode("This API may perform reflection on supplied parameter which may be trimmed if not referenced directly." + + "Consider using a different overload to avoid this issue.")] public override RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues) { if (original == null) @@ -26,11 +30,16 @@ public override RoutePattern SubstituteRequiredValues(RoutePattern original, obj throw new ArgumentNullException(nameof(original)); } - return SubstituteRequiredValuesCore(original, new RouteValueDictionary(requiredValues)); + return SubstituteRequiredValues(original, new RouteValueDictionary(requiredValues)); } - private RoutePattern SubstituteRequiredValuesCore(RoutePattern original, RouteValueDictionary requiredValues) + public override RoutePattern SubstituteRequiredValues(RoutePattern original, RouteValueDictionary requiredValues) { + if (original is null) + { + throw new ArgumentNullException(nameof(original)); + } + // Process each required value in sequence. Bail if we find any rejection criteria. The goal // of rejection is to avoid creating RoutePattern instances that can't *ever* match. // diff --git a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs index da7e1e8740fd..22123f60ac4f 100644 --- a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs +++ b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.AspNetCore.Routing.Constraints; @@ -53,6 +54,7 @@ public static RoutePattern Parse(string pattern) /// Multiple policies can be specified for a key by providing a collection as the value. /// /// The . + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static RoutePattern Parse(string pattern, object? defaults, object? parameterPolicies) { if (pattern == null) @@ -64,6 +66,34 @@ public static RoutePattern Parse(string pattern, object? defaults, object? param return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), requiredValues: null, original.PathSegments); } + /// + /// Creates a from its string representation along + /// with provided default values and parameter policies. + /// + /// The route pattern string to parse. + /// + /// Additional default values to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the parsed route pattern. + /// + /// + /// Additional parameter policies to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the parsed route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. + /// + /// The . + public static RoutePattern Parse(string pattern, RouteValueDictionary? defaults, RouteValueDictionary? parameterPolicies) + { + if (pattern == null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + var original = RoutePatternParser.Parse(pattern); + return PatternCore(original.RawText, defaults, parameterPolicies, requiredValues: null, original.PathSegments); + } + /// /// Creates a from its string representation along /// with provided default values and parameter policies. @@ -84,6 +114,7 @@ public static RoutePattern Parse(string pattern, object? defaults, object? param /// Route values that can be substituted for parameters in the route pattern. See remarks on . /// /// The . + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static RoutePattern Parse(string pattern, object? defaults, object? parameterPolicies, object? requiredValues) { if (pattern == null) @@ -95,6 +126,37 @@ public static RoutePattern Parse(string pattern, object? defaults, object? param return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), Wrap(requiredValues), original.PathSegments); } + /// + /// Creates a from its string representation along + /// with provided default values and parameter policies. + /// + /// The route pattern string to parse. + /// + /// Additional default values to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the parsed route pattern. + /// + /// + /// Additional parameter policies to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the parsed route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. + /// + /// + /// Route values that can be substituted for parameters in the route pattern. See remarks on . + /// + /// The . + public static RoutePattern Parse(string pattern, RouteValueDictionary? defaults, RouteValueDictionary? parameterPolicies, RouteValueDictionary? requiredValues) + { + if (pattern is null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + var original = RoutePatternParser.Parse(pattern); + return PatternCore(original.RawText, defaults, parameterPolicies, requiredValues, original.PathSegments); + } + /// /// Creates a new instance of from a collection of segments. /// @@ -143,6 +205,7 @@ public static RoutePattern Pattern(string? rawText, IEnumerable /// The collection of segments. /// The . + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static RoutePattern Pattern( object? defaults, object? parameterPolicies, @@ -156,6 +219,36 @@ public static RoutePattern Pattern( return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments); } + /// + /// Creates a from a collection of segments along + /// with provided default values and parameter policies. + /// + /// + /// Additional default values to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the route pattern. + /// + /// + /// Additional parameter policies to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. + /// + /// The collection of segments. + /// The . + public static RoutePattern Pattern( + RouteValueDictionary? defaults, + RouteValueDictionary? parameterPolicies, + IEnumerable segments) + { + if (segments is null) + { + throw new ArgumentNullException(nameof(segments)); + } + + return PatternCore(null, defaults, parameterPolicies, requiredValues: null, segments); + } + /// /// Creates a from a collection of segments along /// with provided default values and parameter policies. @@ -174,6 +267,7 @@ public static RoutePattern Pattern( /// /// The collection of segments. /// The . + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static RoutePattern Pattern( string? rawText, object? defaults, @@ -188,6 +282,38 @@ public static RoutePattern Pattern( return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments); } + /// + /// Creates a from a collection of segments along + /// with provided default values and parameter policies. + /// + /// The raw text to associate with the route pattern. May be null. + /// + /// Additional default values to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the route pattern. + /// + /// + /// Additional parameter policies to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. + /// + /// The collection of segments. + /// The . + public static RoutePattern Pattern( + string? rawText, + RouteValueDictionary? defaults, + RouteValueDictionary? parameterPolicies, + IEnumerable segments) + { + if (segments == null) + { + throw new ArgumentNullException(nameof(segments)); + } + + return PatternCore(rawText, defaults, parameterPolicies, requiredValues: null, segments); + } + /// /// Creates a new instance of from a collection of segments. /// @@ -236,6 +362,7 @@ public static RoutePattern Pattern(string rawText, params RoutePatternPathSegmen /// /// The collection of segments. /// The . + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static RoutePattern Pattern( object? defaults, object? parameterPolicies, @@ -249,6 +376,36 @@ public static RoutePattern Pattern( return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments); } + /// + /// Creates a from a collection of segments along + /// with provided default values and parameter policies. + /// + /// + /// Additional default values to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the route pattern. + /// + /// + /// Additional parameter policies to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. + /// + /// The collection of segments. + /// The . + public static RoutePattern Pattern( + RouteValueDictionary? defaults, + RouteValueDictionary? parameterPolicies, + params RoutePatternPathSegment[] segments) + { + if (segments == null) + { + throw new ArgumentNullException(nameof(segments)); + } + + return PatternCore(null, defaults, parameterPolicies, requiredValues: null, segments); + } + /// /// Creates a from a collection of segments along /// with provided default values and parameter policies. @@ -267,6 +424,7 @@ public static RoutePattern Pattern( /// /// The collection of segments. /// The . + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] public static RoutePattern Pattern( string? rawText, object? defaults, @@ -281,6 +439,38 @@ public static RoutePattern Pattern( return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments); } + /// + /// Creates a from a collection of segments along + /// with provided default values and parameter policies. + /// + /// The raw text to associate with the route pattern. + /// + /// Additional default values to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the route pattern. + /// + /// + /// Additional parameter policies to associated with the route pattern. May be null. + /// The provided object will be converted to key-value pairs using + /// and then merged into the route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. + /// + /// The collection of segments. + /// The . + public static RoutePattern Pattern( + string? rawText, + RouteValueDictionary? defaults, + RouteValueDictionary? parameterPolicies, + params RoutePatternPathSegment[] segments) + { + if (segments == null) + { + throw new ArgumentNullException(nameof(segments)); + } + + return PatternCore(rawText, defaults, parameterPolicies, requiredValues: null, segments); + } + private static RoutePattern PatternCore( string? rawText, RouteValueDictionary? defaults, @@ -904,8 +1094,9 @@ private static RoutePatternParameterPolicyReference ParameterPolicyCore(IParamet return new RoutePatternParameterPolicyReference(parameterPolicy); } + [RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)] private static RouteValueDictionary? Wrap(object? values) { - return values == null ? null : new RouteValueDictionary(values); + return values is null ? null : new RouteValueDictionary(values); } } diff --git a/src/Http/Routing/src/Patterns/RoutePatternTransformer.cs b/src/Http/Routing/src/Patterns/RoutePatternTransformer.cs index bd642fa6c0d3..943b19c36a3e 100644 --- a/src/Http/Routing/src/Patterns/RoutePatternTransformer.cs +++ b/src/Http/Routing/src/Patterns/RoutePatternTransformer.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; + namespace Microsoft.AspNetCore.Routing.Patterns; /// @@ -30,5 +32,32 @@ public abstract class RoutePatternTransformer /// return null if any required value cannot be substituted. /// /// + [RequiresUnreferencedCode("This API may perform reflection on supplied parameter which may be trimmed if not referenced directly." + + "Consider using a different overload to avoid this issue.")] public abstract RoutePattern? SubstituteRequiredValues(RoutePattern original, object requiredValues); + + /// + /// Attempts to substitute the provided into the provided + /// . + /// + /// The original . + /// The required values to substitute. + /// + /// A new if substitution succeeds, otherwise null. + /// + /// + /// + /// Substituting required values into a route pattern is intended for us with a general-purpose + /// parameterize route specification that can match many logical endpoints. Calling + /// can produce a derived route pattern + /// for each set of route values that corresponds to an endpoint. + /// + /// + /// The substitution process considers default values and implementations + /// when examining a required value. will + /// return null if any required value cannot be substituted. + /// + /// + public virtual RoutePattern? SubstituteRequiredValues(RoutePattern original, RouteValueDictionary requiredValues) + => throw new NotSupportedException("This API is not supported."); } diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt index 432fe7abd574..3d9ca9b5f48f 100644 --- a/src/Http/Routing/src/PublicAPI.Unshipped.txt +++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt @@ -13,3 +13,18 @@ static Microsoft.AspNetCore.Http.RouteHandlerFilterExtensions.AddFilter(this Mic static Microsoft.AspNetCore.Http.RouteHandlerFilterExtensions.AddFilter(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithDescription(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder, string! description) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithSummary(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder, string! summary) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! +static Microsoft.AspNetCore.Routing.LinkGeneratorEndpointNameAddressExtensions.GetPathByName(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, Microsoft.AspNetCore.Http.HttpContext! httpContext, string! endpointName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, Microsoft.AspNetCore.Http.PathString? pathBase = null, Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? +static Microsoft.AspNetCore.Routing.LinkGeneratorEndpointNameAddressExtensions.GetPathByName(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, string! endpointName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, Microsoft.AspNetCore.Http.PathString pathBase = default(Microsoft.AspNetCore.Http.PathString), Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? +static Microsoft.AspNetCore.Routing.LinkGeneratorEndpointNameAddressExtensions.GetUriByName(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, Microsoft.AspNetCore.Http.HttpContext! httpContext, string! endpointName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, string? scheme = null, Microsoft.AspNetCore.Http.HostString? host = null, Microsoft.AspNetCore.Http.PathString? pathBase = null, Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? +static Microsoft.AspNetCore.Routing.LinkGeneratorEndpointNameAddressExtensions.GetUriByName(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, string! endpointName, Microsoft.AspNetCore.Routing.RouteValueDictionary! values, string! scheme, Microsoft.AspNetCore.Http.HostString host, Microsoft.AspNetCore.Http.PathString pathBase = default(Microsoft.AspNetCore.Http.PathString), Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? +static Microsoft.AspNetCore.Routing.LinkGeneratorRouteValuesAddressExtensions.GetPathByRouteValues(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, Microsoft.AspNetCore.Http.HttpContext! httpContext, string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, Microsoft.AspNetCore.Http.PathString? pathBase = null, Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? +static Microsoft.AspNetCore.Routing.LinkGeneratorRouteValuesAddressExtensions.GetPathByRouteValues(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, Microsoft.AspNetCore.Http.PathString pathBase = default(Microsoft.AspNetCore.Http.PathString), Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? +static Microsoft.AspNetCore.Routing.LinkGeneratorRouteValuesAddressExtensions.GetUriByRouteValues(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, Microsoft.AspNetCore.Http.HttpContext! httpContext, string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, string? scheme = null, Microsoft.AspNetCore.Http.HostString? host = null, Microsoft.AspNetCore.Http.PathString? pathBase = null, Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? +static Microsoft.AspNetCore.Routing.LinkGeneratorRouteValuesAddressExtensions.GetUriByRouteValues(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, string? routeName, Microsoft.AspNetCore.Routing.RouteValueDictionary! values, string! scheme, Microsoft.AspNetCore.Http.HostString host, Microsoft.AspNetCore.Http.PathString pathBase = default(Microsoft.AspNetCore.Http.PathString), Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? +static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Parse(string! pattern, Microsoft.AspNetCore.Routing.RouteValueDictionary? defaults, Microsoft.AspNetCore.Routing.RouteValueDictionary? parameterPolicies) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern! +static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Parse(string! pattern, Microsoft.AspNetCore.Routing.RouteValueDictionary? defaults, Microsoft.AspNetCore.Routing.RouteValueDictionary? parameterPolicies, Microsoft.AspNetCore.Routing.RouteValueDictionary? requiredValues) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern! +static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Pattern(Microsoft.AspNetCore.Routing.RouteValueDictionary? defaults, Microsoft.AspNetCore.Routing.RouteValueDictionary? parameterPolicies, System.Collections.Generic.IEnumerable! segments) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern! +static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Pattern(Microsoft.AspNetCore.Routing.RouteValueDictionary? defaults, Microsoft.AspNetCore.Routing.RouteValueDictionary? parameterPolicies, params Microsoft.AspNetCore.Routing.Patterns.RoutePatternPathSegment![]! segments) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern! +static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Pattern(string? rawText, Microsoft.AspNetCore.Routing.RouteValueDictionary? defaults, Microsoft.AspNetCore.Routing.RouteValueDictionary? parameterPolicies, System.Collections.Generic.IEnumerable! segments) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern! +static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Pattern(string? rawText, Microsoft.AspNetCore.Routing.RouteValueDictionary? defaults, Microsoft.AspNetCore.Routing.RouteValueDictionary? parameterPolicies, params Microsoft.AspNetCore.Routing.Patterns.RoutePatternPathSegment![]! segments) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern! +virtual Microsoft.AspNetCore.Routing.Patterns.RoutePatternTransformer.SubstituteRequiredValues(Microsoft.AspNetCore.Routing.Patterns.RoutePattern! original, Microsoft.AspNetCore.Routing.RouteValueDictionary! requiredValues) -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern? diff --git a/src/Http/Routing/src/RequestDelegateRouteBuilderExtensions.cs b/src/Http/Routing/src/RequestDelegateRouteBuilderExtensions.cs index 69ebe30b1693..88c8f5807ab0 100644 --- a/src/Http/Routing/src/RequestDelegateRouteBuilderExtensions.cs +++ b/src/Http/Routing/src/RequestDelegateRouteBuilderExtensions.cs @@ -256,11 +256,16 @@ public static IRouteBuilder MapVerb( string template, RequestDelegate handler) { + var constraints = new RouteValueDictionary + { + ["httpMethod"] = new HttpMethodRouteConstraint(verb), + }; + var route = new Route( new RouteHandler(handler), template, defaults: null, - constraints: new RouteValueDictionary(new { httpMethod = new HttpMethodRouteConstraint(verb) })!, + constraints: constraints!, dataTokens: null, inlineConstraintResolver: GetConstraintResolver(builder)); diff --git a/src/Http/Routing/src/RouteOptions.cs b/src/Http/Routing/src/RouteOptions.cs index b54487f1c000..ce7474c3808c 100644 --- a/src/Http/Routing/src/RouteOptions.cs +++ b/src/Http/Routing/src/RouteOptions.cs @@ -86,6 +86,12 @@ public IDictionary ConstraintMap } } + /// + /// ensures that types are added to the constraint map in a trimmer safe way. + /// This API allows reading the map without encountering a trimmer warning within the framework. + /// + internal IDictionary TrimmerSafeConstraintMap => _constraintTypeMap; + private static IDictionary GetDefaultConstraintMap() { var defaults = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -130,7 +136,7 @@ private static IDictionary GetDefaultConstraintMap() /// The route token used to apply the parameter policy. public void SetParameterPolicy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string token) where T : IParameterPolicy { - ConstraintMap[token] = typeof(T); + _constraintTypeMap[token] = typeof(T); } /// @@ -146,7 +152,7 @@ public void SetParameterPolicy(string token, [DynamicallyAccessedMembers(Dynamic throw new InvalidOperationException($"{type} must implement {typeof(IParameterPolicy)}"); } - ConstraintMap[token] = type; + _constraintTypeMap[token] = type; } private static void AddConstraint<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TConstraint>(Dictionary constraintMap, string text) where TConstraint : IRouteConstraint diff --git a/src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs b/src/Http/Routing/src/RouteValueDictionaryTrimmerWarning.cs new file mode 100644 index 000000000000..020ad001f110 --- /dev/null +++ b/src/Http/Routing/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/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj b/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj index 721ffadc6613..691b85d69717 100644 --- a/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj +++ b/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj @@ -7,7 +7,6 @@ true true aspnetcore - enable true diff --git a/src/Tools/Tools.slnf b/src/Tools/Tools.slnf index 17832a576e33..b419c1d28c6e 100644 --- a/src/Tools/Tools.slnf +++ b/src/Tools/Tools.slnf @@ -23,6 +23,7 @@ "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", + "src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj", "src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj", "src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj", "src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj",