In this video, Nate will explain how to harness the power of ASP.NET Core MVC's built-in validation functionality to ensure that the search parameters provided by the client are correct.
- [Host] Any search parameters in the query string of incoming requests will be bound to the search array. In the validate method we can let ASP.NET Core know whether the parameters the client gave us are valid search terms. To keep this class clean, we'll delegate to a new class called "Search Options Processor." The Search Options Processor will also need to be bound to the entity type and the resource type and will pass in the search terms the client gave us. Now let's go create that Search Options Processor in the infrastructure folder.
Call this SearchOptionsProcessor T T Entity. And this class will have a private reference to the string array of search query terms, what you will accept in the constructor. The first method we need to write is a get all terms method. This will return an IEnumberable of search term which is a new class we need to create. So let's go create that now.
This will also go in the infrastructure folder. The search term class will represent a search term that's been deconstructed. So that means it will have a name, an operator, and a value. I'll also add a flag that represents whether the syntax was valid. Back in the search options processor we can implement the get all terms method. If the client didn't give us any search terms we can simply yield break and leave the method.
Otherwise we'll use a foreach to loop over all the expressions in the search query. If a particular expression is null or empty we can skip it. For each expression we'll split it up into a set of tokens using split and splitting on the space character. Each expression will look something like this. There will be a field, an operator and a value.
If there aren't any tokens left over after we split on the space character we can just return an invalid result. So we'll return a new search term object with valid syntax set to false and the name set to the entire expression for error reporting purposes. And then we need to make sure that we continue to skip to the next iteration of the loop. If there are some tokens but not enough of them in other words, less than three of them, we'll do the same.
In this case we'll set the name to the first token, which should be the field name. If we've gotten this far we know that we do have valid syntax, so return a new search term with valid syntax set to true and then the name and other properties deconstructed from the tokens. The operator will be the second token and the value will be all the remaining tokens rejoined with the space character, minus the first two. This method will return a list of all the search terms the client specified in the query stream.
However not all of those will necessarily be valid searchable fields on the model. We need to cross reference it with the fields that do have the searchable attribute. We can get a list of those with reflection. I'm going to collapse this method for now. This new method will be private static, it will also return an IEnumerable of search term, and it will be called GetTermsFromModel. In this case we're going to use reflection to look at the type of the resource, and we'll use GetTypeInfo from system.reflection to get the declared properties property.
We'll filter this where the property has a custom attribute, of type searchableattribute. And from those we'll select or project them to, each to a new search term with the name being the property name. Now we'll use this list of declared or approved terms from the model itself to create a list of valid terms for the current request. So we'll create one more IEnumerable of search term method, which is called GetValidTerms.
And in the GetValidTerms method the first thing we need to do is pull all of the terms from the query using GetAllTerms. But now we can filter that on making sure that each one has valid syntax, and we'll put all those in an array. If there aren't any with valid syntax we'll simply yield break and return an empty innumerable. If there's at least one, we need to get all the declared terms from the model, and then we'll iterate over each term from the query.
For each term in the query, I'll try to cross reference it with a declared term from the model. I'll use single or default where the name equals the term name in a case insensitive string comparison. If we didn't find a match, in other words declared term is null, we'll simply continue and skip this one. Otherwise we'll yield return, a new search term, where we can project all the original values from the term, with the exception of name, which we want to pull from the declared term.
All right let's switch back to search options. We'll need to import the infrastructure named space to get access to this new class we created. And using the search options processor we can get a list of valid terms, and then project those to select their names. We can also get a list of invalid terms by getting all the terms in the query, selecting those by name and then excluding the valid terms.
We'll make sure that the case comparison is ignoring case. And we want to do this so we can return to ASP.NET Core a list of the invalid terms for creating the error message. So we'll loop over each term in the invalid terms list, and for each one we'll yield return a new validation result, which is the return type of this method. And that validation result will carry an error message that'll be returned to the client. So we'll say "Invalid search term" and will reflect back the name of the term and we also need to pass the name of the parameter from the query strings so that's simply the nameof Search.
All right let's test out what we have so far using postman. We've annotated the room model with some searchable attributes, so let's try hitting/rooms and we'll add a search term but for an invalid attribute. So we'll say maybe fu equals bar. We should get back an error that says that "fu" is an "Invalid search term." Because there is no "fu" property on the room model with a searchable attribute. However we did mark the room name as being searchable. So let's try name equals oxford suite.
Now we get an error, but it's a different error. This error is because we haven't implemented the apply method yet. So that means that at least the search term validation code is running properly Next we'll implement that apply method and dynamically add the search criteria from the query string into the database query.
- REST vs. RPC
- Using HTTP methods (aka verbs)
- Returning JSON
- Creating a new API project
- Building a root controller
- Routing to controllers with templates
- Requiring HTTPS for security
- Creating resources and data models
- Returning data and resources from a controller
- Representing links (HREFs)
- Representing collections
- Sorting and searching collections
- Creating forms
- Caching and compression
- Authentication and authorization for RESTful APIs