Closed
Description
Background and Motivation
Results
and TypedResults
have methods that use reflection to create route values. These have all been annotated as unsafe.
public static partial class Results
{
[RequiresUnreferencedCode(RouteValueDictionaryTrimmerWarning.Warning)]
public static IResult CreatedAtRoute(string? routeName = null, object? routeValues = null, object? value = null);
}
We have previously worked around this problem by adding overloads that take RouteValueDictionary
. Values can be explicitly added to the dictionary, and the dictionary is then passed to routing. This is a trimming and AOT-safe alternative.
Proposed API
namespace Microsoft.AspNetCore.Http;
public static partial class Results
{
+ public static IResult RedirectToRoute(string? routeName, RouteValueDictionary? routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null);
+ public static IResult CreatedAtRoute(string? routeName, RouteValueDictionary routeValues, object? value = null);
+ public static IResult CreatedAtRoute<TValue>(string? routeName, RouteValueDictionary routeValues, TValue? value = default);
+ public static IResult AcceptedAtRoute(string? routeName, RouteValueDictionary routeValues, object? value = null);
+ public static IResult AcceptedAtRoute<TValue>(string? routeName, RouteValueDictionary routeValues, TValue? value = default);
}
public static partial class TypedResults
{
+ public static RedirectToRouteHttpResult RedirectToRoute(string? routeName, RouteValueDictionary? routeValues, bool permanent = false, bool preserveMethod = false, string? fragment = null);
+ public static CreatedAtRoute CreatedAtRoute(string? routeName, RouteValueDictionary routeValues);
+ public static CreatedAtRoute<TValue> CreatedAtRoute<TValue>(TValue? value, string? routeName, RouteValueDictionary routeValues);
+ public static AcceptedAtRoute AcceptedAtRoute(string? routeName, RouteValueDictionary routeValues);
+ public static AcceptedAtRoute<TValue> AcceptedAtRoute<TValue>(TValue? value, string? routeName, RouteValueDictionary routeValues);
}
Usage Examples
app.MapGet("/old-path", () => Results.CreatedAtRoute("RouteName", new RouteValueDictionary { ["routeValue1"] = "value!" }));
Alternative Designs
Generic type parameter for route values was explored in #46082. For example:
public static partial class Results
{
+ public static IResult CreatedAtRoute<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TRouteValues>(string? routeName, TRouteValues routeValues, object? value = null);
}
It doesn't require converting the route values object into a RouteValueDictionary
, but it has some problems:
- It doesn't work with polymorphism. Property values are gotten from the route value type based on its runtime type. If the route value instance is cast to
object
and then used withCreatedAtRoute<TRouteValues>
, its properties aren't preserved. - It could be a source-breaking change. There will now be a
CreatedAtRoute<TRouteValues>
overload and aCreatedAtRoute<TValue>
overload. Code that explicitly specifies the generic type when callingCreatedAtRoute
could cause an ambiguous method compiler error.
For example:
return Results.CreatedAtRoute<object>("RouteName", new { param1 = "param1Value" }, new User());
Risks
There are a lot of overloads, optional parameters and nullable types. It is possible to create method overloads that are ambiguous.
To avoid that:
RouteValueDictionary
args never have a default value. It must be specified.- Tests for methods must be extensive.