Dynamics CRM 4.0 + Virtual Earth

I was asked by a colleague from work to integrate Virtual Earth into CRM 4.0 for a pre-sales client Demo.

The integration points are the Contact and the Service Activity entities. I’ll go through these two cases and list out the steps how I achieved them in a reasonably short period of time.

Contact – Pushpin an address

It’s required when the address tab is clicked on in the Contact main form, a virtual earth map is displayed within the same tab with a pushpin pining at the address.

1. Create a web site project to host the Virtual Earth maps.

I used Visual Studio 2008, because it allows JavaScript debugging and I’ll need to write some code behind logic for the second example.

image

2. Code up some JavaScript against the Virtual Earth API on this page to load an address.

This is the fun part – interacting with Virtual Earth API in JavaScript. And guess what? Life only gets better, the Virtual Earth folks have put together a demo site, it presents us with most common cases on how one would make use of Virtual Earth AND it provides us with the source code!!! All that I needed to do is, find the most suitable case on the demo site and copy paste the JavaScript code and tweak it from there. 🙂

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

   2: 

   3: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

   4:                         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

   5: <html xmlns="http://www.w3.org/1999/xhtml">

   6: <head><title></title>

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

   8:     <script type="text/jscript"

   9:             src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=5"></script>
   1: 

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

   3: 

   4: var map = null;

   5: var latitude = null;

   6: var longitude = null;

   7: var address = "";

   8: // Find address through virtual earth

   9: function GetMap()

  10: {

  11:     address = '51 Failkirk Ave, Seatoun, Wellington, New Zealand';

  12:     map = new VEMap('myMap');

  13:     map.LoadMap();

  14:     map.Find(null,address,null,null,0,10,true,true,true,true,FindCallBack);

  15: }

  16: 

  17: function FindCallBack(shapeLayer, results, positions, moreResults, e)

  18: {

  19:     if(positions != null && positions.length > 0)

  20:     {

  21:         PlacePushPin(positions[0].LatLong.Latitude,positions[0].LatLong.Longitude);

  22:         latitude = positions[0].LatLong.Latitude;

  23:         longitude = positions[0].LatLong.Longitude;

  24:     }

  25: }

  26: 

  27: function PlacePushPin(lat, lon)

  28: {

  29:     latlong = new VELatLong(lat,lon);

  30:     customerPushPin = new VEShape(VEShapeType.Pushpin,latlong);

  31:     customerPushPin.SetTitle(address);

  32:     map.AddShape(customerPushPin);

  33:     map.SetCenterAndZoom(latlong,15);

  34: }

  35:

</script>

  10: 

  11: </head>

  12: <body onload="GetMap();">

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

  14: </body>

  15: </html>

Note: on line 19,

address = ’51 Failkirk Ave, Seatoun, Wellington, New Zealand’;

I’ve hard coded this address to load in the map. Then we can dynamically read the address fields from the contact form to be loaded on the map.

3. Host this Website in IIS where your CRM installation resides.

 Remember to change it to ASP.NET 2 runtime as it defaults to 1.1.

image

Then view the Location.aspx page in a web browser.

image

4. IFrame this page in Contact entity.

In CRM, Settings -> Customization -> Customize Entities -> Contact -> Forms and Views -> Form.

First add a Tab called Address then move the Address Section from General Tab to Address Tab. Finally, add an IFrame to this new section. Make sure that the “Restrict cross-frame scripting” is NOT checked, as we’ll need to access the address fields from inside of our IFRAME.

image

To test that the addition of our IFRAME worked, simply load a read-only from Preview option in Contact form.

image

You should see a map loaded with the hard coded address.

image

5. Last step, is to read the address fields from the Contact form, Address Tab and pass it on to the map to load.

   1: function GetMap()

   2: {

   3:     //Get adress from CRM

   4:     var addressLine1 = parent.document.forms[0].all.address1_line1.DataValue;

   5:     var city = parent.document.forms[0].all.address1_city.DataValue;

   6:     var stateOrProvince = parent.document.forms[0].all.address1_stateorprovince.DataValue;

   7:     var country = parent.document.forms[0].all.address1_country.DataValue;

   8: 

   9:     //Compose customer address

  10:     address = addressLine1 + ", " + city + ", " + stateOrProvince + ", " + country;

  11:     //address = '51 Failkirk Ave, Seatoun, Wellington, New Zealand';

  12:     map = new VEMap('myMap');

  13:     map.LoadMap();

  14:     map.Find(null,address,null,null,0,10,true,true,true,true,FindCallBack);

  15: }

Now, publish Contact entity and test it with an contact that has the address fields set.

image

There you go, pretty cool huh! 🙂

Service Activity – Routing from A to B

This is to work out the routes from point A to B and return the mileage of this route for reimbursement purposes. The address of the owner of the Service Activity is taken as a source address and the address of the Customer (a Contact in this case) is the destination address. Again, Virtual Earth Dev Demo site provides us with most of the source code here.

The steps are easy to follow,

1. Create a new aspx page in the same web site we just created from the Contact example in IIS, call it Routes.aspx (Or any other name you might fancy).

2. Grab some code from this demo site, hard code a source and destination addresses on the page at this stage before involving CRM at all.

3. Serve this page up in IIS to test it out.

image

4. Add this page to Service Activity as an IFrame. It’s your choice where on the form to add it, a new section on a new tab, a new section on an existing tab, doesn’t matter.

Make sure that you check the query string option and uncheck the cross-frame scripting option.

image

5. Right, we are almost there apart from the map only returns a route from two hard coded points on the page. So, we need to get source and destination addresses from CRM for this Service Activity record. This is the part that’s different from the Contact example. For Contact, the IFrame lives on the same page as the address fields that the map needs to address. So we could do with JavaScript on the page to directly read the address off the fields and feed it through Virtual Earth to serve up an addressed map in the IFrame. However for Service Activity, we don’t have all the address information on the form, so we’ll need to code up some code behind to retrieve the address via CRM WebService and feed that to Virtual Earth to work out a route for us.

   1: protected void Page_Load(object sender, EventArgs e)
   2: {
   3:     // Read the Service Activity ID from query string
   4:     String saId = this.Request.QueryString["id"];
   5:         
   6:     if (!String.IsNullOrEmpty(saId))
   7:     {
   8:         // Retrieve Service Activity instance from CRM
   9:         Guid serviceAppointmentId = new Guid(saId);
  10:         CrmAuthenticationToken token = new CrmAuthenticationToken();
  11:         token.OrganizationName = "MicrosoftCRM";
  12:         CrmService.CrmService crmService = new CrmService.CrmService();
  13:         crmService.CrmAuthenticationTokenValue = token;
  14:         crmService.Credentials = System.Net.CredentialCache.DefaultCredentials;
  15:         serviceappointment apmt = crmService.Retrieve("serviceappointment", serviceAppointmentId, new AllColumns()) as CrmService.serviceappointment;
  16:  
  17:         // Get the address for the record owner and assign it to a label on the page
  18:         systemuser user = crmService.Retrieve("systemuser", apmt.ownerid.Value, new AllColumns()) as CrmService.systemuser;
  19:         string userAddress = user.address1_line1 + ", " + user.address1_city + ", " + user.address1_stateorprovince + ", " + user.address1_country;
  20:         this.lblSource.InnerHtml = userAddress;
  21:  
  22:         // Get the address for the record customer and assign it to a label on the page
  23:         contact contact = crmService.Retrieve("contact", apmt.customers[0].partyid.Value, new AllColumns()) as CrmService.contact;
  24:         string contactAddress = contact.address1_line1 + ", " + contact.address1_city + ", " + contact.address1_stateorprovince + ", " + contact.address1_country;
  25:         this.lblDestination.InnerHtml = contactAddress;
  26:     }
  27: }
  28:  

6. Use preview form to test it, publish it.

image

There you go, away laughing 🙂

If you want a copy of the source code, here is a link.

Thanks to Microsoft Live SkyDrive.

Advertisements

19 responses to this post.

  1. Posted by tim khoo on June 24, 2008 at 1:12 am

    good stuff.

    Reply

  2. Posted by Jessé on July 5, 2008 at 6:22 am

    Hi ,

    I tryed this add-on , the location part works fine even added the postal code to the fields he uses to get the address. im havinf trouble with the routes part because he gives me The request failed with HTTP status 401: Unauthorized.

    On this set of code.
    Line 293: object[] results = this.Invoke(“Retrieve”, new object[] {
    Line 294: entityName,
    Line 295: id,

    Funny thing is that if there is that it works on the form preview and you cant put any adress on preview …

    Strange behavior , is this because im running CRM 4.0 under Windows 2008 and IIS 7 ?

    Reply

  3. Hi Jessé,

    Seems like the code is failing to authenticate against CRM WebService? There is a whole heap of articles out there on troubleshooting this problem…

    In my code, I’m using windows authentication by assigning DefaultCredentials to CrmService. This is the easiest if your calling code is on the same box as your Crm instance.

    A few points to check that I can remember of the top of my head;
    1. In IIS, you shouldn’t allow anonymous access if you are using windows authentication.
    2. If you are using impersonation, make sure the user you are impersonating lives in the PrivUserGroup in AD.
    3. Make sure the application pool you are running your iFrame website in is run under Network Service…

    Failing all that, depends on your intended deployment configuration, google CRM 4.0 HTTP 401.

    When I tested with preview, I had hardcoded addresses on the web page, the goal of that is to isolating out interaction with CRM webservice and just make sure that the virtual earth and iFrame are working.

    Hope that helps,

    Cheers,

    Sun

    Reply

  4. Posted by Robert Peledie on August 5, 2008 at 1:23 am

    Hi
    This really is a great blog. I got mine working in about an hour with your instructions.
    I only followed the first part, as we only need to see a pushpin showing a contact or account address.
    How easy (or not) would it be to show other pushpins in the map? For example, we want to show the parent pushpin in red but also show contacts in the same area in another colour?
    Rob.

    Reply

    • Yep, absolutely. There is javascript function PlacePushPin(lat, lon) you can call at line 27, which is just constructing a new VEShape with VEShapeType.Pushpin type. So you could call this method with latitude and longitude to assign a drop a new pin.

      If you take a look at this VEShape class it has a method VEShape.SetFillColor which you can pass in a VEColor object and sets its colour too.

      http://msdn.microsoft.com/en-us/library/bb412486.aspx

      Reply

  5. Posted by Ricardo on September 25, 2008 at 9:13 pm

    Hi! just to say nice blog!
    Just one more thing:
    could you sahre de js code that makes VE fit IFRAME and resize it?

    thks

    Reply

  6. Posted by dave on January 20, 2009 at 7:51 pm

    very cool! thanks for this post!

    Reply

  7. Posted by mmattson on May 15, 2009 at 2:45 pm

    Great post. Got the location working fast. Having trouble with the CrmService. Getting an error:

    Compiler Error Message: CS0246: The type or namespace name ‘CrmService’ could not be found (are you missing a using directive or an assembly reference?)

    Source Error:

    Line 9: using System.Web.UI.WebControls;
    Line 10: using System.Web.UI.WebControls.WebParts;
    Line 11: using CrmService;
    Line 12:
    Line 13: public partial class Routes : System.Web.UI.Page

    Source File: c:\Inetpub\wwwroot\crmMap\Routes.aspx.cs Line: 11

    I changed the web.config to point to correct folder. Installed on the CRM server, etc.

    Reply

    • @mmattson, without taking a look at the exact code…

      When you add CRM WebService reference to your project, did you name it “CrmService”? It’s looking for that specific namespace at line 11 and apparently it couldn’t find it. Some people name it CrmSdk, it’s rather a person preference.

      Reply

  8. мне кажется: отлично!!

    Reply

  9. Posted by Momo on November 10, 2011 at 4:46 am

    Cool Post!
    Is it possible to use the picture in the IFrame for mailmerge?

    Reply

  10. Have you ever thought about including a little bit more than just your articles?
    I mean, what you say is fundamental and all. However just imagine
    if you added some great graphics or video clips to give your posts more, “pop”!
    Your content is excellent but with images and videos, this website could definitely be one of the most beneficial in its
    field. Terrific blog!

    Reply

  11. Here are some of the most basic marketing strategies.
    This is the advantage of appealing to consumers in a medium that can bring in real money.
    With this marketing database, you will keep
    going. But you do need a do simple things like updating the sales page
    were very convincing for sure. Then after this you offering them a list of favourite links, videos, web
    pages and more at the touch of a button away.

    Reply

  12. Fine way of explaining, and nice article to obtain information regarding my presentation focus, which i am going to deliver in school.

    Reply

  13. In your relationship, do you find yourself laughing over crying.
    For some individuals, it implies climbing the corporate ladder until they achieve the office with
    all the big wide desk (your window office), and then for others it indicates performing a best wishes and being recognized for it.

    Although you are not sleeping with him, he’s probably just using you as somebody to hang out with when he’s
    hardly anything else to do.

    Reply

  14. I was extremely pleased to discover this great site.
    I want to to thank you for ones time for this fantastic read!

    ! I definitely appreciated every bit of it and I have you bookmarked to look at new things in
    your web site.streetdirectory

    Reply

  15. I know this website offers quality based posts and extra data, is
    there any other site which provides these kinds of information in quality?

    Reply

  16. What a material of un-ambiguity and preserveness of precious familiarity on the topic
    of unpredicted emotions.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: