Implementing the Store Locator Application Using ASP.NET MVC (Part 1)
By Scott Mitchell
Introduction
Back in May 2010 I wrote a three-part article series titled Building a Store Locator ASP.NET Application Using Google Maps API, which showed how to build a simple store locator application using ASP.NET and the Google Maps API. The application consisted of two ASP.NET pages. In the first page, the user was prompted to enter an address, city, or postal code (screen shot). On postback, the user-entered address was fed into the Google Maps API's geocoding service to determine whether the address, as entered, corresponded to known latitude and longitude coordinates. If it did, the user was redirected to the second page with the address information passed through the querystring. This page then queried the database to find nearby stores and listed them in a grid and as markers on a map (screen shot).
Since the WebForms store locator application was published, several readers have emailed me to ask for an ASP.NET MVC version. I recently decided to port the existing WebForms application to ASP.NET MVC. This article, the first in a two-part series, walks through creating the ASP.NET MVC version of the store locator application and pinpoints some of the more interesting and challenging aspects. This article examines creating the ASP.NET MVC application and building the functionality for the user to enter an address from which to find nearby stores. Part 2 will examine how to show a grid and map of the nearby stores. 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. If you have not
yet read those articles and downloaded and tested the code, please take time to do that before continuing on with this article.
This article gives step-by-step instructions on building the store locator application using ASP.NET MVC; if you like, you can follow along from your computer as you read through the article. This ASP.NET MVC application is available for download at the end of this article and was created using C#, Visual Studio 2010, ASP.NET 4, and ASP.NET MVC 2. A key advantage of ASP.NET MVC over the WebForms model is that ASP.NET MVC applications naturally lend themselves to test-driven development (TDD). When creating ASP.NET MVC applications it is good practice to create a Unit Test project and to practice TDD. However, for this application I bypassed that step. I considered creating unit tests, but decided to pass on them for two reasons: first, I wanted the port to ASP.NET MVC to be as similar to the original WebForms version as possible, and the WebForms version did not include any tests; second, I was curious how long it would take to convert the existing application to ASP.NET MVC and was more interested in finding the quickest time, not the time it would take to do it "right." Long story short, realize the code presented in this article is meant to illustrate some of the challenges you might face when porting a WebForms application to ASP.NET MVC, as well as to highlight some of the key differences between the two models. It is not intended as a best practices guide. |
Step 1: Getting Started
This article provides step-by-step instructions on how I created the ASP.NET MVC version of the store locator application and is designed so that you can follow along and create the same ASP.NET MVC application with me from the ground up. In porting the application to ASP.NET MVC I used C# as the programming language as well as the latest versions of Visual Studio and ASP.NET MVC at the time of writing, namely Visual Studio 2010 and ASP.NET MVC 2. So fire up Visual Studio 2010 and let's get started!
To start, create a new ASP.NET MVC 2 Empty Web Application project. Doing so will create a new project with the pertinent ASP.NET MVC folders - Content
, Controllers
,
Models
, Views
, and so on - as well as an appropriately configured Global.asax
file that defines the default route.

There are a number of files from the original WebForms store locator application that will be needed in this ASP.NET MVC version, so let's add those to the project now. (You can get these files by downloading the WebForms store locator application at https://aspnet.4guysfromrolla.com/code/GoogleMapsDemo3.zip.) The files that need to be included in the ASP.NET MVC project include:
- The
StoreLocations.mdf
database file (and theStoreLocations.ldf
log file) in theApp_Data
folder need to be copied over to anApp_Data
folder in the ASP.NET MVC application. This database contains the table (Stores
) that has a record for each of the store locations. - The CSS files
sinorcaish-screen.css
andCustomStyles.css
. In the WebForms application these files were located in theStyles
folder. For the ASP.NET MVC application they need to be placed in theContent
folder. - The
GoogleMapsAPIHelpersCS.cs
file, which is located in theApp_Code/CSCode
folder in the WebForms application, needs to be included in the ASP.NET MVC application. Because the ASP.NET MVC application is a Web Application Project you should not create anApp_Code
folder; instead, create a new folder in the ASP.NET MVC application namedHelperClasses
and then copy theGoogleMapsAPIHelpersCS.cs
file into this new folder. - Copy the
GoogleMapHelpers.js
file from the WebForms application'sScripts
folder into the ASP.NET MVC application'sScripts
folder. Feel free to delete the jQuery and Microsoft ASP.NET Ajax-related files in the ASP.NET MVC application'sScripts
folder, as we won't be using them.

Step 2: Creating the Master Page
With the necessary files from the original store locator application copied into our ASP.NET MVC application, the next step is to create a master page for the ASP.NET MVC application. Master pages belong in the
Views\Shared
folder, so expand the Views
folder, right-click on the Shared
folder, and
add a new MVC 2 View Master Page named Site.master
. Next, open the Site.master
master page from the WebForms store locator application and
select all of the text in the page except for the <%@ Master %>
directive at the very top. Copy this content to the clipboard and then paste it
into the ASP.NET MVC application's Site.master
master page, overwriting all of the content except for its <%@ Master %>
directive.
The master page's content imported from the WebForms application is good to go, for the most part. There are a few areas we'll need to fix up, though. First up, note
that the <link>
elements in the <head>
element reference the CSS files sinorcaish-screen.css
and CustomStyles.css
as if they were in a subfolder named Styles
. However, these files, relative to the master page, are two directories up and in the Content
folder,
so the href
attributes in the <link>
elements need to be updated. Also, add a <link>
element for the Site.css
file
(also in the Content
folder).
Another change that's worth making is replacing the <title>
attribute with a ContentPlaceHolder control so that each content page can define the title declaratively.
Replace the existing text within the <title>
element - "Untitled Page" - with a ContentPlaceHolder named TitleContent
. Also, let's change the
head
ContentPlaceHolder's ID
from head
to HeadContent
to match the naming convention used with the TitleContent
ContentPlaceHolder.
After making these changes your ASP.NET MVC application's master page's
section should similar to the following:
<head runat="server">
|
Next, we need to get rid of the WebForm and other Web controls within the master page. Delete the server-side <form>
element's opening and closing tags
and the ScriptManager control.
Finally, the links in the left sidebar need to be updated. Right now there are three links to web pages in the application - one to Default.aspx
, one to
FindAStore.aspx
, and one to FindAStoreCS.aspx
. The Default.aspx
page is the application's homepage, whereas the
FindAStore.aspx
and FindAStoreCS.aspx
pages are the starting points for the store locator application (in VB and C#, respectively) where
the visitor will enter the address for which she wants to see nearby stores. ASP.NET MVC applications URLs take the form Controller/Action
(by default),
rather than PageName.aspx
. At this point we haven't yet decided the name of the controller or actions we'll be using. Once we decide upon these
names we'll need to return to the master page and update these URLs. For now, feel free to leave them as-is.
One final change before we're done with the master page - change the ID
of the ContentPlaceHolder control in the Main Content section of the page from
ContentPlaceHolder1
to MainContent
.
Step 3: Creating the Home Controller and the Index
Action and View
In an ASP.NET MVC application, incoming requests are handled by a controller, which is responsible for building the model and choosing the view to render the user interface. In the WebForms application there were two pertinent pages,
FindAStore.aspx
and ShowStoreLocations.aspx
.
FindAStore.aspx
prompted the user to enter an address. Upon entering an address that corresponded to a known latitude and longitude pair, the user was
redirected to ShowStoreLocations.aspx
with the address information passed through the querystring. ShowStoreLocations.aspx
then displayed a
grid of nearby stores and a map of the entered address with nearby stores rendered as markers on the map.
Let's keep this pattern in use - namely using two "pages" to implement the store locator application. Of course, in ASP.NET MVC we don't have pages, but rather actions.
Let's create a controller named HomeController
that will have actions named StoreLocator
and StoreLocatorResults
, which function
like the FindAStore.aspx
and ShowStoreLocations.aspx
pages in the WebForms application.
To start off, we need a controller, which is a class that contains the actions. Right-click on the Controllers folder and choose to add a new controller. This displays
the Add Controller dialog box, where you can specify the controller's name and whether Visual Studio should automatically create action methods for create, update, delete, and
details scenarios. Name it HomeController
and do not have it auto-generate any actions.

A controller is composed of actions, which are public methods that return an object of type ActionResult
. When a visitor reaches an ASP.NET MVC application
the URL determines which controller and which action is executed. (The mapping from URL patterns to which action is executed is orchestrated by
ASP.NET's Routing framework and ASP.NET MVC's MapRoute
extension method, two topics that
are beyond the scope of this article.)
For example, the controller class that was just created already has an action defined (Index
).
If a user were to visit www.yoursite.com/Home/Index
then this action would be executed. What's more, in Global.asax
the routing rule
specifies default values of "Home" and "Index" for the controller and action portions of the URL, meaning that if someone visits www.yoursite.com/Home
or www.yoursite.com
they same controller and action will execute. The HomeController
will also be where we add our StoreLocator
and StoreLocatorResults
actions, meaning this functionality will be accessible via www.yoursite.com/Home/StoreLocator
and
www.yoursite.com/Home/StoreLocatorResults
, respectively.
Actions typically have an associated view, which is responsible for generating the user interface for the request. By default, views are implemented as
.aspx
files in the Views
folder. Creating a view is simple enough - just right-click on the action's method name in the controller's class file
and choose the Add View option from the context menu. This brings up the Add View dialog box (shown below) where you can name the view and choose its master page, among
other things.
Let's take a moment to create a view for the Index
action. Right-click on the Index method name in the HomeController.cs
class file and choose
Add View. When the Add View dialog box appears, click OK.

Adding the view creates a new file in the Views\Home
folder named Index.aspx
. Let's keep this "page" simple, with nothing but a link to the
"page" where the user will enter an address. Recall that the link to enter the address will be www.yoursite.com/Home/StoreLocator
. Consequently, you can
create an <a>
element like so: <a href="/Home/StoreLocator">Enter an address!</a>
. Alternatively, you can use ASP.NET MVC's
Html.ActionLink
method to generate a hyperlink to a specific action. This server-side method takes input parameters such as the link text and the name of
the action to link to, and returns a string of HTML. Using the Html.ActionLink
method is preferred to hard-coding your hyperlinks because it is more resilient
to changes in the controller names.
Add the following markup to the MainContent
Content control in Index.aspx
:
<h2>Welcome to the Store Locator (MVC)!</h2>
|
The Html.ActionLink
method call above creates the HTML to render a hyperlink to the StoreLocator
action (Home/StoreLocator
)
with the link text, "Find a store near you!" (Specifically, it generates the HTML <a href="/Home/StoreLocator">Find a store near you!</a>
.) To
emit the HTML returned by this server-side method we surround it with the <%: ... %>
tags which tells
ASP.NET to output the result of the code within and to HTML-encode it. For more on how the <%: ... %>
tags work, see
New <%: %>
Syntax for HTML Encoding Output in ASP.NET 4 (and ASP.NET MVC 2).
Now would be a good time to return to the master page and update the links in the left sidebar. I suggest replacing the hyperlinks with two Html.ActionLink
statements, one that points to the Index
and the other that points to the StoreLocator
action:
<ul>
|
Step 4: Creating the StoreLocator
Action and View
With the
HomeController
class created, we're ready to add our StoreLocator
actions. (There will actually be two
StoreLocator
actions - more on that in a moment.) Recall that the intent of this page is to prompt the user to enter an address.
Once the address is entered we need to invoke the Google Maps API's geocoding
service to determine if the address is a valid address - that is, one that has a known latitude and longitude pair. If so, great, we can send the user onto the
StoreLocatorResults
action to display the nearby stores. If not, that means one of two things: either the address is completely unknown or there may be
multiple matching addresses. For example, entering an address of "Springfield" is ambiguous because Google doesn't know if you mean Springfield, Missouri;
Springfield, Illinois; Springfield, Ohio; or some other Springfield.
But first things first - let's create a simple action that returns a view with the user interface for entering the address information. Start by adding a new action
to the HomeController
class named StoreLocator
that returns a view. After making these changes your ASP.NET MVC application's
master page's
public class HomeController : Controller
|
Next, add a view for the StoreLocator
action. In the view we need to create a form that, when submitted, will do a postback, which is when we'll determine
whether the entered address was valid or not. Inside the form we need a textbox for the user to enter the address and a button that, when clicked, will submit the form.
With WebForms you would simply use a TextBox control and a Button control, but with ASP.NET MVC you do not have those controls at your disposal. Instead, you are on the
hook for writing the necessary HTML. To simplify this process, ASP.NET MVC offers a number of helpers methods available through the Html
object. As the
following content shows, the Html class's BeginForm and TextBox methods are used to generate the HTML for a <form>
and for a textbox (<input type="text" ... />
).
Put the following markup into the Content control for the MainContent
ContentPlaceHolder.
<% using (Html.BeginForm()) { %>
|
If you visit this page through a browser (www.yoursite.com/Home/StoreLocator
) you should see a user interface that prompts you for an address with a
button titled "Search!"

Clicking the "Search!" button submits the form, causing the browser to re-request www.yoursite.com/Home/StoreLocator
and sending the address entered by the
user back to the server. This, again, causes the HomeController
class's StoreLocator
action to execute, but this time we want to take the
user's address and determine if it's valid. To accomplish this we could expand our existing action to handle the postback logic, but a cleaner approach is to create
another StoreLocator
action that is configured to execute only when there is a postback. What's more, we can define the
parameters we expect to be passed back on form submission as input parameters to this action method.
The following code snippet achieves this end. Note the addition of the [HttpPost]
attribute - this is what tells ASP.NET MVC to only run this action when the
form is posted back. Also note the action's input parameter, address
, of type string
. This input parameter is automatically assigned the value
the user entered into the address
textbox.
public class HomeController : Controller
|
Much of the code for this action can be taken from the code created in the WebForms version. Here is the majority of the code, with a few places that still need filling out:
[HttpPost]
|
Note that the method starts by asking, "Did the user supply an address?" If not, the view is returned, so the user sees the same page as before. Presuming an address
was supplied, the code calls the GoogleMapsAPIHelpersCS
class's GetGeocodingSearchResults
method, which interacts with the Google Maps API's
geocoding service to determine if the address is valid. If the address is valid there will be one result returned from the GetGeocodingSearchResults
method,
namely the address that was passed into the method. If there were no results then we need to tell the user their address could not be found. Likewise, if there are
multiple results that means that there was some ambiguity, in which case we want to show the user various options to choose from.
Right now in the cases when there are zero results or multiple results we return the view. This has the effect of just reloading the page and is, obviously, no help to
the user. We'll come back to this momentarily to see how we can send information to the view that zero or multiple results were found so that the view can display
more relevant information. But before we get there, let's look at what we're doing in the case of one result. Note that this returns a RedirectToAction
object,
which, simply put, tells the browser, "Hey, go over here." It's akin to a Response.Redirect
, but instead of specifying a URL to redirect to we specify
an action. In this case we say, "Go to the StoreLocatorResults
action and pass along the formatted address." The net effect is that the browser will
be told to request www.yoursite.com/Home/StoreLocatorResults?Address=address
. This will result in a 404 for the time being, as there is no
StoreLocatorResults
action defined in the HomeController
class. We'll create this action in Part 2, which will be responsible for taking
that address and showing nearby store locations in both a grid and on a map.
Now that we have the case where there is precisely one result out of the way (with the actual functionality of showing the nearby stores saved for Part 2), let's return to how we're going to deal with the case when there's zero or multiple results. In short, when there are zero or multiple results we need some way for the action to tell the view this information. Moreover, if there are multiple results the action needs to tell the view what results were found so that they may be displayed.
With MVC, the model is used to manage information. The controller is tasked with constructing the model, which the view can use to generate its output.
ASP.NET MVC provides two mechanisms for building the model: through the loosely-typed ViewData
collection and through strongly-typed models. The
StoreLocatorResults
action, which we'll examine in Part 2 of this series, uses a strongly-typed model. For the StoreLocator
action, let's use the
ViewData
collection.
The ViewData
collection is a dictionary who items are indexed by a string. The syntax for accessing or reading values from ViewData
is the
same as with session variables - you just use ViewData["someName"] = someValue
to add an item to the ViewData
collection and
ViewData["someName"]
to read that value back out. But unlike session, which is specific to a user and lives across requests, the items you put into
the ViewData
collection are specific to a request. Think of ViewData
as this big ol' bucket that you can throw things into from the controller
that the view can then rummage through.
Let's update our code in the StoreLocator action to use the ViewData
collection. In the case when there are no matching addresses found we'll add a flag
to the ViewData
collection that tells the view as much. In the case of multiple results, we'll take the formatted addresses from all of the potential
matches and put that in the ViewData
collection. The following code shows the update if
and else
statements in the StoreLocator
action, with the just-added code highlighted.
if (resultCount == 0)
|
Note that in the case of zero results we add a ViewData
element named NoResults
with a value of true
. In the case of multiple
addresses we get back just the collection of formatted addresses (which are each a string) and stick that in the ViewData
collection with the name Matches
.
The last part of the puzzle is to update the view so that it displays appropriate information based on whether there are no results or multiple results. First, the markup for handling no results (which I have placed beneath the textbox and button, but could really go anywhere in the view).
<% if (ViewData["NoResults"] != null) { %>
|
The above markup contains a mix of server-side code and HTML. We start by determining if there's an item in the ViewData
collection named
NoResults
. If so, that implies that no results were found, in which case we emit the HTML to display a message that explains that the entered address
was not found.
To see this in action, enter a bogus address, like "asdf". As the following screen shot shows, on postback the geocoding service reports no matching addresses, which
causes the action to add the NoResults
value to the ViewData
collection and to return the view. When the view is rendered, it notes that
the ViewData
collection contains a NoResults
element and, consequently, displays a message alerting the user. (Note: I made some changes to
the input-validation-error
CSS class in Site.css
to get the formatting you see in the screen shot below.)

If there are multiple addresses found, we want to display a list of these possible addresses, with each one linking to the SearchLocatorResults
action passing along
the address through the querystring. For example, say the user searches on "Springfield". Google's geocoding service will return a collection of potential addresses,
including: "Springfield, MO, USA"; "Springfield, IL, USA,"; and "Springfield, OH, USA", among others. We want to let the user click one of these and be taken to
the results "page," just as if they had typed in the formatted address initially.
To achieve this, the view needs to loop through the results and, for each one, add a link to the StoreLocator
action, passing the address through the
querystring just like we did in the StoreLocator
action when there was precisely one result from the geocoding service. The following markup and code
accomplishes this:
<%
|
The above markup/code starts by determining if the ViewData
collection contains an element named Matches
. If so, some markup is emitted and then
a foreach
loop is used to enumerate the possible addresses. For each address, a link is generated (using Html.ActionLink
) to the
StoreLocatorResults
action, passing the address through the querystring.
To test out this functionality, enter an address like "Springfield". You should see a bulleted list of possible address matches. Clicking one of them takes you to the
StoreLocatorResults
action, passing the address through the querystring.

Looking Forward...
At this point we have succeeded in porting the first part of the WebForms store locator application to ASP.NET MVC. In particular, we created the ASP.NET MVC web application, added a master page, created the
HomeController
class and implemented both the Index
and StoreLocator
actions and views. All that
remains is to implement the StoreLocatorResults
action, which is responsible for displaying the nearby stores in a grid and on a map. We'll tackle this in an
upcoming Part 2.
Happy Programming!
Attachments:
Further Readings: