Skip to content

Honor default value provided in parameters for route handlers #47266

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
amcasey opened this issue Mar 16, 2023 · 8 comments
Closed
1 task done

Honor default value provided in parameters for route handlers #47266

amcasey opened this issue Mar 16, 2023 · 8 comments
Assignees
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc bug This issue describes a behavior which is not expected - a bug. feature-rdg
Milestone

Comments

@amcasey
Copy link
Member

amcasey commented Mar 16, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

There are a number of ways to make it unnecessary to pass an argument corresponding to a given parameter. We should think about what we want to do in these scenarios with RDG.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/{purple:int}", HandleRequest);

app.Run();

static string HandleRequest(int purple, int orange = 1) => "Hello World!";
static string HandleRequest(int purple, [System.Runtime.InteropServices.OptionalAttribute] int orange) => "Hello World!";
static string HandleRequest(int purple, params int[] orange) => "Hello World!";

Describe the solution you'd like

I see that parameters can't already be flagged as optional:

// Endpoint Parameter: purple (Type = int, IsOptional = False, IsParsable = True, Source = RouteOrQuery)

Maybe we just need to recognize more forms of optionality?

Additional context

No response

@captainsafia captainsafia changed the title Think about how to handle optional parameters in RDG Think about how to handle optional params arguments in RDG Mar 16, 2023
@mitchdenny mitchdenny self-assigned this Mar 20, 2023
@mitchdenny
Copy link
Member

Thanks for bringing this up, it is something I hadn't considered. Just looking into the errors in the generated code now for these scenarios.

@mitchdenny
Copy link
Member

OK for the params scenario I don't think we need to worry. The generated code is going to look at the signature for the method and identify that it is an array, and an array is always going to have a value, even if it is empty. I just tested it and the code it emits works fine (I will add some additional tests for it though!).

@mitchdenny
Copy link
Member

In the case of this delegate signature:

static string HandleRequest(int purple, [System.Runtime.InteropServices.OptionalAttribute] int orange) => "Hello World!";

Omitting the orange variable fails with a 400 (predictably). So, RDF doesn't appear to do anything special with the [Optional] attribute.

The remaining case (int orange = 1) we don't handle at all - and the generator blows up (@captainsafia heads up that we'll need this captured in the known issues Gist). RDF works fine so its clearly some test cases we need to bring across ;)

@captainsafia
Copy link
Member

captainsafia commented Mar 20, 2023

OK for the params scenario I don't think we need to worry. The generated code is going to look at the signature for the method and identify that it is an array, and an array is always going to have a value, even if it is empty. I just tested it and the code it emits works fine (I will add some additional tests for it though!).

@amcasey had brought up that it might make sense for params to treat the array parameter as optional since that matches the behavior for methods in general (zero, one, or many values are supported). params in inline lambdas was only recently added to C# (I believe it is part of C#12) and while both RDF and RDG respect the array type of the parameter, they don't set a params argument as optional in anyway.

Omitting the orange variable fails with a 400 (predictably). So, RDF doesn't appear to do anything special with the [Optional] attribute.

Yep, don't think this is a scenario we'd support. If we did, it would be via the DataAnnotations attributes and the minimal validation support.

The remaining case (int orange = 1) we don't handle at all - and the generator blows up (@captainsafia heads up that we'll need this captured in the known issues Gist). RDF works fine so its clearly some test cases we need to bring across ;)

Yeah, it looks like this is a result of the fact that code generated by RDF is oblivious to nullability while RDG code is not. I think we should create a separate issue for this to link in the doc. I dug into this a little bit further and it looks like for the given code:

string HandleRequest(int purple, int orange = 1) => "Hello World!";
app.MapGet("/hello", HandleRequest);

We end up generating a parsing block that looks like this:

if (GeneratedRouteBuilderExtensionsCore.TryParseExplicit<int>(orange_temp!, CultureInfo.InvariantCulture, out var orange_temp_parsed_non_nullable))
{
    orange_parsed_temp = orange_temp_parsed_non_nullable;
}
else if (string.IsNullOrEmpty(orange_temp))
{
    orange_parsed_temp = null;
}
else
{
    wasParamCheckFailure = true;
}

With the problematic code in question being:

else if (string.IsNullOrEmpty(orange_temp))
{
    orange_parsed_temp = null;
}

orange_parsed_temp is emitted as a non-nullable int to match the signature of the route handler delegate. I believe the fix is to change this line of code to emit a nullable type for the argument:

writer.WriteLine($"""{parameter.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {outputArgument} = default;""");

using a strategy similar to what we do for the delegate cast:

var parameterTypeList = string.Join(", ", endpoint.Parameters.Select(
p => p.Type.ToDisplayString(p.IsOptional ? NullableFlowState.MaybeNull : NullableFlowState.NotNull, EmitterConstants.DisplayFormat)));

Edit: Correcting myself. We don't need to use an inline conditional like we do in the delegate cast code. We're already in the IsOptional case:

So we should always emit _parsed_temp as a nullable type.

@mitchdenny mitchdenny added this to the 8.0-preview4 milestone Mar 20, 2023
@mitchdenny mitchdenny added the bug This issue describes a behavior which is not expected - a bug. label Mar 20, 2023
@captainsafia captainsafia changed the title Think about how to handle optional params arguments in RDG Fix nullability generated in parsing TryParsable-types with default value Mar 20, 2023
@captainsafia captainsafia changed the title Fix nullability generated in parsing TryParsable-types with default value Honor default value provided in parameters for route handlers Mar 20, 2023
@captainsafia
Copy link
Member

I ended up addressing this in #47657 since I imported some tests from RDF that were failing because we didn't handle default values.

@mitchdenny
Copy link
Member

Thanks @captainsafia

@captainsafia
Copy link
Member

I'll keep this open until the PR is merged...

@captainsafia captainsafia reopened this Apr 13, 2023
@eerhardt
Copy link
Member

@captainsafia - can this be closed now? It is in the "Done" column for both projects, but this issue is still open.

@ghost ghost locked as resolved and limited conversation to collaborators May 26, 2023
@amcasey amcasey added area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed area-runtime labels Jun 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc bug This issue describes a behavior which is not expected - a bug. feature-rdg
Projects
None yet
Development

No branches or pull requests

6 participants
@mitchdenny @captainsafia @eerhardt @amcasey @mkArtakMSFT and others