Wednesday, March 10, 2010

Categories

Minimize

Archive

Minimize

Tag Cloud

Minimize

Altriva Team Blog

Minimize

Use Bing Maps with Dynamics CRM 4.0

Posted by: Tim Dutcher on 9/30/2009
  • Categories:
  • CRM

Combining Microsoft Dynamics CRM 4.0 and Bing Maps can help businesses visualize geographic and location-based information quickly and easily. This article provides examples and source code to help you implement this useful mashup.

In the following example, our fictitious client “Amgeen Brake Products” asked us to drop by each of their retail locations in Michigan for a safety inspection. Before getting in her car, our safety inspector opened Dynamics CRM, loaded the Facilities view for the customer, selected the Michigan locations, and clicked the custom Map button to get a print-out of the locations.

Clicking the Map button invokes a custom ASP.NET application. The application queries CRM for the location (longitude/latitude) of each facility and constructs Jscript code that controls Bing Maps. The resulting map is shown below.

It looks like our safety inspector has a full day of driving ahead!

Now, let’s jump into technical details for creating the ASP.NET application I mentioned above.

Application Overview

The Visual Studio .NET 2005 application structure is shown below. The application is deployed as a typical CRM ISV application, which means that it is deployed into a subfolder under the CRM website ISV folder.  (Note: The solution uses the Web Deployment Projects add-in from Microsoft to produce a fixed .NET assembly name.)

The file that is called from CRM (by way of a custom ISV.Config button) is “MapSelectedFacilities.aspx”. The custom button is added to the Facility grid’s menubar with the following ISV.Config entry:

<Grid>
<MenuBar>
  <Buttons>   
    <Button Icon="/_imgs/ico_18_debug.gif" Url="/isv/altriva/BingMaps/MapSelectedFacilities.aspx" WinMode="2">     
    <Titles>       
    <Title LCID="1033" Text="Map" />     
    </Titles>     
    <ToolTips>        
      <ToolTip LCID="1033" Text="Map the selected Facility records" />      
    </ToolTips>    
    </Button> 
  </Buttons>
  </MenuBar>
</Grid>

Determining Selected Rows in a CRM Grid

In order to map only the selected facilities, it’s necessary to get the ID of each of the selected facilities in the CRM grid and pass the collection to the ASP.NET application. The function “listselecteditems” in the ASPX page (see below) provides this functionality.  (Thanks to David Fronk at Dynamic Methods for posting the hidden field technique that I ended up using in this application.)

The HTML for MapSelectedFacilities.aspx and the code-behind C# class is provided below.

MapSelectedFacilities.aspx

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="MapSelectedFacilities.aspx.cs" Inherits="MapSelectedFacilities" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Bing Map</title>

    <script type="text/javascript">
    function listselecteditems()
    {
        var hiddenfield = document.getElementById("HiddenField_SelectedGUIDs");
        var sGUIDValues = "";
        var selectedValues;
        if (window.dialogArguments)
        {
            selectedValues = new Array(window.dialogArguments.length - 1);
        }
        else
        {
            return;
        }

        selectedValues = window.dialogArguments;
      
        if (selectedValues != null)
        {
            for (i = 0; i < selectedValues.length; i++)
            {
                sGUIDValues += selectedValues[i] + "\n";
            }
            var hidden = sGUIDValues;
            form1.HiddenField_SelectedGUIDs.value = hidden;
            form1.submit();
        }
        else
        {           
            alert("Please select one or more facility records to map.");       
        }    
    }   
    </script>
</head>
<body onload="listselecteditems()">    
    <form id="form1" runat="server">     
        <input id="HiddenField_SelectedGUIDs" type="hidden" runat="server" />   
    </form>
</body>
</html>

When CRM invokes MapSelectedFacilities.aspx, it provides the selected facility IDs in the dialogArguments window property. Those values are stored in the hidden HTML object “HiddenField_SelectedGUIDs”.

MapSelectedFacilities.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;

public partial class MapSelectedFacilities : System.Web.UI.Page
{
    AccountFacilities _accountFacilities;
    protected void Page_Load(object sender, EventArgs e)
    {
        if (this.IsPostBack)
        {
            string crmOrgName = "Altriva";

            if (HiddenField_SelectedGUIDs.Value != string.Empty)
            {
                // Get the list of selected Facility records in the CRM grid view
                string rawFacilityGuidList = HiddenField_SelectedGUIDs.Value.ToString();
                rawFacilityGuidList = rawFacilityGuidList.TrimEnd();
                rawFacilityGuidList = rawFacilityGuidList.Replace("{", "").Replace("}", "");
                string[] arrFacilityGuidsStr = rawFacilityGuidList.Split(new char[] { '\n' });

                using (new CrmImpersonator())
                {
                    CrmDataRetriever crmDataRetriever = new CrmDataRetriever(crmOrgName);
                    _accountFacilities = crmDataRetriever.GetFacilityLocationDetails(arrFacilityGuidsStr);
                }

                Server.Transfer("~/MapSelectedFacilitiesPost.aspx", true);
            }
        }
    }
    public AccountFacilities FacilityDetails
    {
        get
        {
            return this._accountFacilities;
        }
    }
}

When the code-behind is executed, it reads the list of GUIDs, parses them into an array, and then queries CRM for the longitude and latitude for each of the facilities. The custom method GetFacilityLocationDetails is in a class that is responsible for querying CRM for facility details. Since there are several examples of querying CRM data in the CRM SDK I am not providing the source code for the CrmDataRetriever class – it is simply a class that’s responsible for retrieving and storing (as class properties) facility details.

For purposes of “separation of responsibility” in the application design, I chose to divide the work of collecting facility details and rendering the Bing Map into two ASPX pages. That explains the use of Server.Transfer in the code-behind provided above.

Rendering the Bing Map

As mentioned above, the code-behind for MapSelectedFacilities.aspx passes control to MapSelectedFacilitiesPost.aspx. This second page generates the Jscript code to render the Bing Map.

The HTML for MapSelectedFacilitiesPost.aspx and the code-behind is provided below.

MapSelectedFacilitiesPost.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="MapSelectedFacilitiesPost.aspx.cs" Inherits="MapSelectedFacilitiesPost" %>
<%@ PreviousPageType VirtualPath="~/MapSelectedFacilities.aspx" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">   
  <title>Facilities</title>   
  <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2">
  </script>
</head>
<body onload="LoadMap()" onunload="UnloadMap()">   
  <form id="form1" runat="server">       
  <div id='mapDiv' style="position:relative; width:800px; height:600px;" runat="server">
  </div>   
  </form>
</body>
</html>

You’ll see that the onload event of the HTML body calls a function named “LoadMap”. That function, along with UnloadMap, is generated in the page’s code-behind.

MapSelectedFacilitiesPost.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class MapSelectedFacilitiesPost : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        GenerateClientSideMapCode(PreviousPage.FacilityDetails);
    }
    private void GenerateClientSideMapCode(AccountFacilities accountFacilities)
    {
        ClientScriptManager csm = Page.ClientScript;

string code = @"
<script type='text/javascript'>
 var myMap = null;
 function LoadMap()
 {
    myMap = new VEMap('mapDiv');
    myMap.LoadMap();
    AddPushpinShapes();
 }

 function UnloadMap()
 {
    if (myMap != null) {
       myMap.Dispose();
    }
 }
";
        // Render the JavaScript code that adds pushpins to the map.
        int mapShapeCounter = 0;
        string pushpinCode = "\n\nfunction AddPushpinShapes()\n{";
        pushpinCode += "\n" + "var pushpinCounter = 0;" + "\n";
        foreach (AccountFacilities.AccountFacilitiesRow dr in accountFacilities._AccountFacilities)
        {
            if (!dr.IsFacilityLatitudeNull() && !dr.IsFacilityLongitudeNull() && dr.FacilityLatitude != "0" && dr.FacilityLongitude != "0")
            {
                mapShapeCounter++;
                pushpinCode += "\n" + "var veLatLong" + mapShapeCounter + " = new VELatLong(" + dr.FacilityLatitude + "," + dr.FacilityLongitude + ");";
                pushpinCode += "\n" + "var veShape" + mapShapeCounter + " = new VEShape(VEShapeType.Pushpin, veLatLong" + mapShapeCounter + ");";
                pushpinCode += "\n" + "myMap.AddShape(veShape" + mapShapeCounter + ");";
                pushpinCode += "\n" + "pushpinCounter++;" + "\n";
            }
        }
        pushpinCode += "\n" + "if (pushpinCounter > 0) { myMap.SetCenterAndZoom(veLatLong1, 7); }\n}";
        code += pushpinCode;
        code += "\n\n</script>";

        csm.RegisterClientScriptBlock(this.GetType(), "map", code);
    }
}


The code-behind generates the Jscript code that creates the Bing Map instance (see “new VEMap”), plots the pushpins, and then registered the Jscript code with the page so that it will execute once the page loads. The mapping process begins with the call to the LoadMap function.

A Second Example

The screen shown below demonstrates another way of integrating Bing Maps into Dynamics CRM. In this example, the Bing Maps screen appears in an IFRAME within the CRM UI. The code needed to implement this functionality is essentially the same as provided in this article. The difference is that, for this implementation, all facilities for the account are mapped rather than only the selected facilities.

Summary

With a small amount of ASP.NET and Jscript code, it’s possible to add significant new capabilities to Dynamics CRM. This blog post demonstrated how using Bing Maps with CRM data can help service personnel plan for road travel. Bing Maps have hundreds of other uses as well. Some of the more popular applications include geography-based sales analysis, GPS-based tracking of personnel and vehicles, and for strategic planning of business opportunities.

Bing Maps Licensing

Please visit the Licensing Bing Maps for Enterprise page for details regarding the commercial use of Bing Maps.

Create a trackback from your own site.

0 Comments

Leave A Comment



Please enter the CAPTCHA phrase above.



  
  

Recent Comments

Minimize

"Apologies to those who have commented on this post -- your comments have been mistakenly removed." Read more
by Phil Edry on Allow Multiple Users to Sync the Same Contact to Outlook Effortlessly with Microsoft CRM

  
Copyright 2010 by Altriva, LLC