Skip to content

Consider trimmer friendly alternatives in Http.Routing that do not initialize a RouteValueDictionary #40975

@pranavkm

Description

@pranavkm

Background and Motivation

This is a follow up to #40438. Now that RouteValueDictionary's existing ctor is annotated as being trimmer unfriendly, it would make sense to provide alternatives in the framework where previously the framework
invoked the constructor on the user's behalf. For instance, in Http.Routing there are several APIs that do this. This proposal is to provide alternative overloads that accept an instance of RouteValueDictionary to avoid trimmer warnings.

Proposed API

namespace Microsoft.AspNetCore.Routing;

public static class LinkGeneratorEndpointNameAddressExtensions
{
    public static string? GetPathByName(
        this LinkGenerator generator,
        HttpContext httpContext,
        string endpointName,
        object? values,
        PathString? pathBase = default,
        FragmentString fragment = default,
        LinkOptions? options = default);

+    public static string? GetPathByName(
+        this LinkGenerator generator,
+        HttpContext httpContext,
+        string endpointName,
+        RouteValueDictionary? values = default,
+        PathString? pathBase = default,
+        FragmentString fragment = default,
+        LinkOptions? options = default);

    public static string? GetPathByName(
        this LinkGenerator generator,
        string endpointName,
        object? values,
        PathString pathBase = default,
        FragmentString fragment = default,
        LinkOptions? options = default);

+    public static string? GetPathByName(
+        this LinkGenerator generator,
+        string endpointName,
+        RouteValueDictionary? values = default,
+        PathString pathBase = default,
+        FragmentString fragment = default,
+        LinkOptions? options = default);

    public static string? GetUriByName(
        this LinkGenerator generator,
        HttpContext httpContext,
        string endpointName,
        object? values,
        string? scheme = default,
        HostString? host = default,
        PathString? pathBase = default,
        FragmentString fragment = default,
        LinkOptions? options = default);

+    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);

    public static string? GetUriByName(
        this LinkGenerator generator,
        string endpointName,
        object? values,
        string scheme,
        HostString host,
        PathString pathBase = default,
        FragmentString fragment = default,
        LinkOptions? options = default);

+    public static string? GetUriByName(
+        this LinkGenerator generator,
+        string endpointName,
+        RouteValueDictionary values, // Note that this is not nullable compared to the other API
+        string scheme,
+        HostString host,
+        PathString pathBase = default,
+        FragmentString fragment = default,
+        LinkOptions? options = default);
}

public static class LinkGeneratorEndpointNameAddressExtensions
{
    public static string? GetPathByName(
        this LinkGenerator generator,
        HttpContext httpContext,
        string endpointName,
        object? values,
        PathString? pathBase = default,
        FragmentString fragment = default,
        LinkOptions? options = default);
        
+    public static string? GetPathByName(
+        this LinkGenerator generator,
+        HttpContext httpContext,
+        string endpointName,
+        RouteValueDictionary? values = default,
+        PathString? pathBase = default,
+        FragmentString fragment = default,
+        LinkOptions? options = default);

    public static string? GetPathByName(
        this LinkGenerator generator,
        string endpointName,
        object? values,
        PathString pathBase = default,
        FragmentString fragment = default,
        LinkOptions? options = default);

+    public static string? GetPathByName(
+        this LinkGenerator generator,
+        string endpointName,
+        RouteValueDictionary? values = default,
+        PathString pathBase = default,
+        FragmentString fragment = default,
+        LinkOptions? options = default);

    public static string? GetUriByName(
        this LinkGenerator generator,
        HttpContext httpContext,
        string endpointName,
        object? values,
        string? scheme = default,
        HostString? host = default,
        PathString? pathBase = default,
        FragmentString fragment = default,
        LinkOptions? options = default);
    
+    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);

    public static string? GetUriByName(
        this LinkGenerator generator,
        string endpointName,
        object? values,
        string scheme,
        HostString host,
        PathString pathBase = default,
        FragmentString fragment = default,
        LinkOptions? options = default);
    
+    public static string? GetUriByName(
+        this LinkGenerator generator,
+        string endpointName,
+        RouteValueDictionary values, // Note that this is not-nullable.
+        string scheme,
+        HostString host,
+        PathString pathBase = default,
+        FragmentString fragment = default,
+        LinkOptions? options = default);
}

namespace Microsoft.AspNetCore.Routing.Patterns;

public abstract class RoutePatternTransformer
{
    public abstract RoutePattern? SubstituteRequiredValues(RoutePattern original, object requiredValues);
+    public virtual RoutePattern? SubstituteRequiredValues(RoutePattern original, RouteValueDictionary requiredValues);
}
    
public static class RoutePatternFactory
{
    public static RoutePattern Parse(string pattern, object? defaults, object? parameterPolicies);
+    public static RoutePattern Parse(string pattern, RouteValueDictionary? defaults, RouteValueDictionary? parameterPolicies);
    public static RoutePattern Parse(string pattern, object? defaults, object? parameterPolicies, object? requiredValues);
+    public static RoutePattern Parse(string pattern, RouteValueDictionary? defaults, RouteValueDictionary? parameterPolicies, RouteValueDictionary? requiredValues);
    public static RoutePattern Pattern(object? defaults, object? parameterPolicies, IEnumerable<RoutePatternPathSegment> segments);
+    public static RoutePattern Pattern(RouteValueDictionary? defaults, RouteValueDictionary? parameterPolicies, IEnumerable<RoutePatternPathSegment> segments);
    public static RoutePattern Pattern(string? rawText, object? defaults, object? parameterPolicies, IEnumerable<RoutePatternPathSegment> segments);
+    public static RoutePattern Pattern(string? rawText, RouteValueDictionary? defaults, RouteValueDictionary? parameterPolicies, IEnumerable<RoutePatternPathSegment> segments);
    public static RoutePattern Pattern(object? defaults, object? parameterPolicies, params RoutePatternPathSegment[] segments);
+    public static RoutePattern Pattern(RouteValueDictionary? defaults, RouteValueDictionary? parameterPolicies, params RoutePatternPathSegment[] segments);
    public static RoutePattern Pattern(string? rawText, object? defaults, object? parameterPolicies, params RoutePatternPathSegment[] segments);
+    public static RoutePattern Pattern(string? rawText, RouteValueDictionary? defaults, RouteValueDictionary? parameterPolicies, params RoutePatternPathSegment[] segments);
}

Usage Examples

// Before
var myEndpoint = LinkGenerator.GetPathByName(context, "my-cool-endpoint", new { controller = "MyController" });

// After
var myEndpoint = LinkGenerator.GetPathByName(context, "my-cool-endpoint", new RouteValueDictionary { ["controller"] = "MyController" });

etc

-->

Alternative Designs

Don't offer trimmer safe overloads. Users can still operate in a trimmer-safe way by passing an instance of RouteValueDictionary to existing overloads. This would prevent trimmer problems (such as getters on types getting trimmed), but continue to present trimmer warnings when publishing.

Risks

n/a

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractionsfeature-routing

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions