« ISV.Config: Pass the Current View Id and Type with Javascript | Main | BizTalk Adapter for Microsoft Dynamics 4.0 Now Available »
October 07, 2008
Geocoding Addresses in CRM
Every once in a while, you may find yourself with a need to know the latitude and longitude coordinates for an address. The process of looking up these coordinates is called geocoding. There are many good reasons to geocode your addresses, but the big one is you can more readily take advantage of some slick functionality with various mapping applications. This post will demonstrate building a plugin to call a geocoding web service, which will look up the coordinates for the specified address and save them to the latitude and longitude attributes on the address.
This post is quite lengthy, so we'll dive right in after the jump...
Working with Addresses in CRM
If you haven't discovered it yet, the addresses in CRM are pretty tricky. We need to account (no pun intended...) for the two initial addresses that are related to accounts and contacts, as well as the additional addresses that may be added later.Therefore, we want the plugin to fire for each of the following:
- Creation of an account record
- Update of an account record
- Creation of a contact record
- Update of a contact record
- Creation of an address record
- Update of an address record
In case you are wondering, when you create an account in CRM, the events on the customeraddress entity do not get executed. It would be nice if they did (and if they do, I've yet to figure out how to leverage them), but it's not too difficult to work around this.
Virtual Earth's MapPoint Web Service
I'm fascinated by GIS and mapping technology, but how it works is over my head (I'll admit to not really reading into the details, though). We have to consume a third-party web service in order to geocode our address. In this demonstration, we're going to use Microsoft's Virtual Earth MapPoint Web Service. Different services come with different fees associated with how you use the service. There are some free services (some come with a limit to the number of addresses you can geocode in a 24-hour period), so you really want to factor in how you'll be consuming the service. In our case, here, we're geocoding as the account or contact record is created or updated (and the address has changed). For the purposes of this demonstration, though, I'll be using a Virtual Earth Platform Developer Account and accessing the MapPoint staging environment. I find the MapPoint web service to be quite easy to pick up, and there is pretty good sample code and documentation out there to get you moving quickly.
The Nuts and Bolts
I'm going to assume you are already familiar with programming plugins for CRM 4.0. That saves me a lot of time having to explain stuff that I think is beyond the scope of this post.
The first thing we'll do in our Execute() method is to test for the Message Name and Processing Stage. In this case, we want the plugin to fire on the Create and Update messages and we want it to happen before our main transaction. I like the Before stage because it allows you to really easily update attributes on an entity before it gets saved. Let's go ahead and grab our entity:
// test the message (create/update/delete)
if (((context.MessageName == "Create") || (context.MessageName == "Update")) &&
(context.Stage == MessageProcessingStage.BeforeMainOperationOutsideTransaction))
{
// grab the target entity
if (context.InputParameters.Properties.Contains("Target") && context.InputParameters.Properties["Target"] is DynamicEntity)
{
DynamicEntity entity = (DynamicEntity)context.InputParameters.Properties["Target"];
Okay, now we've got our DynamicEntity object, next we need to detect which entity is being created/updated and we can specify the attributes accordingly. For this demo, I'm only going default the attribute names to those that are on the CustomerAddress entity and use the Address1 values if the Account and Contact entities are specified:
string sLine1 = "line1";
string sCity = "city";
string sStateOrProvince = "stateorprovince";
string sPostalCode = "postalcode";
string sLatitude = "latitude";
string sLongitude = "longitude";
if ((entity.Name == "account") || (entity.Name == "contact"))
{
sLine1 = "address1_line1";
sCity = "address1_city";
sStateOrProvince = "address1_stateorprovince";
sPostalCode = "address1_postalcode";
sLatitude = "address1_latitude";
sLongitude = "address1_longitude";
}
Before we get going with the MapPoint web service, we need to build the address string which we'll pass to MapPoint. Just the basic address info will do.
// build address (line1, city, stateorprovince, postalcode)
StringBuilder sbFormattedAddress = new StringBuilder();
if (entity.Properties.Contains(sLine1) && entity.Properties.Contains(sCity) &&
entity.Properties.Contains(sStateOrProvince) && entity.Properties.Contains(sPostalCode))
{
sbFormattedAddress.Append(entity.Properties[sLine1]);
sbFormattedAddress.Append(", " + entity.Properties[sCity]);
sbFormattedAddress.Append(", " + entity.Properties[sStateOrProvince]);
sbFormattedAddress.Append(", " + entity.Properties[sPostalCode]);
}
else
{
// halt plugin execution (not all attributes are available)
return;
}
Note: You may notice that I'm halting the plugin execution if any of my address attributes are missing. We're going to assume that when an account/contact/address record is created, that all of these will be filled in. That's not always the case during an update (ie. a user could only update the line1 attribute, which would cause the others to not show up - they haven't changed!). Ideally, what you'll want to do is implement some sort of data validation to ensure all the attributes are filled out and perform a look up to grab the missing values when you're processing an Update message.
Let's get down to business now and query MapPoint. We're going to initialize the service and set up our request in the form of a FindAddressSpecification. Here is where we pass our address and also we can define which results we want to receive (in our case, just latitude and longitude):
// Initialize MapPoint web service
oFindService = new FindServiceSoap();
oFindService.Credentials = new System.Net.NetworkCredential("username", "password");
oFindService.PreAuthenticate = true;//Define the address specification
Address myAddress = new Address();
myAddress.FormattedAddress = sbFormattedAddress.ToString();// Set the search options
FindOptions myFindOptions = new FindOptions();FindAddressSpecification findAddressSpec = new FindAddressSpecification();
findAddressSpec.InputAddress = myAddress;
findAddressSpec.Options = myFindOptions;
findAddressSpec.DataSourceName = "MapPoint.NA";// Set the ResultMask to include the latitude and longitude values
findAddressSpec.Options.ResultMask = FindResultMask.LatLongFlag;
Now that the request is set up, we want to execute the request using the FindAddress() method of the MapPoint web service. Since we can possibly receive many results back, we'll parse them and only deal with the first result that is returned (which is also the best match):
// Call the MWS FindAddress method
FindResults myFindResults = oFindService.FindAddress(findAddressSpec);// Grab the coordinates of the first result
FindResult bestResult = myFindResults.Results[0];
Finally, we grab the latitude and longitude values and save them to our dynamic entity:
double dLat = bestResult.FoundLocation.LatLong.Latitude;
CrmFloatProperty pLatitude = new CrmFloatProperty(sLatitude, new CrmFloat(dLat));
double dLong = bestResult.FoundLocation.LatLong.Longitude;
CrmFloatProperty pLongitude = new CrmFloatProperty(sLongitude, new CrmFloat(dLong));
if (entity.Properties.Contains(sLatitude))
{
entity.Properties[sLatitude] = pLatitude;
}
else
{
entity.Properties.Add(pLatitude);
}
if (entity.Properties.Contains(sLongitude))
{
entity.Properties[sLongitude] = pLongitude;
}
else
{
entity.Properties.Add(pLongitude);
}
That's it for the good stuff. If you want, you can also implement this same technique by utilizing Javascript on a CRM form, but your best strategy would be to implement a plugin. Like I mentioned earlier, the basic idea is laid out, but you'll really want to implement some additional logic to perform the lookup on the second address for the Account and Contact entities and also some logic to better account for partial address updates.
Enjoy!
Posted by Will Wilson on October 07, 2008 at 08:28 AM | Permalink
TrackBack
TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00e54fb34b6f88330105356903de970c
Listed below are links to weblogs that reference Geocoding Addresses in CRM:
Verify your Comment
Previewing your Comment
Posted by: |
This is only a preview. Your comment has not yet been posted.
The letters and numbers you entered did not match the image. Please try again.
As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.
Having trouble reading this image? View an alternate.




Comments