In the last lesson, we learned how to seed our database with data. In this lesson, we'll learn how to add search parameters to our get request so that we can request and retrieve filtered data.
Right now, when a GET request is sent to http://localhost:5000/api/animals
, all animals in the database are returned in JSON format. Our Get
method currently looks like this:
public async Task<ActionResult<IEnumerable<Animal>>> Get()
{
return await _db.Animals.ToListAsync();
}
What if we wanted the animals endpoint of our API to have the ability to return results that are filtered by certain search criteria? For example, say a user wanted to get all animals that are dinosaurs or all female animals? The API query in those cases would look something like this:
http://localhost:5000/api/animals?species=dinosaur
http://localhost:5000/api/animals?gender=female
We've seen this syntax for API calls before. As a reminder, the ?
here represents the beginning of a query string. What follows are key value pairs that represent the search parameter and its value. We'll need to change our logic a bit to handle this request.
In order for us to return a filtered set of results based on species, let's edit our Get
method in our controller:
...
// GET: api/Animals
[HttpGet]
public async Task<ActionResult<IEnumerable<Animal>>> Get(string species)
{
var query = _db.Animals.AsQueryable();
if (species != null)
{
query = query.Where(entry => entry.Species == species);
}
return await query.ToListAsync();
}
...
We've added a parameter to the method of type string
that we've called species
. The naming here is important as .NET will automatically bind parameter values based on the query string. A call to http://localhost:5000/api/animals?species=dinosaur
will now trigger our Get
method and automatically bind the value "dinosaur" to the variable species
. The framework does this by utilizing Model Binding which we used in our MVC apps to collect information from the route or from forms to use in our controllers.
In the body of the method we create a variable called query
and then collect the list of all animals from our database and return it as a queryable LINQ object. We return a queryable object so that we can use LINQ methods to build onto the query before finalizing our selection.
Then we do a check to see if there is a species parameter, and if there is, we build onto the query by calling the Where
method.
The Where
method accepts a function that will check whether each element passes the condition and does not get filtered out. In our case, we pass in entry => entry.Species == species
to specify that we only want an entry if its species value matches the query parameter from our route.
Finally, we call ToListAsync
on the final query to turn our new results into a list.
Let's test out our new species search functionality in Postman:
We can now retrieve entries from the database that are of a specific species, but what if we wanted to drill down further and find all the female dinosaurs? In order to do this, we can build on the query we created and add new parameters, like so:
...
// GET: api/Animals
[HttpGet]
public async Task<ActionResult<IEnumerable<Animal>>> Get(string species, string gender)
{
var query = _db.Animals.AsQueryable();
if (species != null)
{
query = query.Where(entry => entry.Species == species);
}
if (gender != null)
{
query = query.Where(entry => entry.Gender == gender);
}
return await query.ToListAsync();
}
...
Now, we can send a GET request to http://localhost:5000/api/animals?species=dinosaur&gender=female
and receive the following result:
[
{
"animalId": 2,
"name": "Rexie",
"species": "Dinosaur",
"age": 10,
"gender": "Female"
},
{
"animalId": 3,
"name": "Matilda",
"species": "Dinosaur",
"age": 2,
"gender": "Female"
}
]
&
symbol between each key-value pair.We've successfully filtered the results with multiple parameters, but we can add as many parameters as we want with this pattern. Further, this method allows for any combination of parameters to be used in the request. Let's also add a name parameter:
...
// GET api/animals
[HttpGet]
public Task<ActionResult<IEnumerable<Animal>>> Get(string species, string gender, string name)
{
var query = _db.Animals.AsQueryable();
if (species != null)
{
query = query.Where(entry => entry.Species == species);
}
if (gender != null)
{
query = query.Where(entry => entry.Gender == gender);
}
if (name != null)
{
query = query.Where(entry => entry.Name == name);
}
return await query.ToListAsync();
}
...
Now we are able to search for a female dinosaur named Matilda and our API will successfully return that specific entry:
Requesting http://localhost:5000/api/animals?species=dinosaur&gender=female&name=matilda
will yeild:
[
{
"animalId": 3,
"name": "Matilda",
"species": "Dinosaur",
"age": 2,
"gender": "Female"
}
]
We don't always have to filter content based on whether it matches the value in the search parameter directly. For example, If we wanted to get all dinosaurs that were older than 5 years old, it would be necessary for us to author this API endpoint to allow a request with a parameter that specifies a minimum age.
Because Model Binding in our web API works for any primitive, we can then add another parameter of int
type called minimumAge
and handle the logic in a similar fashion:
...
// GET api/animals
[HttpGet]
public async Task<List<Animal>> Get(string species, string gender, string name, int minimumAge)
{
IQueryable<Animal> query = _db.Animals.AsQueryable();
if (species != null)
{
query = query.Where(entry => entry.Species == species);
}
if (gender != null)
{
query = query.Where(entry => entry.Gender == gender);
}
if (name != null)
{
query = query.Where(entry => entry.Name == name);
}
if (minimumAge > 0)
{
query = query.Where(entry => entry.Age >= minimumAge);
}
return await query.ToListAsync();
}
...
Because intigers in C# are non-nullable data types the default for an intiger value parameter will be 0
when no minimumAge
parameter is entered. So we can check minimumAge > 0
in our if statment.
Now if we request http://localhost:5000/api/animals?minimumAge=5
in postman we should get:
[
{
"animalId": 1,
"name": "Matilda",
"species": "Woolly Mammoth",
"age": 7,
"gender": "Female"
},
{
"animalId": 2,
"name": "Rexie",
"species": "Dinosaur",
"age": 10,
"gender": "Female"
},
{
"animalId": 5,
"name": "Bartholomew",
"species": "Dinosaur",
"age": 22,
"gender": "Male"
}
]
Follow the link below to view how a sample version of the project should look at this point. Note that this is a link to a specific branch in the repository.
Lesson 8 of 22
Last updated April 6, 2022