Implementing the Store Locator Application Using ASP.NET MVC (Part 2)By Scott Mitchell
Last week's article, Implementing the Store Locator Application Using ASP.NET MVC (Part 1), started a two-part article series that walked through converting my ASP.NET store locator application from WebForms to ASP.NET MVC. Last week's article stepped through the first tasks in porting the store locator application to ASP.NET MVC, including: creating the new project; copying over stylesheets, the database, scripts, and other shared content from the WebForms application; building the
HomeController; and coding the
StoreLocatoractions and views.
Recall that the
StoreLocator action and view prompts the user to enter an address for which to find nearby stores. On form submission, the action interfaces
with the Google Maps API's geocoding service to determine if the entered address corresponds to known latitude and
longitude coordinates. If so, the user is redirected to the
StoreLocatorResults action (which we create in this article) that displays the nearby stores in
both a grid and as markers on a map. Unlike the
StoreLocator action created in Part 1, the
StoreLocatorResults action uses a more intricate
model and a strongly-typed view. Read on to learn more!
|Before We Get Started...|
This article assumes you have already read, downloaded, and used the original store locator application introduced in the
Building a Store Locator ASP.NET Application Using Google Maps API article series. It also presumes
that you have read and worked through Part 1 of this article series. If you have not
yet read these articles and downloaded and tested the code, please take time to do that before continuing on with this article.
Part 1 presented a series of steps to perform to convert the existing store locator application to ASP.NET MVC, and ended with "Step 4: Creating the
Step 5: Creating the Object Model Using LINQ To SQL
The store locator application includes a database with a single table (
Stores) that contains one record for each store location. In the WebForms application this database is queried from the
ShowStoreLocations.aspxpage, which is where the user is redirected after they've entered a valid address from which to find nearby stores. The
ShowStoreLocations.aspxpage uses a SqlDataSource control to retrieve those stores within (roughly) 15 miles of the address entered by the user and these nearby stores are both listed in a grid and plotted on a map.
One of the design goals of ASP.NET MVC is to offer a clean separation of concerns. The user interface - the view, in MVC parlance - should not include data access or business logic code. With ASP.NET WebForms is it all too easy to mesh together the user interface, data access, and business logic; using a SqlDataSource in the ASP.NET page is one such example of this mixing of concerns.
To help maintain separation of concerns, let's use LINQ to SQL, which is a simple, straightforward object-relational mapping tool from Microsoft that builds an object model that represents the data in a database. This article does not delve into LINQ to SQL in depth and assumes the reader has some familiarity with it. If this is not the case, check out Scott Guthrie's multi-part Using LINQ to SQL tutorials.
To create the LINQ to SQL classes, right-click on the
Models folder and choose to add a new item. Navigate to the Data templates and add a LINQ to SQL Classes
StoreLocations.dbml file has been added and opened, go to the Server Explorer window, expand the
StoreLocations.mdf database node's
Tables folder and then drag the
Stores table onto the LINQ to SQL designer. This creates a new object named
Store that contains properties
that map to the database table's columns as well as a
StoreLocationsDataContext class that we will use later to programmatically retrieve those stores
with 15 miles (or so) of the user-entered address.
Save your changes and close the
StoreLocations.dbml file. If you go to the Class View you should see a new namespace in your project (
with two new classes -
Store class represents the
Stores database table and
has properties that correspond to the table's columns (
and so forth). The
StoreLocatorDataContext class is used to get store information from the underlying database.
Step 6: A First Stab at the
StoreLocatorResults Action and View
When a user visits the
StoreLocatoraction (which we created in Part 1) and enters a valid address, she is whisked to the
StoreLocatorResultsaction, which we have yet to create. Let's do that now.
Add a method named
StoreLocatorResults that returns an
ActionResult object to the
HomeController class. Recall that when the user
enters a valid address they are redirected to this action with the address they entered passed through the querystring, like so:
StoreLocatorResults action method to have a string input parameter named
address; ASP.NET MVC will automatically assign
that input parameter the value of the
Address querystring field. At this point the method definition should look like the following:
StoreLocatorResults action is responsible for determining what stores are nearby the specified address. To accomplish this we need to start by
determining the latitude and longitude of the address, which we can do using the same technique as in Part 1, namely calling the
method and passing in the address. Once armed with the latitude and longitude coordinates we can determine the nearby stores by creating an instance of the
StoreLocatorDataContext and then returning all records from its
Stores collection whose latitude and longitude is less than 0.25 units
away from the latitude and longitude of the address entered by the user. (As discussed in the WebForms article, this will return stores within, roughly, a 15 mile
diameter of the user-entered address.)
The set of nearby stores needs to be available to the view so that these stores can be displayed in a grid and on a map.
ASP.NET MVC provides two mechanisms for building the model: through the loosely-typed
ViewData collection and through strongly-typed models. We already saw
how to use the loosely-typed
ViewData collection when we created the
StoreLocator action and view back in Part 1. To use a strongly-typed model
we need to do two things:
- Pass the model to the view using the syntax,
return View(model), and
- Specify the model's data type from the
Inheritsattribute in the view's
The next step is to create the
StoreLocatorResults's view. Right-click on the
StoreLocatorResults method name in the
and choose Add View. From the Add View dialog box, check the "Create a strongly-typed view" checkbox and choose the
Web.Models.Store type from the drop-down list.
Also, select the List option from the "View content" drop-down list. Finally, click the Add button to create the strongly-typed view.
Note the new
Inherits attribute, which lets the view know that its model
will be of a particular type (in this case, an enumerable collection of
Choosing the List template for the view causes Visual Studio to add markup that displays the contents of the model in a grid, with a column for each property.
(There are also Edit, Details, and Delete links that link to non-existent actions.) If you visit the application through a browser, navigate to the
action, and enter a valid address, you will be sent to the StoreLocatorResults action and shown a grid of the nearby stores. The following screen shot shows this grid when
entering the address "San Diego".
At this point you could customize the markup to remove the Edit, Details, and Delete links and to reformat the grid to make the output look more like it did in the
original article series. But rather than doing that, let's take a step back and question whether we want to use a collection of
Store objects as our model in the first place.
Store class contains much of the information we want to display in the results page - the address, for example - it is missing other bits of important
information, most notably the distance from the user-address entered. As discussed in the original article series, the distance between each store and the user-entered
address can be computed using the Pythagorean theorem. This computation could certainly be performed directly in the view; however, ASP.NET MVC views should be simple.
Ideally, the distance between each store location and the user-entered address would be part of the model itself and would not need to be determined by the view.
Rather than using a collection of
Store objects as our model, let's instead take the time to create a more finely-tuned model for this view, one that
will intuitively know the distance between the store and the user-entered address and not require any computation within the view.
Step 7: Creating the
Start by creating a new class in the
NearbyStoreLocation. This class will represent a store location nearby a particular address. In short, this class needs to offer the same functionality as the existing
Storeclass, but with the addition of the user-entered address's latitude and longitude coordinates and a property to determine the distance between the store and the user-entered address.
Here's my version of the
NearbyStoreLocation class, which I have extending the existing
Store class. Note the read-only
property, which uses the Pythagorean theorem to determine the distance between the store's latitude and longitude and the supplied address's latitude and longitude.
Additionally, a read-only
DistanceFromAddressDisplay property provides a formatted version of this distance for use in the view.
Step 8: Updating the
StoreLocatorResults Action and View
With this model in place, return to the
StoreLocatorResultsaction and replace the current
nearbyStoresquery with the following:
The highlighted portions indicate what has changed. Previously, the query returned each
Store object that was within a
particular distance from the user-entered address. The updated query above creates a new
NearbyStoreLocation object for each matching store. Note that
NearbyStoreLocation objects have their
AddressLongitude properties set to the latitude and
longitude of the user-entered address (
In addition to the modified query, I also have added an
OrderBy clause so that the stores are ordered from the nearest to the farthest away.
These results are stored in a new query,
nearbySortedStores, which is what is passed to the view as the model.
Next, modify the view's
Inherits attribute, replacing
Nuke the existing markup and replace it with the following, which shows the results in a grid similar in appearance to the one created in the WebForms application:
The following screen shot shows the updated view when searching for stores near "San Diego":
Step 9: Adding a the Map to the
~/Scripts/GoogleMapHelpers.jsfile, and passing it the latitude and longitude of the address entered along with arrays containing information about the markers to plot on the map.
GoogleMapHelpers.js file from the
portion of the web page. This can be done by using the Content control for the
Next, we need to define the map canvas, which is where the map will appear on the web page. Add this above the
<table> that lists the nearby locations.
infoWindowContents. As we loop through each nearby store we need to add that store's
information to the arrays, which the code below does.
The highlighted content shows what has been added. Through each iteration of the
foreach loop information about the current
location is added to the
infoWindowContents arrays. After the nearby stores have been enumerated, the
to the location and popup window information, the
init_map function is also passed the latitude and longitude of the user-entered address.
The screen shot below shows the current iteration of the store locator page when searching for stores near "San Diego."
Step 10: Tidying Up and Final Steps
At this point we have a functioning store locator application in ASP.NET MVC, although there are still a couple of rough edges. Rather than walking through each of these in fine detail, I'll instead just point them out and leave them as features that you can implement on your own. (The download available at the end of this article includes the fully completed application with all of these final touches.)
As things stand now, an error will occur if a user enters an address for which there are no nearby stores (such as "Chicago"). In the event that there are no nearby stores, the user should see a suitable message.
As the screen shot above shows, each store location on the map is displayed using the default marker, a red circle with a black dot. In the WebForms store locator application we looked at using the GeneratedImage control to dynamically create a custom marker with the number 1, 2, 3, and so on, and then showed the matching icon down in the grid. In this way, users could quickly see what stores in the grid corresponded to what stores on the map. Adding this functionality in ASP.NET MVC isn't difficult, but requires a bit of a discussion and the addition of a new routing rule. Rather than detail those changes here, please refer to my blog entry, Using the GeneratedImage Control in ASP.NET MVC. The download at the end of this article includes the configuration changes and the needed code.
I also added a "Directions" link to both the grid and the info windows that popup when you click a marker on the map. Clicking the "Directions" link opens a new browser window that loads Google Maps, showing the directions from the user-entered address to the selected store.