1

How to Access Query Strings in Minimal APIs

 2 years ago
source link: https://wildermuth.com/2022/04/04/query-strings-optional-arguments-minimal-apis-aspnetcore/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

How to Access Query Strings in Minimal APIs

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?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK