Learn how to extend the query logic to support types beyond strings.
- [Instructor] The search expressions that come over the wire in the request query string are strings. In order to search against other property types like numbers, we'll need to cast the string values to the correct destination type. Since we'll need to potentially handle a number of different types let's create a separate set of classes that can handle converting the search value into a link expression. I'm going to create an interface in the infrastructure folder called I search expression provider. The search expression provider interface will have one method which returns a constant expression which we'll need to import from the expression namespace.
And we'll call this GetValue for a particular string input. And we'll create a default implementation called default search expression provider. We'll implement this method. This default implementation will do exactly what our existing apply method does right now which is put that string into the constant expression and return it. So we'll return expression dot constant and the input. I'm going to make this method virtual so we can derive from this class and override the method.
Next I'm going to open up the searchable attribute and add a property. This new property will be of type I search expression provider and we'll call it expression provider. The default will be new default search expression provider. This way each instance of the searchable attribute can declare its own expression provider. This should give us the flexibility to handle many different scenarios. Let's take a look at the room resource. The room resource has a property called rate which is searchable and it's a decimal on the room model.
However, it's stored in the database as an int to keep the database as simple as possible. We'll need to write an expression provider that will parse the string as a decimal and then convert it to an int. I'll create a new class in infrastructure called decimal to int search expression provider. And I'll inherit from default search expression provider. I want to override GetValue and provide my own implementation. Let's do a sanity check first. We can use TryParse to see if this is a valid decimal.
And then use out var to save this to a variable. If this fails we'll simply throw a new argument exception saying invalid search value. This will get serialized and sent back to the client. Now we need to do a little binary math to remove the decimal point from the decimal without changing the value of the number. To do this we'll say var places equals bit converter dot get bytes decimal dot get bits on our decimal instance and then get the third and then the second value from the array. This will get the number of decimal places in the decimal.
If that's less than two we'll set it to two and then we'll get just the digits from the decimal. We'll cast it to an int and say decimal times math dot pow ten and places. This will raise the number to the number of decimal places we need to get rid of the decimal. And then at the end of all this we can simply return expression dot constant with just the digits and that's going to be an int. Next let's create a new attribute that extends searchable.
I'm going to call this attribute searchable decimal attribute. This will extend from searchable. You want to set the attribute usage to property targets and disallow the use of multiple attributes. And the only thing different about the searchable decimal attribute is that we'll set the expression provider to be an instance of new decimal to int search expression provider. Now let's jump back to the room model and change the attribute here to searchable decimal.
The final change we need to make is to update the search options processor. In the get terms from model method we need to make sure we pass forward the search expression provider. So we'll say expression provider equals the property dot get custom attribute of type searchable attribute dot expression provider. Now, there's no expression provider property on search term so let's jump into search term and add that.
This will be an I search expression provider called expression provider. We also need to update the get valid terms method, to forward this as well. And finally in the apply method, when we get the right side comparison expression now we'll call term dot expression provider dot get value on term dot value. Alright let's test it out.
Previously I tried to send a request searching on the rate field and that failed because we couldn't search on an int. Now let's try it again. And this time it worked fine. We're able to search on a field that is not a string and this can be extended to any other types of fields you might need to search on. So far though we've only handled the equals operator. Before we move on from search, let's add a few more operators.
- 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