Skip to content
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

Parameter optionality in Minimal APIs #35

Open
1 of 3 tasks
LadyNaggaga opened this issue Aug 23, 2021 · 1 comment
Open
1 of 3 tasks

Parameter optionality in Minimal APIs #35

LadyNaggaga opened this issue Aug 23, 2021 · 1 comment
Labels
Getting Started Docs designed to give developer quick overviews on how to implement specific features

Comments

@LadyNaggaga
Copy link
Contributor

LadyNaggaga commented Aug 23, 2021

What would you like see:

  • Documentation
  • Sample
  • Tutorial
@LadyNaggaga LadyNaggaga added the Getting Started Docs designed to give developer quick overviews on how to implement specific features label Aug 23, 2021
@captainsafia
Copy link
Collaborator

Content pulled from https://blog.safia.rocks/minimal-apis-optionality.html

In .NET 6 RC1, we shipped support for a new feature in Minimal APIs that allows developers to set the optionality of request parameters by using nullable annotations and default parameters to indicate which values are required and which aren't. For example, let's say you had an endpoint that generated a random number based on a seed, like so:

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

app.MapGet("/random", (int seed, int max) => 
{
	var random = new Random(seed);
	return random.Next(0, max);
});

app.Run();

By default, in the scenario above, the seed parameter will be treated as required. That means if the user sends the following request to the endpoint:

$ http "http://localhost:5184/random"               
HTTP/1.1 400 Bad Request
Content-Length: 0
Date: Sun, 22 Aug 2021 21:52:51 GMT
Server: Kestrel

They'll be meet with a 400 Bad Request response. However, everything is all fine and dandy if both values are provided, though.

$ http "http://localhost:5184/random?seed=5&max=100"
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Aug 2021 22:06:56 GMT
Server: Kestrel
Transfer-Encoding: chunked

33

As it turns out, we can generate a Random object without providing a seed. By annotating seed as a nullable property, we can permit users to send requests to the endpoint without providing a seed property to the query.

app.MapGet("/random", (int? seed, int max) => 
{
	var random = seed.HasValue ? new Random(seed.Value) : new Random();
	return random.Next(0, max);
});

In this case, both requests below are valid.

$ http "http://localhost:5184/random?max=100" 
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Aug 2021 22:08:16 GMT
Server: Kestrel
Transfer-Encoding: chunked

17

$ http "http://localhost:5184/random?seed=5&max=100"
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Aug 2021 22:08:35 GMT
Server: Kestrel
Transfer-Encoding: chunked

33

However, since the max attribute is still required, omitting that from the request will result in the 400 Bad Request error.

$ http "http://localhost:5184/random?seed=5"
HTTP/1.1 400 Bad Request
Content-Length: 0
Date: Sun, 22 Aug 2021 22:09:22 GMT
Server: Kestrel

In addition to nullable annotations, we can indicate that a parameter is optional by specifying a default value for the parameter.

int GetRandom(int? seed, int max = 5)
{
    var random = seed.HasValue ? new Random(seed.Value) : new Random();
    return random.Next(0, max);
}
app.MapGet("/random", GetRandom);

⚠️ Note: In the code above, the endpoint logic has been moved to a separate function since default parameters in inline lambdas are not currently supported in C#.

The change above permits the user to provide the seed and max parameters as optional within requests. All the following requests will be processed by the endpoint.

$ http "http://localhost:5184/random?seed=5&max=100"
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Aug 2021 22:12:58 GMT
Server: Kestrel
Transfer-Encoding: chunked

33

$ http "http://localhost:5184/random?seed=5"        
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Aug 2021 22:15:11 GMT
Server: Kestrel
Transfer-Encoding: chunked

1

$ http "http://localhost:5184/random"       
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Aug 2021 22:15:24 GMT
Server: Kestrel
Transfer-Encoding: chunked

4

$ http "http://localhost:5184/random?max=100" 
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Aug 2021 22:15:51 GMT
Server: Kestrel
Transfer-Encoding: chunked

95

In the above scenario, we were able to mark a query parameter as optional but the same principles apply to parameters in the body as well as services injected to the endpoint. So for example, let's say that we took the configuration for our random number generator as a set of arguments provided in the body of the request.

app.MapPost("/random", (ConfigOptions? options) =>
{
    var random = options is not null ? new Random(options.Seed) : new Random();
    return random.Next(0, options?.Max ?? 100);
});

app.Run();

class ConfigOptions
{
    public int Seed { get; }
    public int Max { get; }
}

The above will appropriately handle requests where the the body parameters are provided and those where it isn't.

One thing to note is that the behavior is different for service parameter. For one, instead of returning a 400 status code, the endpoint will return a 500 status code depending on whether or not the service was provided. Also, due to some nuances in the parameter parsing logic, optionality is only support for services that are injected into the endpoint via an explicit reference (using the FromService attribute) and not those that are implicit referenced.

🗒️ The nuance here is that there is some subtlety around discerning whether the SomeType in app.MapPost("/foo", (SomeType st) => ...) is referring to a service SomeType or a body param that deserializes to SomeType.

Finally, depending on the nullability context the parameter exists in the, the behavior of this feature will differ slightly.

  • Unannotated value types are always required regardless of nullability context.
  • Unannotated reference types are always optional if they exist in an unknown nullability context.
  • Regardless of nullability context, you can use default values to indicate that a reference or value type parameter is optional.

One final note, by default, no message will be sent in the response when a parameter fails a requiredness check. For more information on this, including current solutions, check out this GitHub issue.

@LadyNaggaga LadyNaggaga added this to Todo in Quick start content RC1 via automation Aug 23, 2021
@LadyNaggaga LadyNaggaga moved this from Todo to Needs review in Quick start content RC1 Aug 23, 2021
Quick start content RC1 automation moved this from Needs review to Done Sep 7, 2021
@LadyNaggaga LadyNaggaga reopened this Sep 7, 2021
Quick start content RC1 automation moved this from Done to Inprogress Sep 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Getting Started Docs designed to give developer quick overviews on how to implement specific features
Projects
Development

No branches or pull requests

2 participants