For anyone that has ever built an automotive dealer website, you would have come across the need to implement dropdown lists that link to each other, or as I call them, Cascading Dropdowns. When doing a search for a used car, the user typically wants to narrow down the search based on the make and model and specification of the car.
So, how would we implement this common scenario with today’s technology?
First of all create a new ASP.NET website and choose your preferred language, I’m using C#.
Next create a new SQL database. For this demo I’ve created a SQL Express 2005 DB and called it “VehicleDatabase”.
Next create three tables, Makes, Models and Derivatives. All ID’s are of type INT and the Name fields are of type VARCHAR.
Next we create the stored procedures to query the tables. Create the following three stored procedures:
These stored procedures simply just return the required fields, the only thing to note is on the GetModels and GetDerivatives SP’s, the parameters have a default value of -1. We also do an inline case statement to check for that default value and if matched, return all of the records. This allows us to populate the dropdowns with all available data before we perform the filter.
Add some test data to the tables.
Now that the database is sorted we need to create our entities. For this example, we don’t really need the entities as we are using stored procs that return a generic dictionaries but it gives you a flavour on how it can fit into you existing architecture. Add a new item to our website and choose the LINQ to SQL Classes.
Now drag you tables from the Server Explorer onto the left hand side of the designer.
And drag the stored procs onto the right hand side of the designer. Alternatively we could have dropped the stored procs onto their corresponding table. This would tell the designer that the return type of SP would be of that table/entity. So for example, if we were to drop the "GetMakes" SP onto the “Makes” table we would be telling the designer that the “GetMakes” SP returns a set of Make objects.
I have decided to return a Generic Dictionary<string, string> instead of the entities as I only want to return a key and value for each make, model or derivative.
As we are exposing this data via WCF we need to change the Serialization Mode from “None” to “Unidirectional”. This can be achieved through the properties of the LINQ to SQL Classes.
Right, now we have our data, we need to expose it through WCF webservices. Add a new item to our website and choose the WCF service item. I’ve called my service “VehicleService.svc”. Click yes when it asks you if you want to place it into the App_Code folder.
Once we have added the service, we need to alter the web.config. When adding a service to the project, Visual Studio changes the web.config and puts some default settings in for us. Copy and paste the XML below and replace the serviceModel section of the web.config. The main points here to notice is the endpoint binding is set to webHttpBinding rather than the SOAP default and the behaviour has httpGetEnabled = true.
The first service we’ll create is to populate the makes dropdown. With WCF we first need to create a contract for the service. Open up the interface IVehicleService and type or copy in the following code.
The things to notice about this code is the WebInvoke attribute. This is where all the work goes on. The first thing we set is the method, as we are wanting RESTful services, we will set this to GET. We could have also used the WebGet attribute here instead of the WebInvoke attribute. The next setting is the BodyStyle property and we set this to WebMessageBodyStyle.Bare. This removes the wrapper around the data. If we were exposes as POX (Plain Old XML) then it would remove the namespace declaration etc. but as we are using JSON, it removes a wrapper object.
As I mentioned earlier, the return type is of Dictionary<string, string> which allows us to expose just the data required.
Next we need to implement the interface we've just created. Open up the code behind for the service VehicleService.cs which is located in the App_Code folder. Create the following method.
The code above creates a generic dictionary and populates it with the results of the stroed proc. The thing to notice here is the use of Lambda expressions to specify the key and value fields for the toDictionary() extension method.
Now we can test the first service. Open a browser and navigate to the service endpoint.
My endpoint is http://localhost/VehicleService.svc/GetMakes
Now we have a working service, we can implement the other two services we require. Notice how we pass the parameters through to the services. The UriTemplate uses place holders that get replaced with the passed in params. The reason why we pass the MakeId and ModelId to the GetDerivatives is so that if only the Make dropdown is selected the derivatives dropdown is still filtered. Here is the interface implementation:
And the service implementation:
I we were to set our “GetModels” service method to return of type “Model” we would have sent over the “MakeId” with the data, but as we didn’t select it in our stored proc then they come through as zeros. This can be seen below. We test our services by navigating directly to them and passing in dummy parameters like so: http://localhost/CascadingDropdowns/VehicleService.svc/GetModels/1
Now we have all of our server side stuff done, we can move onto the client side jQuery.
We next add the reference to the jQuery library, I’ve linked to the google hosted version which gives a couple of benefits.
- Due to restrictions on most browsers of only two http requests at one time, the file will be download asynchronously due to it being hosted on another domain to the website.
- If the user has already visited a site that has linked to the same file then they will have it in their cache.
Go to http://code.google.com/apis/ajaxlibs/documentation/index.html#jquery to find out more.
Then download a jQuery plugin that makes it easier to deal with select boxes. http://www.texotela.co.uk/code/jquery/select/
Please note that I’ve opted for jQuery over ASP.NET AJAX due to it being lightweight and in my opinion more elegant.
Link to the scripts in our Default.aspx page ensuring to keep the correct order.
Next add the three dropdowns to the page. These can be normal html elements as we don't need any server side involvement.
We then need to create an init function that hooks up the events and binds once the page is loaded. I start off by creating another singleton object prefixed with the namespace of SteveTaylor. The first two lines in the init function binds an onchange event to the makes and models select boxes. The third line calls another function that populates the makes dropdown. As the makes and models dropdowns have an onchange event, once the makes are populated the models and derivatives automatically follow suit.
Next add the three functions that will populate the select boxes. Here is the full vehicle search object:
You will notice from the above code that the three functions are very similar and could have even been abstracted out a bit more. Lets analyse the getMakes function first.
The first line uses the removeOption functions of the plugin and passes in a regex to remove all items in the dropdown. This is necessary due the caching that the plugin implements. Then utilising jQuery’s chaining, we add a temporary option that shows the user that data is loading. We then call the service and pass in the URI, the method and a callback function. Note we pass in an anonymous function that receives the results and loops through the key value pairs and adds them to the dropdown. We then add the default “Any Make” option that has the -1 value to the list.
The other two functions are almost identical, the only difference being we add the values of the other dropdowns to the URI to pass them into the service.
The last thing we do is call the init function once the DOM is ready.
Download Solution - CascadingDropdowns.zip
Hope it all makes sense and let me know how you get on.