Tag Archives: Bing Maps

Geocoding and displaying a map for an address in CRM 2011

There are a few blog posts out there that cover this topic, but the ones I’ve found don’t take you through it step by step.  I will, however, make assumptions like you know how to create an entity, add web resources to a form, etc.  Here goes…

The overall solution is going to consist of:

If you are a “yea yea, blog blah blah, just give me the code” type then jump to the bottom of the postSmile.

Let’s get started.  First, you will need the Developer Toolkit for Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online and NuGet installed.  Let’s create an entity called GeocodeMapSample. As a habit, I tend to uncheck all the entity defaults after the Options for Entity section of the form.  You can always go back and turn additional features on as you need them after you create the entity.

image

Next create the following fields (all of type single line of text):

  • AddressLine1
  • AddressLine2
  • City
  • State
  • Zip
  • Latitude
  • Longitude

Yours should look like mine except you will have a different prefix than dkdt_.

image

Finally, put everything on the form:

image

Once you’ve done all this, go ahead and Publish your changes.  Next, create a new Dynamics CRM 2011 Package project called GeocodeMapSamplePackage:

image

Fill out the Connect to Dynamics CRM Server dialog:

image

If you are using CRM Online, then your discovery service is dev.crm.dynamics.com if you log in with a Live ID or disco.crm.dynamics.com if your org was provisioned through Office 365.  Make sure you select HTTPS as the protocol if necessary.  Add a Dynamics CRM 2011 Plug-in Library called GeocodMapSamplePlugins to the Visual Studio solution:

image

Find the GeocodeMapSample entity in the Entities node of CRM Explorer, right-click it and select Create Plug-in:

image

Make your dialog look like mine:

image

I am picking the Pre-Operation for the Pipeline Stage because I want to be able to throw an exception back to the caller and roll the transaction back if geocoding fails because my business requirement is that addresses must be valid.  Create another plug-in for the same entity.  This time, we want to do a few things differently.  First, set the value of Message to Update, but DO NOT click OK yet:

image

We only want this plug-in to fire when address related fields change.  Click the Filtering Attributes ellipsis (…) to bring up the following dialog:

image

Make sure you have deselected all of the fields, then ONLY selected items in the image.  In an update scenario, we want to make sure the plugin has the previous values from when the form was loaded.  Click the Parameters ellipsis (…) next to Pre Image Alias to bring up a similar dialog to before and select the same set of attributes.  We’re going to ask the Bing Maps REST service for JavaScript Object Notation (JSON) since it’s the fastest across the wire.  Add the DynamicJson package from NuGet to the plug-in project:

image

image

Now it’s time to generate some early-bound types.  The CRM SDK covers early-bound vs. late-bound here.  Most developers prefer the early bound approach unless they truly need a late-bound for writing very generic, reusable code.  This is because you become more productive and less error prone due to intellisense and compile time checking.  While the developer toolkit does have the ability to generate strongly typed classes:

clip_image002

…it does not give you any control over what classes get generated.  It just brute force creates one for each entity you have read permissions to.  Therefore, you end up generating a bunch of unnecessary code that bloats your codebase and makes your plugin WAY BIGGER than it needs to be.  Instead, I prefer to use this approach to generate the classes:

http://dkdt.me/LO71gI

Which one you choose, is up to you.  Generate Wrapper is OK for now since you are just learning, but take my advice and learn the approach above for production codeSmile.  Add a new class to the plug-in project called GeocodeMapSampleCommon.cs.  Replace the entire contents of the class declaration with the following code:

using System;

using System.Net;

using System.Text;

using Codeplex.Data;

using GeocodeMapSamplePackage.GeocodeMapSamplePlugins.Entities;

using Microsoft.Xrm.Sdk;

 

namespace GeocodeMapSamplePackage.GeocodeMapSamplePlugins

{

    class GeocodeMapSampleCommon

    {

        internal static void GeocodeAddress(Plugin.LocalPluginContext localContext, Entity preImageEntity)

        {

            if (localContext == null)

            {

                throw new ArgumentNullException("localContext");

            }

 

            var pluginExecutionContext = localContext.PluginExecutionContext;

            var targetEntity = pluginExecutionContext.InputParameters["Target"] as Entity;

 

            if (targetEntity == null)

            {

                throw new NullReferenceException("targetEntity");

            }

 

            try

            {

                var targetGeocodeMapSampleEntity = targetEntity.ToEntity<dkdt_GeocodeMapSample>();

                var sb = new StringBuilder();

                

                // see http://dkdt.me/KH4roL for Bing Maps REST API reference

                const string restQueryStart = "https://dev.virtualearth.net/REST/v1/Locations/";

                const string noAddress = restQueryStart + ",,,,";

                var address1 = string.Empty;

                var address2 = string.Empty;

                var city = string.Empty;

                var state = string.Empty;

                var zip = string.Empty;

 

                sb.Append(restQueryStart);

 

                if (preImageEntity != null)

                {

                    var previousGeocodMapSampleEntity = preImageEntity.ToEntity<dkdt_GeocodeMapSample>();

 

                    if (targetGeocodeMapSampleEntity.dkdt_AddressLine1 == null && previousGeocodMapSampleEntity.dkdt_AddressLine1 != null)

                        address1 = previousGeocodMapSampleEntity.dkdt_AddressLine1;

                    if (targetGeocodeMapSampleEntity.dkdt_AddressLine2 == null && previousGeocodMapSampleEntity.dkdt_AddressLine2 != null)

                        address2 = previousGeocodMapSampleEntity.dkdt_AddressLine2;

                    if (targetGeocodeMapSampleEntity.dkdt_City == null && previousGeocodMapSampleEntity.dkdt_City != null)

                        city = previousGeocodMapSampleEntity.dkdt_City;

                    if (targetGeocodeMapSampleEntity.dkdt_State == null && previousGeocodMapSampleEntity.dkdt_State != null)

                        state = previousGeocodMapSampleEntity.dkdt_State;

                    if (targetGeocodeMapSampleEntity.dkdt_Zip == null && previousGeocodMapSampleEntity.dkdt_Zip != null)

                        zip = previousGeocodMapSampleEntity.dkdt_Zip;

                }

 

                if (targetGeocodeMapSampleEntity.dkdt_AddressLine1 != null)

                    address1 = targetGeocodeMapSampleEntity.dkdt_AddressLine1.Trim();

                sb.Append(address1);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_AddressLine2 != null)

                    address2 = targetGeocodeMapSampleEntity.dkdt_AddressLine2.Trim();

                sb.Append(address2);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_City != null)

                    city = targetGeocodeMapSampleEntity.dkdt_City.Trim();

                sb.Append(city);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_State != null)

                    state = targetGeocodeMapSampleEntity.dkdt_State.Trim();

                sb.Append(state);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_Zip != null)

                    zip = targetGeocodeMapSampleEntity.dkdt_Zip.Trim();

                sb.Append(zip);

 

                var restQuery = sb.ToString();

 

                if (restQuery == noAddress)

                {

                    return;

                }

 

                // TODO: Move key to plugin configuration

                const string key = "?key=[INSERT_YOUR_BING_MAPS_KEY]";

 

                var webClient = new WebClient();

 

                var jsonString = webClient.DownloadString(restQuery + key);

                var response = DynamicJson.Parse(jsonString);

 

                if (response.statusCode != 200)

                {

                    throw new InvalidPluginExecutionException("Bad address.  Please fix it and try again.");

                }

 

                var coordinates = response.resourceSets[0].resources[0].point.coordinates;

                targetGeocodeMapSampleEntity.dkdt_Latitude = coordinates[0].ToString();

                targetGeocodeMapSampleEntity.dkdt_Longitude = coordinates[1].ToString();

            }

            catch (Exception ex)

            {

                throw new InvalidPluginExecutionException("Something went wrong in the plug-in.  This must be buggy sample code eh?", ex);

            }

        }

    }

}

This code does all the geocoding work.  Your generated classes will have different prefix values than dkdt_, so fix the errors by replacing dkdt_ with your prefix.  You will also have to replace the [INSERT_YOUR_BING_MAPS_KEY] string with yours from http://dkdt.me/KH4pNx.  Because of the way I am sharing this class across multiple plugins we, need to make a slight tweak to the Plugin.cs class.  Change protected class Plugin : IPlugin to protected internal class Plugin : IPlugin.  Now is a good time to build.  If you get build errors, well fix em Smile.  It’s a blog post after all.  Assuming you got your build errors fixed, there are a couple more things to do.  Open PreGeocodeMapSampleCreate.cs and replace the contents of  the ExecutePreGeocodeMapSampleCreate method with:

GeocodeMapSampleCommon.GeocodeAddress(localContext, null);

Open PreGeocodeMapSampleCreate.cs and replace the contents of  the ExecutePreGeocodeMapSampleCreate method with:

if (localContext == null)

{

    throw new ArgumentNullException("localContext");

}

 

var context = localContext.PluginExecutionContext;

 

var preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(preImageAlias)) ? context.PreEntityImages[preImageAlias] : null;

 

GeocodeMapSampleCommon.GeocodeAddress(localContext, preImageEntity);

Probably a good time to build and troubleshoot again.  If your build is successful, then sign the plug-in assembly:

image

image

Ok, now you should be able to right-click the package project and deploy:

image

This will register your plug-in and messages.  Now go create a new GeocodeMapSample entity through the CRM UI.  Save, but don’t save and close.  You will see Latitude and Longitude have values after the save.  So now we have to present this stuff on a map.  Let’s do the easy one first: static map image.  Go back to Visual Studio and add a new web resource to the package project:

image

image

Replace the contents of StaticMap.htm with the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>

    <head>

        <title></title>

        <script type="text/javascript">
   1:  

   2:             function renderStaticMap() {

   3:                 // ReSharper disable InconsistentNaming

   4:                 var Xrm = window.parent.Xrm;

   5:                 // ReSharper restore InconsistentNaming

   6:  

   7:                 var latitude = Xrm.Page.getAttribute("dkdt_latitude").getValue();

   8:                 var longitude = Xrm.Page.getAttribute("dkdt_longitude").getValue();

   9:  

  10:                 if (latitude == null) {

  11:                     return;

  12:                 }

  13:                 if (longitude == null) {

  14:                     return;

  15:                 }

  16:  

  17:                 // see http://dkdt.me/KH4roL for Bing Maps REST API reference

  18:                 var imageUrl = "http://dkdt.me/KH4rF4" +

  19:                 latitude + "," + longitude +

  20:                     "/18?key=[INSERT_YOUR_BING_MAPS_KEY]";

  21:                 document.getElementById("mapImage").setAttribute("src", imageUrl);

  22:             }

  23:         

</script>

    </head>

    <body onload="renderStaticMap();" style="BORDER-RIGHT-WIDTH: 0px; BACKGROUND-COLOR: rgb(246,248,250); MARGIN: 0px; PADDING-LEFT: 0px; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; PADDING-TOP: 0px">

        <img id="mapImage" alt="An map image of the current record" src=""/>

    </body>

</html>

Again, replace dkdt_ with your prefix.  You will also have to replace the [INSERT_YOUR_BING_MAPS_KEY] string with yours from http://dkdt.me/KH4pNx.  Drop the web resource in the GeocodeMapSample entity form.  Publish it all and you should find a static map image on the form next time you load/refresh it.  Go through the same process to build a web resource named InteractiveMap.htm.  Replace the contents of the file with the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>

    <head>

        <title></title>

        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

        <script type="text/javascript" src="https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&s=1"></script>
   1:  

   2:         <script type="text/javascript">

   3:         var _map;

   4:  

   5:         function loadMap() {

   6:             _map = new Microsoft.Maps.Map(document.getElementById("map"), { credentials: "[INSERT_YOUR_BING_MAPS_KEY]" });

   7:  

   8:             // ReSharper disable InconsistentNaming

   9:             var Xrm = window.parent.Xrm;

  10:             // ReSharper restore InconsistentNaming

  11:  

  12:             var latitude = Xrm.Page.getAttribute("dkdt_latitude").getValue();

  13:             var longitude = Xrm.Page.getAttribute("dkdt_longitude").getValue();

  14:  

  15:             if (latitude == null) {

  16:                 return;

  17:             }

  18:             if (longitude == null) {

  19:                 return;

  20:             }

  21:  

  22:             var location = new Microsoft.Maps.Location(latitude, longitude);

  23:             var pushpin = new Microsoft.Maps.Pushpin(location);

  24:             _map.setView({ center: location, zoom: 10 });

  25:             _map.entities.push(pushpin);

  26:         }

  27:         

</script>

    </head>

    <body onload="loadMap();" style="BORDER-RIGHT-WIDTH: 0px; BACKGROUND-COLOR: rgb(246,248,250); MARGIN: 0px; PADDING-LEFT: 0px; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; PADDING-TOP: 0px">

        <div id="map" style="position: relative; width: 100%; height: 100%"></div>

    </body>

</html>

Again, replace dkdt_ with your prefix.  You will also have to replace the [INSERT_YOUR_BING_MAPS_KEY] string with yours from http://dkdt.me/KH4pNx.  Drop the web resource in the GeocodeMapSample entity form.  Drop the web resource in the GeocodeMapSample entity form.  Publish it all and you should find a static map image on the form next time you load/refresh it.  Finally, Latitude & Longitude really are just there for code interaction.  They don’t need to be visible on the form.  Go ahead and hide them.

That’s it!  If you made it through the whole post, you now know the fundamentals of using plug-ins, web resources, and the Bing Maps SDKs to add basic “geospatial” functionality to CRM 2011.  Of course, you probably won’t want to scatter your Bing Maps key through out your code, but I wanted to keep the walkthrough as simple to follow as possible.  You can grab the unmanaged solution and Visual Studio source code here:

http://sdrv.ms/KH4j8D

@devkeydet

San Francisco, Open 311, Silverlight and Windows Azure

“City of San Francisco, CA has launched their Open 311 solution called HeyGov! for San Francisco .  HeyGov! is a SaaS (Software as a Service) offering from Microsoft Partner, ISC, that provides a new and engaging way for citizens and governments communicate more effectively in the Web 2.0 era.  

The service requests are captured from device-centric applications or entered by city’s 311 staff into their existing CRM (Customer Relationship Management) system, exposed via an API based on Open 311 standards and visualized via a rich user-interface built with Silverlight 4 and Bing Maps. Built and hosted on the Windows Azure platform, the HeyGov! solution also takes advantage of virtually unlimited storage and processing power of the cloud and provides the ability to quickly address service requests and implement updates even during peak times.”

More details here…

 

Open Government Data and Bing Map Apps

The Bing Maps team is running a King of Bing Maps contest.  Chris Pendleton just published a blog post on Bing Map App Development Resources over on the Bing Maps blog.  If you aren’t familiar with Bing Map Apps, they are mini applications you write in Silverlight that become part of the Silverlight version of Bing Maps.

http://www.bing.com/maps/explore

You have to submit your app for approval.  Approved apps show up in the Map Apps gallery:

image

You bring up the Map Apps gallery by clicking the “MAP APPS” button in the left pane of the Bing Maps UI:

image

Map Apps are a great way to visualize open Government data that has the necessary location information. So far, I haven’t seen many map apps do this other than the Bing Health Maps application:

image

I’m keeping my fingers crossed that some of the apps submitted will use publicly available Government data as the source for Bing Map Apps.  Will you be the person to submit one and win?  I hope so.

Don’t know where to get publicly available Government data? You can find a few over on the producers page of http://odata.org as well as http://data.gov.  Most of the Government OData services on the producers page of http://odata.org use the OGDI starter kit created by my team.  If you are a Government organization that wants to make your data publicly available on the internet through an OData service, then OGDI is a great way to get started.

My Gov2.0 Expo presentation on Bing Maps / OData

You can find my deck here:

http://cid-1f72da7294089597.skydrive.live.com/browse.aspx/Public/Gov2.0Expo

I plan to have my OData + Entity Framework + SQL 2008 spatial demos packaged/cleaned up some time this week.  I will also record a deeper walkthrough of the demo/sample and publish it to Channel 9 some time soon.

Bing Maps Offline

digg_url = “http://blogs.msdn.com/devkeydet/archive/2010/04/19/bing-maps-offline.aspx&#8221;;digg_title = “Bing Maps Offline”;digg_bgcolor = “#555555”;digg_skin = “normal”;http://digg.com/tools/diggthis.jsdigg_url = undefined;digg_title = undefined;digg_bgcolor = undefined;digg_skin = undefined;

Due to my focus on US Government customers, I get asked quite a bit about using Bing Maps in environments without internet access.  My response is always to point them to the Microsoft Bing Maps Server:

http://www.vexcel.com/geospatial/bingmapsserver/index.asp

Recently, a 3 part webcast series was delivered diving fairly deep into the Bing Maps Server.  Details here:

http://www.bing.com/community/blogs/maps/archive/2010/02/22/go-offline-with-the-bing-maps-server-web-cast.aspx

I totally missed this… Web App Toolkits

digg_url = “http://blogs.msdn.com/devkeydet/archive/2010/02/23/i-totally-missed-this-web-app-toolkits.aspx&#8221;;digg_title = “I totally missed this… Web App Toolkits”;digg_bgcolor = “#555555”;digg_skin = “normal”;http://digg.com/tools/diggthis.jsdigg_url = undefined;digg_title = undefined;digg_bgcolor = undefined;digg_skin = undefined;

http://www.microsoft.com/web/downloads/webapptoolkits/

“These FREE Web App Toolkits help you complete common web development tasks and quickly add new features to your apps. Whether it’s Bing Maps integration or adding social capabilities to your site, there’s a toolkit for you. Download and install them today.”

http://blogs.msdn.com/webapptoolkits/

My friend and coworker Vlad made me aware of these.  I’ve been living in the dark.  Apparently, they have been around since September.  I didn’t even know they exist until last week. 

Here’s a dump of all the toolkits available as of today:

Web App Toolkit for "Freemium" Applications
Web App Toolkit for Calendars
Web App Toolkit for Bing Maps
Web App Toolkit for IE8
Web App Toolkit for Bing Search
Web App Toolkit for REST Services
Web App Toolkit for Mobile Web Applications
Web App Toolkit for Template-Driven Email
Web App Toolkit for Making Your Website Social
Web App Toolkit for FAQs

You will find screencasts for most of the toolkits at https://channel9.msdn.com/tags/web+application+toolkit/.  I just checked out the IE8 toolkit screencast.  You basically get ASP.NET controls that simplify the building of Web Slices, Accelerators, and Visual Search Providers for IE8.  You also get a sample site that incorporates the controls. 

I will be watching the rest of the screencasts over the next week or so.  Good Stuff!