In this article, we’re going to tackle the topic of searching in ASP.NET Core Web API. Searching is one of those functionalities that can make or break your API, and the level of difficulty when implementing it can vary greatly depending on your specifications.
If you need to implement a basic searching feature where you are just trying to search one field in the database, you can easily implement it. On the other hand, if it’s a multi-column multi-term search you would probably be best of with some of the great search libraries out there like Lucene.NET which are already optimized and proven.
You can find the source code on the GitHub repo. If you want to follow along with the article, you can use the start branch and if you want to get the final solution or if you get stuck, switch to the final branch.
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.
Let’s dive right into it.
What is Searching?
There is no doubt in our minds that you’ve seen a search field on almost every website on the internet.
It’s easy to find something when we are familiar with the website structure or when a website is not that large. But, if we want to find the most relevant topic for us, or if we don’t know what we’re going to find, or maybe we’re first-time visitors of a large website, we’re probably going to use a search field.
Basic search is not that hard to implement, but if taken lightly, the search function can be useless. Sometimes it’s better to just revert back to Google, or use a google dork to search the website. If you are not familiar with Google dorks, we highly recommend you try them out.
In our simple project, one use case of a search would be to find an owner by his/her name.
Let’s see how we can achieve that.
How to Implement Searching in ASP.NET Core Web API
Since we’re going to implement the most basic search in our project, the implementation won’t be complex at all. We have all we need infrastructure-wise since we already covered paging and filtering. We’ll just extend our implementation a bit.
What we want to achieve is something like this:
https://localhost:5001/api/owners?name=Anna Bosh
This should return just one result: Anna Bosh. Of course, the search needs to work together with filtering and paging, so that’s one of the things that we need to keep in mind too.
Like we did with filtering, we’re going to extend our OwnerParameters
class first since we’re going to send our search query as a query parameter:
public class OwnerParameters : QueryStringParameters { public uint MinYearOfBirth { get; set; } public uint MaxYearOfBirth { get; set; } = (uint)DateTime.Now.Year; public bool ValidYearRange => MaxYearOfBirth > MinYearOfBirth; public string Name { get; set; } }
We’ve added just one new property – Name
.
Simple as that.
Now we can write queries with name="term"
in them.
The next thing we need to do is to actually implement the search functionality in our OwnerRepository
class:
public PagedList<Owner> GetOwners(OwnerParameters ownerParameters) { var owners = FindByCondition(o => o.DateOfBirth.Year >= ownerParameters.MinYearOfBirth && o.DateOfBirth.Year <= ownerParameters.MaxYearOfBirth); SearchByName(ref owners, ownerParameters.Name); return PagedList<Owner>.ToPagedList(owners.OrderBy(on => on.Name), ownerParameters.PageNumber, ownerParameters.PageSize); } private void SearchByName(ref IQueryable<Owner> owners, string ownerName) { if (!owners.Any() || string.IsNullOrWhiteSpace(ownerName)) return; owners = owners.Where(o => o.Name.ToLower().Contains(ownerName.Trim().ToLower())); }
First, we need to check if the name parameter is actually sent, by doing a simple IsNullOrWhiteSpace
check on the Name
property. If it’s not, there’s no point in searching for anything really.
After that, we are using the Where
clause with trimmed Name
string (just in case). We are doing this after the filtering is done to search through fewer results.
Everything else stays the same.
That’s it for our implementation. As you can see it really isn’t that hard since it is the most basic search, and we already had an infrastructure set.
Testing Our Implementation
The only thing that remains is to test our solution.
First, let’s recall how our database table looks.
Now, let’s try to find Anna:
https://localhost:5001/api/owners?name=Anna Bosh
Sure enough, we get our result.
If we entered her full name, Anna Bosh, the result would be the same. That’s because we are looking for the entire term by default with .Contains()
.
For an additional example, let’s try to find all the owners that contain the letter “o”:
https://localhost:5001/api/owner?name=o
Now, we should get three results instead of one:
[ { "id": "261e1685-cf26-494c-b17c-3546e65f5620", "name": "Anna Bosh", "dateOfBirth": "1974-11-14T00:00:00", "address": "27 Colored Row" }, { "id": "24fd81f8-d58a-4bcc-9f35-dc6cd5641906", "name": "John Keen", "dateOfBirth": "1980-12-05T00:00:00", "address": "61 Wellfield Road" }, { "id": "66774006-2371-4d5b-8518-2177bcf3f73e", "name": "Nick Somion", "dateOfBirth": "1998-12-15T00:00:00", "address": "North sunny address 102" } ]
Now let’s combine that with filtering and paging:
https://localhost:5001/api/owner?name=o&minYearOfBirth=1974&maxYearOfBith=1985&pageSize=1&pageNumber=2
Can you guess which result we should get (hint, it’s a single result)? If you’ve guessed, leave us a comment in the comments section.
That’s it, we’ve successfully implemented and tested our search functionality.
Let’s sum that up.
Conclusion
In this article we’ve covered:
- What searching is and how it’s different from filtering
- The way to implement searching in ASP.NET Core Web API
- How to test searching in our API
- Combining search with our existing solutions for paging and filtering
Hope this short article was useful to you. If you have any questions or suggestions, please leave us a comment and we’ll get to you as quickly as possible.
Next up, we’ll cover sorting.
You are the best. Thank you for the work you do. I will purchase your Ultimate ASP.NET Core Web API program once I finish this serie.
In the searchByName method definition inside the GetOwners Controller method i get an error of the ref owners the errors is as
Severity Code Description Project File Line Suppression State
Error CS1503 Argument 1: cannot convert from ‘ref System.Linq.IOrderedQueryable<AccountOwnerServer.Models.Owner>’ to ‘ref System.Linq.IQueryable<AccountOwnerServer.Models.Owner>’ AccountOwnerServer D:\Code Maze Asp Tutorial Project\AccountOwnerServer\AccountOwnerServer\Repository\OwnerRepository.cs 35 Active
any solution suggestion
Maybe this comment can help you: https://code-maze.com/searching-aspnet-core-webapi/#comment-2199. If not, just download our source code and compare it to yours.
Why
Why http://localhost:5000/api/owner?name=o&minYearOfBirth=1974&maxYearOfBith=1985&pageSize=1&pageNumber=2 returned 1 result and if i change pageSize on 2 it will return 0 results? There should be still 1 no?
That’s because your result contains 2 records. With a first URI, you are asking for one record per page and ask for the second page, so you will get only one result. But once you change page size on 2 and ask for the second page, you get 0 results because both records are on the first page.
Oh, yes. Thanks.
Hi, Excellent post. I am new to using Net Core, and I found what you posted very useful. One question: how can I define that a certain search parameter will have a default value? For example, that “name” by default is “Mary”?
Ηell᧐ there! I simply wish to gіѵe you a big thumbs up fоr thе
great information you have ցot here on this post.
I wіll be returning to yоur blog for more soon.
Hi, i get the following error cannot convert from ‘System.Collections.Generic.List‘ to ‘System.Linq.IQueryable‘ (CS1503), can you help me?
Hi Benny,
You’re right, the problem was in the code itself. You just need to remove the .ToList() method from the ToPagedList call in the GetOwners method.
So use this instead: owners.OrderBy(on => on.Name)
You can pull the newest source code too, since we’ve fixed the problem.
Hope this helps and let us know if you have any more problems.
Somewhere back when we used an ‘Include’ I changed IQueryable to IOrderedQueryable. Perhaps convinced it was needed. If anyone else did this to convert IQueryable to IOrderedQueryable you just have to add an OrderBy like this. owners = ownerTemp.OrderBy(o => o.Name);.
Hi Brian. I would just like to say thank you for all the comments on our articles and those suggestions. That’s very kind of you.