In this article, we will cover filtering in ASP.NET Core Web API. We’ll learn what filtering is, how it’s different from searching, and how to implement it in a real-world project.

While not critical as paging, filtering is still an important part of a flexible REST API, so we need to know how to implement it in our API projects. Filtering helps us get the exact result set we want instead of all the results without any criteria.

The starting point source code for this article can be found on the GitHub repo. For the finished project, switch to the filtering final branch.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

NOTE: Some degree of previous knowledge is needed to follow this article. It relies heavily on the ASP.NET Core Web API series on Code Maze, so if you are not sure how to set up the database or how the underlying architecture works, we strongly suggest you go through the series.

So let’s get right into it.

What is Filtering?

Filtering is a mechanism to retrieve results by providing some kind of criterium. We can write many kinds of filters to get the results by type of class property, value range, date range or anything else.

When implementing filtering, you are always restricted by the predefined set of options you can set in your request. For example, you can send a date value to request an owner account type, but you won’t have much success.

On the front end, filtering is usually implemented as checkboxes, radio buttons or dropdowns. This kind of implementation limits you to only those options that are available to create a valid filter.

Take for example a car-selling website. When filtering the cars you want, you would ideally want to select:

  • Car manufacturer as a category from a list or a dropdown
  • Car model from a list or a dropdown
  • Is it new or used with radio buttons?
  • The city where the seller is as a dropdown
  • The price of the car is an input field (numeric)
  • ….

You get the point. So the request would look something like this:
https://bestcarswebsite.com/sale?manufacturer=ford&model=expedition&state=used&city=washington&price_from=30000&price_to=50000

Or even like this:
https://bestcarswebsite.com/sale/filter?data[manufacturer]=ford&[model]=expedition&[state]=used&[city]=washington&[price_from]=30000&[price_to]=50000

Or anything else that makes sense to you the most. The API needs to parse the filter, so we don’t need to get too crazy with it :).

Now that we know what filtering is, let’s see how it’s different from searching.

How is Filtering Different from Searching

When searching for results you usually have only one input and that’s the one you use to search for anything within a website.

So in other words, you send a string to the API, and API is responsible for using that string to find any results that match it.

On our car website, we would use the search field to find the “Ford Expedition” car model, and we would get all the results that match the car name “Ford Expedition”. This would return every “Ford Expedition” car available.

We can also improve the search by implementing search terms like Google does it for example. If the user enters Ford Expedition without quotes in the search field, we would return both what’s relevant to Ford and Expedition. But, if the user puts the quotes around it, we would search the entire term “Ford Expedition” in our database.

It makes for better user experience.

Example:
https://bestcarswebsite.com/sale/search?name=ford focus

Using search doesn’t mean we can’t use filters together with it. It makes perfect sense to use the filtering and searching together, so we need to take that into account when writing our source code.

But enough of the theory.

Let’s implement some filters.

How to Implement Filtering in ASP.NET Core Web API

We have a DateOfBirth property in our Owner class. Let’ say we want to find out which owners are born between the year 1975 and 1997. We also want to be able to enter just the starting year, and not the ending one, and vice versa.

We would need a query like this one:
https://localhost:5001/api/owner?minYearOfBirth=1975&maxYearOfBirth=1997

But, we want to be able to do this too:
https://localhost:5001/api/owner?minYearOfBirth=1975

Or like this:
https://localhost:5001/api/owner?maxYearOfBirth=1997

Ok, we have a specification. Let’s see how to implement it.

We’ve already implemented paging in our controller so we have the necessary infrastructure to extend it with the filtering functionality. We’ve used the OwnerParameters class to define the query parameters for our paging request.

Let’s extend our OwnerParameters class to support filtering too:

public class OwnerParameters : QueryStringParameters
{
	public uint MinYearOfBirth { get; set; }
	public uint MaxYearOfBirth { get; set; } = (uint)DateTime.Now.Year;

	public bool ValidYearRange => MaxYearOfBirth > MinYearOfBirth;
}

We’ve added two unsigned int properties (to avoid negative year values), MinYearOfBirth and MaxYearOfBirth.

Since the default uint value is 0, we don’t need to explicitly define it, 0 is okay in this case. For the MaxYearOfBirthproperty, we want to set it to the current year. If we don’t get it through the query params, we have something to work with. It doesn’t matter if someone sets the year to 3053 through the params, it won’t affect the results.

We’ve also added a simple validation property – ValidYearRange. Its purpose is to tell us if the max year is indeed greater then min year. If it’s not, we want to let the API user know that he/she is doing something wrong.

Okay, now that we have our parameters ready, we can extend the controller:

[HttpGet]
public IActionResult GetOwners([FromQuery] OwnerParameters ownerParameters)
{
	if (!ownerParameters.ValidYearRange)
	{
		return BadRequest("Max year of birth cannot be less than min year of birth");
	}

	var owners = _repository.Owner.GetOwners(ownerParameters);

	var metadata = new
	{
		owners.TotalCount,
		owners.PageSize,
		owners.CurrentPage,
		owners.TotalPages,
		owners.HasNext,
		owners.HasPrevious
	};

	Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(metadata));

	_logger.LogInfo($"Returned {owners.TotalCount} owners from database.");

	return Ok(owners);
}

As you can see, there’s not much to it, we’ve added our validation check and a BadRequest response with a short message to the API user.

That should do it for the controller.

Let’s get to the implementation in our OwnerRepository class:

public PagedList<Owner> GetOwners(OwnerParameters ownerParameters)
{
	var owners = FindByCondition(o => o.DateOfBirth.Year >= ownerParameters.MinYearOfBirth &&
								o.DateOfBirth.Year <= ownerParameters.MaxYearOfBirth)
							.OrderBy(on => on.Name);

	return PagedList<Owner>.ToPagedList(owners,
		ownerParameters.PageNumber,
		ownerParameters.PageSize);
}

Actually, at this point, the implementation is rather simple too.

We are using FindByCondition method to find all the owners with the DateOfBirth between MaxYearOfBirth and MinYearOfBirth.

Pretty simple hah?

Let’s try it out.

Sending and Testing Some Requests

Like the specification states, we have 3 use cases to test.

For the reference, these are the owners in our database:

database owners

First, let’s test just the minYearOfBirth parameter.
https://localhost:5001/api/owner?minYearOfBirth=1975

In this case, we shouldn’t see Anna Bosh in our results.

The second test should be just the maxYearOf Birth.
https://localhost:5001/api/owner?maxYearOfBirth=1997

Now, Nick Somion should not appear amongst our owners.

And the final test is to include both minYearOfBirth and maxYearOfBirth.
https://localhost:5001/api/owner?minYearOfBirth=1975&maxYearOfBirth=1997

In this case, neither Anna nor Nick should be in the results.

We should also check if our validation works:
https://localhost:5001/api/owner?minYearOfBirth=1975&maxYearOfBirth=1974

We should get a bad request with the message that the range is invalid.

To top this off we can combine filtering with our current paging solution.
https://localhost:5001/api/owner?minYearOfBirth=1975&maxYearOfBirth=1997&pageSize=2&pageNumber=2

Can you guess what the result is (hint: it’s a single person)? If you’ve guessed, let us know in the comments.

That’s it, we’ve tested all the relevant cases.

Conclusion

We’ve covered another important concept when building RESTful APIs. Certainly not important as paging is, but filtering is often needed in APIs because we can restrict the results to only the ones we are interested in.

The solution is rather simple when you know what to do. It was also easy to implement since we had an infrastructure set up already in our paging article.

In this article we’ve learned:

  • What filtering is
  • How it’s different from searching
  • How to implement filtering in ASP.NET Core
  • Tested our implementation

Hopefully, you’ve learned something new and useful. In the next article, we’ll cover searching.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!