Virtual Earth meets MVC


Something I do quite a bit with customers is to help them understand and use Virtual Earth... plus I’ve been doing some overviews of the beta of the ASP.NET MVC Framework, so it seemed a great combination. How would you best combine them?


Note: This post is based on the October 2008 Beta of the ASP.NET MVC Framework... it will likely break before v1.0.


ASP.NET WebForms Approach


When hosting Virtual Earth in a WebForms solution, I use the following rough architecture;



The WCF service has two jobs; it provides lists of points to display on the map, and it provides additional information to render about each point if the user clicks a “more information” link. This info is usually shredded manually into the HTML by custom script.


There are a few problems with this;


1.       You cannot currently use the <ASP:ScriptManager> control in ASP.NET MVC.


2.       I want to minimize the amount of custom AJAX I write – the MVC framework has helpers that I want to use.


3.       I suspect some of the partial rendering capabilities of the MVC framework will be useful.


An ASP.NET MVC Approach


My MVC approach is slightly different. On face value it looks complex, but in reality the implementation is very straightforward;


 


Well let’s get going. Hosting Virtual Earth is easy, so we’ll get the basics in place and see what we’ve got. Firstly I need to import the Virtual Earth controls with a simple script tag;


<script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6"></script>


Note I’m using version 6 – if a new version is out when you read this, you may need to update this link and revisit some of the code.


Now I’ll create a “div” to hold the map in my Master Page (do excuse some of the terrible CSS and layout in this post, I’ve just not got time to beautify my code today!);


<div id="LocationsMap"


     style="position: relative; width: 100%; height: 100%;">


</div>


Next, I edit the body tag to cause the map to load with the page;


<body onload="DisplayMap()">


... and add a simple JavaScript function called DisplayMap that does the work. The core of this is creating the VEMap control, specifying the name of my “div”, as follows – for the rest, check out the code download;


map = new VEMap('LocationsMap');


This is all on my master page, and we’ve now got enough code to display a map the user can navigate around. To let us do this, simply add an Action to a Controller that returns a View(), which in turn is using the master page we’ve just defined;


public ActionResult Display()


{


    return View();


}


Problem 1: No ScriptManager


The next thing I need to do is call my WCF service to fetch a list of “waypoints” to display on my map. But ASP.NET MVC isn’t compatible with the ScriptManager control. There are a few options here – the first is a codeplex project named ajaxmvc. What I’ve found to be a simpler solution, though, is to manually create the script reference that the ScriptManager creates for you;


<script src="<%= Url.Content("~/Services/WaypointService.svc/js") %>" type="text/javascript"></script>


I use the MVC framework’s “Url.Content” helper method to map the URL to my service, and append  “/js” to ask the service for JavaScript proxy script. This now means I can call my service using the generated proxy;


function GetWayPoints() {


    MvcWithVE.Services.IWaypointService.GetWaypoints(


        SuccessCallback, FailureCallback);


}


The callback does the job of adding each waypoint to the map. Check out the code download to see how, but there is an important bit coming up...


Problem 2: Loading More Information


What I want to do next is load in more information about a selected waypoint, and display it in a panel beside the map. This just sounds ideal for the MVC framework’s partial rendering capabilities. If I can return a fragment of HTML and push it into the page, I’ll have saved loads of time.


Well, the Ajax.ActionLink helper method is useful here. I can provide it with a Controller and Action to call, and the ID of an HTML element to put the returned fragment into.


So let’s create a <span> to hold our fragment containing further information;


<span id="details">Please select a Waypoint...</span>


How do I get Virtual Earth to give me a “more information” link that populates this span then? The solution is two-fold. Firstly, I like to create a template for what will be displayed when I hover over a pin on the map;


<span id="pinTemplate" style="visibility: hidden;">


    <div>


        <p>


            <b>{{Title}}</b>


        </p>


        <p>


            This Waypoint is located at {{Latitude}} latitude,


            {{Longitude}} longitude. For more information


            <%= Ajax.ActionLink("click here...", "WaypointDetails",


                        new { id = "{{Id}}" },


                        new AjaxOptions { UpdateTargetId="details" })%>


        </p>          


    </div>


</span>


This is a very simple bit of HTML, with markers in place (e.g. “{{Title}}”) that I can use to replace with real data. I guess I could put it in a separate User Control and use RenderPartial to pull it in if I wanted to. The cunning bit is that I also embed an Ajax.ActionLink call, providing a placeholder named {{Id}} for the “id” parameter to the WaypointDetails action. I’ve also specified the UpdateTargetId parameter to AjaxOptions as “details”, the name of the span I created above.


Now consider the code I use to add a pin to the map;


// Adds a waypoint to the map


function AddPoint(id, name, latitude, longitude)


{


    // create the pin


    var pin = new VEShape(VEShapeType.Pushpin,


                          new VELatLong(latitude, longitude));


 


    // load in the template for the pin


    var content = document.getElementById('pinTemplate').innerHTML;


   


    // replace fields with values


    content = content.replace('{{Title}}', name);


    content = content.replace('{{Latitude}}', latitude);


    content = content.replace('{{Longitude}}', longitude);


    content = content.replace(escape('{{Id}}'), id);


   


    // set the pin's content to the parsed template


    pin.SetDescription(content);


   


    // enable the pin's icon


    pin.ShowIcon();


          


    // add it to the map


    map.AddShape(pin);


}


I load in my template and replace the fields with real data. The one thing that might trip you up is that I must URL-encode the “{{Id}}” placeholder using the JavaScript “escape” function in order for the replace method to match it, as the Ajax.ActionLink will have URL-encoded it already. You can see this by looking at the HTML source for a rendered web page, and note that the link has become;


... href="http://blogs.msdn.com/Map/WaypointDetails/%7B%7BId%7D%7D" ...


We then use the pin.SetDescription call to ask Virtual Earth to display this whole fragment when a pin is hovered over.


The very last piece of the puzzle is that we need an Action named “WaypointDetails” that the Ajax.ActionLink method is pointing at. This is really simple in my case, as I’m just using LINQ-to-SQL to pull back some data;


public ActionResult WaypointDetails(int id)


{


    using (WaypointDataContext db = new WaypointDataContext())


    {


        Waypoint point = db.Waypoints.Single(w => w.Id == id);


        return View(point);


    }


}


Of course, I have a matching View named “WaypointDetails.ascx”, which looks like this;


<table class="WaypointInfoTable">


<tr>


    <th>Title</th>


    <td><%= ViewData.Model.Title %></td>


</tr>


<tr>


    <th>Latitude</th>


    <td><%= ViewData.Model.Latitude.ToString() %></td>


</tr>


<!-- snip -->


</table>


To round off the solution, I’ve added a link to call GetWaypoints() when the user clicks on it, tweaked the routing to default to my new Controller, and deleted some of the stuff the MVC project template gives me by default that I don’t need.


Phew!


OK, I’ve been through this really quickly because I think the code is pretty self-explanatory, but I hope you’ve enjoyed seeing how easy this is. What is more, it gives you a great way to separate responsibilities for rendering the host page, rendering fragments, hosting your logic, fetching data, and more.


The end result is pretty cool, I think! Like it?



 

MvcWithVE.zip

Comments (4)
  1. This post by Simon Ince explorer the combination of Virtual Earth and ASP.NET MVC Framework. Something

  2. Dan says:

    Had one problem getting the sample up and running.  In both the sql included and the Waypoint.dbml file you used "date" as the data type for column UpdatedDate.  The system will complain about this with compile errors or worse, so it should be changed to "datetime" or "smalldatetime" depending on which you were intending to use.  Make sure you right click on the dbml and select "Run Custom Tool" after doing so.

    Otherwise, Love the sample and the cool tricks and insights.  Thank you.

  3. Simon J Ince says:

    @ Dan;

    apologies – I should have pointed that out.

    The "Date" data type is a SQL 2008 type, as I created the demo on this platform originally. In order to run it on SQL 2005 I believe the only change is to use a "datetime" or similar, exactly as you point out.

    Thanks for the heads up, and for the feedback!

    Simon

Comments are closed.

Skip to main content