Thanks for visiting my blog!
I love that this job allows me to learn new stuff every day. In this case, I was building a simple API to use for some upcoming Pluralsight courses. I wanted to use Minimal APIs to expose some data for an old dataset from FiveThirtyEight on Bechdel Tests for Films. While I was adding paging, I got confused.
So, I started with a minimal API for getting all the films like so:
app.MapGet("api/films", async (BechdelDataService ds) =>
{
if (ds is null) return Results.BadRequest();
FilmResult data = await ds.LoadAllFilmsAsync();
if (data.Results is null) return Results.NotFound();
return Results.Ok(data);
}).WithTags("By Film").Produces(200).ProducesProblem(404);
Pretty simple Minimal API to get some data (my BechdelDataService is just a repository-like class). When I added paging, I wanted to use the typical Web API trick of using optional parameters:
app.MapGet("api/films", async (BechdelDataService ds,
int page = 1,
int pageSize = 50) =>
{
...
}).WithTags("By Film").Produces(200).ProducesProblem(404);
To my surprise this didn’t work. What I expected was that if I used a query string, it would bind to the extra parameters by name:
/api/films?page=1
/api/films
In this case, these should be the same. What is going on. At first I dove into seeing why Minimal APIs didn’t support this. I didn’t see much about it, I saw options to read the query strings manually:
app.MapGet("api/films", async (
HttpRequest request,
BechdelDataService ds) =>
{
var page = request.Query["page"] ?? 1;
var size = request.Query["pageSize"] ?? 50;
...
}).WithTags("By Film").Produces(200).ProducesProblem(404);
This works, but I found it clunky. I wanted binding to work. I saw some examples that broke out the lambda to a method:
app.MapGet("api/films", GetAllFilms);
async Task<IResult> GetAllFilms(BechdelDataService ds,
int page = 1,
int pageSize = 50)
{
...
}
This works, so the problem wasn’t with Minimal APIs, the problem is that Lambdas do not support default values (since you’re passing in a lambda, the spec assumes that you’ll supply all values).
So this is a fix? I don’t like the messiness of breaking it out into separate methods. So how do we fix it? Let’s use nullable values:
app.MapGet("api/films", async (BechdelDataService ds,
int? page,
int? pageSize) =>
{
if (ds is null) return Results.BadRequest();
int pageNumber = page ?? 1;
int pagerTake = pageSize ?? 50;
FilmResult data = await ds.LoadAllFilmsAsync(pageNumber, pagerTake);
if (data.Results is null) return Results.NotFound();
return Results.Ok(data);
}).WithTags("By Film").Produces(200).ProducesProblem(404);
By supplying the values as nullable types (int?), we have to do our own defaulting, but I think it’s acceptable. One note here, is that I’m defining the types to non-nullable types to make my calls to the data service easier, but this isn’t necessary.
So, was I the only one who didn’t know that default values weren’t supported in lambda’s?