In my last post we saw that using the MapQuest NET API version 5.3 in an ASP.NET web application was not all that much harder than using it in a winforms application. Now in order to easily reuse some of the code it would be nice to create an ASP.NET custom control which displays a map just based on an origin and a destination.

So what to do?

  1. We want to put the control in a class library, so we add an item of type 'ASP.NET Server control named 'RouteMapControl'. This will create a class which inherits from WebControl.
  2. Next we add an Origin and Destination propert of type 'GeoAddress' (from the MQClientInterface namespace).
  3. Lastly we override the 'protected override void RenderContents( HtmlTextWriter output )' method and have it use the Origin and Address to create a route and use the route to create a map.

This is what the code for the class looks like:

using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using MQClientInterface;
using DevelopOne.MapQuest.Proxies;
using DevelopOne.MapQuest.Extensions;

namespace DevelopOne.MapQuest.Web.Controls
{
    [DefaultProperty( "Text" )]
    [ToolboxData( "{0}:RouteMapControl>" )]
    public class RouteMapControl : WebControl
    {
        [Bindable( true )]
        [Category( "MapQuest" )]
        [DefaultValue( "" )]
        [Localizable( false )]
        public GeoAddress Origin
        {
            get
            {
                GeoAddress o = new GeoAddress();
                o.Init();
                o.LatLng.Latitude = (double) ViewState["Origin_Latitude"];
                o.LatLng.Longitude = (double) ViewState["Origin_Longitude"];
                return o;
            }

            set
            {
                ViewState["Origin_Longitude"] = value.LatLng.Longitude;
                ViewState["Origin_Latitude"] = value.LatLng.Latitude;
            }
        }

        [Bindable( true )]
        [Category( "MapQuest" )]
        [DefaultValue( "" )]
        [Localizable( false )]
        public GeoAddress Destination
        {
            get
            {
                GeoAddress o = new GeoAddress();
                o.Init();
                o.LatLng.Latitude = (double) ViewState["Destination_Latitude"];
                o.LatLng.Longitude = (double) ViewState["Destination_Longitude"];
                return o;
            }

            set
            {
                ViewState["Destination_Longitude"] = value.LatLng.Longitude;
                ViewState["Destination_Latitude"] = value.LatLng.Latitude;
            }
        }

        protected override void RenderContents( HtmlTextWriter output )
        {
            if ( this.Origin == null || this.Destination == null )
            {
                return;
            }
            else
            {
                if ( this.Width.IsEmpty )
                    this.Width = new Unit( 800 );

                if ( this.Height.IsEmpty )
                    this.Height = new Unit( 600 );

                RouteResults route;
                using ( RouteServerProxy proxy = new RouteServerProxy() )
                {
                    route = proxy.CreateRoute( this.Origin, this.Destination );
                }
                using ( MapServerProxy proxy = new MapServerProxy() )
                {
                    string url = proxy.GetMapUrl( route.ShapePoints.ToLine(), (int)this.Width.Value, (int)this.Height.Value, true).ToString();
                    url = HttpUtility.HtmlEncode( url );
                    string html = "<img src=\"" + url + "\"/><br/>Distance: " + Math.Round(route.Distance, 2).ToString() + " miles.";
                    output.Write( html );

                }
            }
        }
    }
}

Notice in the RenderContents method that:

  1. We're creating an HTML image tag, but also some text to display the distance of the route.
  2. The proxy.GetMapUrl has a boolean parameter as the fourth parameter. I discovered that for longer routes the amount of information in the Url becomes too much, making the Url too long and it won't load. Using the true parameter makes the proxy use a serverside session. Now the Url just contains a reference to the session and stays much shorter. The code needed to create a session is straightforward, use the Exec object in the MapQuest library.:
    string sessionId = Exec.CreateSessionEx( session );
    url = Exec.GetMapFromSessionURL( sessionId, new MQClientInterface.DisplayState() );

By adding a reference to the class library Visual Studio 2008 will detect the control and make it available in the toolbar when you're editing an ASP.NET page. Just drag and drop the control on you form and your ready to go.

I like my controls to have meaning full tags so I add a control reference in the web.config:


<pages>
   <controls>
        <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add tagPrefix="mq" namespace="DevelopOne.MapQuest.Web.Controls" assembly="DevelopOne.MapQuest"/>
  </controls>
</pages>


The markup in the page now looks like this:

<mq:RouteMapControl ID="RouteMapControl1" runat="server" />

If you choose to skip the web.config step you'll see the tag as 'cc1:RouteMapControl'.

Happy coding!

- Mark Blomsma

Download the code here.