Building an entity model faster through the CRM 2011 import process

Here’s a little tip that, as I speak with customers/partners, I’m discovering isn’t well known.  I will show you how to leverage features of the import process to the fact that import process allows you to build your entities much faster in Dynamic CRM 2011.  It is one approach, but I would love to hear any you might have.  The basic process is:

  • Create entity
  • Create option set fields
  • Create relationships where necessary
  • Import data and create the rest of the fields along the way

To get this to work without prefix side effects, make sure you follow the instructions in my Ensuring you are using your prefix everywhere post first.  Let’s start with a very simple entity model:

 

image

I know, bad example, but I wasn’t feeling creative.  To build this out we’re first going to create the Parent and Child entities:

image

image

NOTE: As a personal practice, I always clear out everything below the Options for Entity section of the form.  You can always go back and add features later, but you can’t undo things that have the + sign next to them. 

Technically, we could create the entity as part of the import process, but I prefer creating it explicitly because I have full control of the creation options.  For Fields of type Option Set, you should create them in the entity ahead of time.  In this example we are going to create a Blood Type field and create a new reusable option set with the values in the picture: 

image

image

Technically, you can create option set fields during the import process, but they won’t be reusable.  Therefore I always create them ahead of time.   For other field types, we’ll create them during import.  Now, let’s go ahead and create a 1:N relationship between the Parent and Child entity:

image

image

Technically, we could create the relationship during import, but I prefer creating it explicitly because I have full control of the options.  Next, create two csv files that look like:

 

Parent.csv

Name,Date Of Birth,Blood Type

Parent1,11/05/2000,O+

Parent2,11/05/2000,A+

Parent3,11/05/2000,B-

 

Child.csv

Name,Date Of Birth,Blood Type,Parent

Kid1,11/05/2000,O+,Parent1

Kid2,11/05/2000,A+,Parent2

Kid3,11/05/2000,B-,Parent3

 

 

Now import Parent.csv:

image

Browse to the file and accept the defaults until you get to this screen:

image

As you can see Blood Type is already mapped to the option set we created.  Map the Date Of Birth field:

image

Keep clicking the Next button until you get to the end and click the Submit button.  The Import Data Wizard will create the remaining field and import the data.  Now let’s get the Child entity set for import.  Go ahead and create the Blood Type option set field for the Child entity like we did for the Parent, but this time reuse the existing option set.  Now import the Child.csv file.  You will have to create a new field for Date Of Birth, but Blood Type and Parent from the input file will have been auto mapped to the corresponding fields in the entity:

image

That’s it!  I’ve shown a really simple example where we really only automated the creation of one field per entity.  I did that to keep the walkthrough simple.  I also walked through why I choose to still manually create option set fields and relationships.  If you apply this technique to creating entities with say 10+ fields, you really start to appreciate the productivity gains of automating the field creation process.  HTH

@devkeydet

Ensuring you are using your prefix everywhere

With Dynamics CRM 2011, there are many ways to get the same task done when you are customizing your solution.  However, what prefix is used depends on where you came from.  Let’s take the scenario of creating an entity.  If you are working in the Default Solution, then you probably have a prefix of new_ unless you have already changed the prefix of the Default Publisher:

image

image

If you are working in a solution you’ve created, but using the Default Publisher, then you probably still have a new_ prefix.  If you are working in a solution you’ve created, using a publisher you’ve created, and created the new entity in from within that solution, then you will have the prefix of the publisher you created:

image

image

However, when you customize anything through the Customize tab in the ribbon:

image

…you will be brought to the Default Solution, which means your prefix will be whatever is in the Default Publisher.  To get your prefix to show up when you use the Customize tab, you need to update the prefix of the Default publisher to match the prefix of your publisher:

image

image

By doing so, your prefix will be the default everywhere you try to customize the system.

@devkeydet

Upgrading to IE9 without breaking incompatible apps

As many of you know, the CRM 2011 UI leverages more “in browser” processing.  Because of this, CRM 2011 will perform better when using the latest version of Internet Explorer (currently IE9) because of IE’s advances in performance through hardware acceleration.  Simply put, CRM 2011 runs best on IE9.  In speaking with many customers about this topic, I typically get the following response:

“We can’t upgrade to IE9 because we have apps that only work in IE7/8. We can’t afford to invest in updating those applications just so we can run CRM on IE9.”

There’s a solution. The reality is that you CAN tell IE9 to render your app in IE7/8 compatibility WITHOUT changing your web app code. Unfortunately, I’ve found that most people just don’t realize it is possible.  The Defining Document Compatibility article explains how to make it so that IE9 will render as if it were a previous version IE.  What most people overlook is that you can do this without modifying a single line of your applications code.  How?  Jump to the Configuring Web Servers to Specify Default Compatibility Modes section of the previous link.  It explains how.  Hopefully this helps in making the decision to upgrade the workstations in your environment to IE9 so that you can take advantage of it’s advances in performance, etc. without breaking your incompatible web apps.

@devkeydet

Building a CRM 2011 dev box using a Windows Azure Virtual Machine

UPDATE: The instructions were a bit out of order.  Thanks for @DynamicsCRM_EDU for the heads up!  I fixed it.  Please let me know if you find anything else that needs clarification.  I write these things late in the night most of the timeWinking smile.

I am a big fan of the One Organization per Developer approach to team development with Dynamics CRM 2011.  Additionally, I think every CRM developer should have their own isolated development environment (just like most traditional development environments).  Some of the benefits:

  • Not stepping on other peoples work (or worse someone else hosing my work)
  • No one else’s publishing action slowing me down
  • Can attach debuggers for plugin / workflow activity code
  • Sometimes you need to spin up/tear down CRM orgs for prototyping, fixing things that are hard to undo once you’ve done them, etc.
  • Like most development, you churn a bit until you are ready to check your changes in for others to use

I’ll be blogging more about source control stuff, but have a look at this post for a peek at a tool that’s coming to make source control of non code customizations easier:

http://dkdt.me/LuupSo

Frankly, the problem with building out a CRM dev box is that it requires capable hardware to run CRM and Visual Studio and SharePoint and SQL and Reporting Services and…you get the point.  Sometimes, acquiring capable hardware is not something you have control over Sad smile.  Therefore, people end up using less than adequate developer environments for CRM development.  While CRM Online is great for production and staging environments, it just doesn’t offer the developer productivity that self contained, all tools on one box, isolated CRM environment do.  Windows Azure’s new Infrastructure as a Service (IAAS) Virtual Machines (VMs) to the rescue!  I just wrapped up building my first CRM dev box on Windows Azure.  Here are my notes on getting it working for those who want to try it.

DISCLAIMER: CRM 2011 isn’t support for production deployment in Windows Azure VMs just yet, but works just fine in a single VM developer install.

First, I followed the instructions below up to the Open the virtual machine using Remote Desktop and complete setup section.

http://dkdt.me/NTRiPa

I chose a 4 core, 7 GB RAM VM because that’s what I run for the VM on my Lenovo 510 laptop.  You can probably get away with less, but this config has served me well OnPremises, so I went with it.  Once I was able to establish a Remote Desktop connection, I immediately went into Server Manger and clicked the Configure IE ESC link and turned it off so I could surf the web without warnings:

image

Then, I enabled Microsoft Update:

http://dkdt.me/LuusgN

I went ahead and ran Microsoft Update and installed everything. 

https://www.windowsazure.com/en-us/manage/windows/how-to-guides/attach-a-disk/

The Windows Azure VM we picked has two drives (C: and D:).  The C: drive only has 30 GB.  The D: drive isn’t durable, so don’t put anything on it that you expect to still be there if your VM gets rebooted, moved, etc.  Therefore, you are going to want to add another drive for your databases.  I added a 30 GB drive:

image

image

You will also need to initialize the disk in the VM:

http://dkdt.me/MlAOOH

Make sure you configure SQL Server to use the new drive as the default location for data and log files:

http://dkdt.me/LuupSp

Per this blog post:

http://dkdt.me/LuusgO

…you don’t want write caching on the disk your databases are on.  The good news is that the data disk we just created has write caching off by default.  The pre-configured VM I picked from Windows Azure already has SQL Server 2012 installed, including Reporting Services.  From there, I followed the instructions in Girish’s  blog to setup a CRM 2011 VM:

http://dkdt.me/LuupSr

Of course, you can skip the Windows/SQL Server/Hyper-V stuff since we already have a VM running in Windows Azure with Windows/SQL pre-installed.  You can skip to about the 19:30 point in the video to get started.  At about the 23:00 point, you’ll need to reboot.  The next step in the video is to install SQL Server.  While you don’t need to because it’s already installed in the VM, you do need to configure Reporting Services (accept all defaults, unless I call a change out otherwise):

image

image

In the picture above, make sure you run under a domain account.  Otherwise, the CRM Reporting Extensions will not pass the environment check.

image

image

image

    

Since we don’t need to install SQL Server, you can skip to the 34:15 point.  At about the 38:10 point, you will be told to go ahead and Run the SharePoint Products Configuration Wizard.  DON’T!  Instead, install Service Pack 1 for SharePoint Foundation:

http://dkdt.me/LuusgP

Once you’ve done that, then pick up where the video left off by manually running the SharePoint Products Configuration Wizard:

image

When you get to the point of running the CRM 2011 installer, grab the bits from:

http://dkdt.me/Luuq8E

This update has Update Rollup 6 slipstreamed so it installs perfectly fine on SQL Server 2012.  You can use your MSDN/TechNet keys to register CRM 2011 if you want to use the VM for longer than 90 days.  Otherwise, just use the trial key from the link above.  The last thing I did was run Microsoft Update one more time and accept all the updates (which include Update Rollup 8).  Cleaning up any of the installer files you downloaded isn’t a bad idea either.

At this point, you have a fully functional CRM 2011 dev box which you can remote into and work within in isolation.  Of course, you’ll probably want to have a look at My CRM Dev VM post which talks about what else I like to install into a CRM Dev VM.  If you need source control for this VM, then you should try http://dkdt.me/Luuq8G.  If you really need to gain access to resources from the VM that are behind your corporate firewall (like an existing TFS server), there’s always Windows Azure Virtual Network

Hopefully this helps any of you out there who had the same idea I had!

@devkeydet

UPDATED SAMPLE: CRM 2011 Metro style OData helper

I’ve updated the sample I started here by implementing Create Update and Delete methods.  The library is still not complete or well tested, but it is at least a head start to building a CRUD capable Windows 8 Metro style application using the CRM 2011 OData (aka Organization Data) service.  Both the helper web resource and the Windows 8 Metro style source code have been updated.  The Visual Studio solution now has a little sample Metro style app to quickly test out the functionality of the library.

http://sdrv.ms/NIiMY2

To get the sample to work, you will need to import the updated CRM solution package which updates the helper web resource. Also, make sure you change the CRM organization url in the sample app.  You can still follow the instructions from the first post since there are no breaking changes in the sample, just new functionality.

@devkeydet

CRM 2011, OData and datajs

UPDATE: FALSE ALARM about batch updates.  Turns out the CRM 2011 OData service doesn’t support batch updatesSad smile.  I just assumed it did because I’ve written .NET/Silverlight clients against the CRM 2011 OData service using a derived DataServiceContext.  DataServiceContext based contexts using the “unit of work” pattern through SaveChanges()/BeginSaveChangesc() and they’ve always “just worked.”  I just confirmed that Batch isn’t the default SaveChangesOption so all this was predicated on a bad assumption on my partSad smile.  Nothing to see here.

What people often call the CRM 2011 REST Service or Organization Data Service is an OData service.  If you go to the libraries section of the OData site and select JavaScript, you will be linked to the datajs CodePlex site.  So what about the CRM SDK OData samples (SDK.REST.js and SDK.JQuery.js)?  It’s probably one of those “six in one hand, half dozen in the other” situations in terms of the shared capabilities of the libraries.  Which API you like better probably boils down to a style preference.  There are some differences in capabilities as well.  The CRM SDK libraries have explicit functions to associate / disassociate records.  One of the things the CRM SDK sample libraries don’t offer is a facility to do batch updates, but datajs does.  This comes in really handy when you want to do a bunch of work client side to some data and send it all at once.  Have a look at the OData Code Snippets.  They also have a full OData API reference.

@devkeydet

Adxstudio Productivity Pack

http://dkdt.me/LozNSi

Run, don’t walk, and go grab this CRM dev essential.  A few of my blog posts are irrelevant (or at least much less effort) once you’ve installed this free set of enhancements to Dynamics CRM 2011.  From their website:

Adxstudio Productivity Pack is a suite of customizations for Microsoft Dynamics CRM® 2011, useful across a wide rage of CRM applications. It includes the following enhancements:

  • Rich Text Editor (TinyMCE)
  • Auto Numbering
  • Bing Maps Geolocation Lookup
  • Date/Time Parsing
  • Boolean Parsing
  • General Application Settings

This product is provided to the Adxstudio community at no cost.

@devkeydet

Eliminating manual device registration

If you are connecting to CRM Online with a Live ID, you need to register a Device ID and Device Password when using the organization service from an outside caller.  I walk through this in my Connecting to CRM Online from an outside caller post.  Thanks to some internal discussions with folks on the SDK team, I discovered a simpler way that doesn’t require manually registering and putting the DeviceId /DevicePassword in the config file.  First you’ll have to download and use the Helper Code: DeviceIdManager Class from the SDK.  In addition to the references from the previous post, you’ll need to add references to System.Security.dll and System.ServiceModel.dll to get it to build.  Once you have done that, you can eliminate the DeviceId and DevicePassword settings from the connection string:

<connectionStrings>

    <add name="CrmOnline" connectionString="Url=[YOUR_CRM_ORG_URL]; Username=[YOUR_LIVE_ID]; Password=[YOUR_PASSWORD];"/>

    </connectionStrings>

You can now connect to CRM Online like so:

var connection = new CrmConnection("CrmOnline");

connection.DeviceCredentials = DeviceIdManager.LoadOrRegisterDevice();

var context = new XrmServiceContext(connection);

DeviceIdManager.LoadOrRegisterDevice() automates the manual steps from the Connecting to CRM Online from an outside caller post.  I’m pretty sure it requires full trust, but haven’t taken the time to confirm.  If you have, leave me a comment to confirm/correct.

@devkeydet

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

Connecting to CRM Online from an outside caller

UPDATE: Thanks to some internal discussions with folks on the SDK team, I discovered a simpler way that doesn’t require manually putting the deviceid/devicepassword in the config file.  I decided to leave this post as-is for two reasons.  First, because the easier way requires full trust and won’t work for you if your hoster or IT department requires partial trust.  Second, so people understand what the easier way automates.  I’ve updated this post with a “you can skip this step” explanation. 

Scenario:

“I want to make calls to CRM from an ASP.NET page, .NET middle tier code, etc.  How do I do it with CRM Online?”

Simple answer right?  Maybe for the persistent developer who is happy to reverse engineer the steps from the SDK documentation.  However, I have to say the SDK doesn’t spell it out for you step by step. 

The following resources seem to help on the surface:

Walkthrough: Build a Web Application That Connects to Microsoft Dynamics CRM 2011 Using Developer Extensions

ASP.NET Web Forms and Data Binding

Create Early Bound Entity Classes with the Code Generation Tool (CrmSvcUtil.exe)

Simplified Connection to Microsoft Dynamics CRM

Sample: Authenticate Users with Microsoft Dynamics CRM Web Services

But there’s a little CRM Online nuance that sort of gets lost in the shuffle unless you connect the dots across some of these articles.  CRM Online, when using a Live ID, requires that you use a DeviceId and DevicePassword when connecting.  While the Simplified Connection to Microsoft Dynamics CRM article does explain this, it leaves it up to the reader to hunt down the whole Device ID setup process.  This post is my attempt to connect the dots in a step by step walkthrough of building a simple ASP.NET Web Forms page that connects to CRM Online using a Live ID.

Let’s start by creating a new ASP.NET Empty Web Application and call it CrmOnlineConnectedWebApp:

image

Add the following references from the SDKbin folder.

  • Microsoft.Xrm.Client.dll
  • Microsoft.Xrm.Sdk.dll

Add the following references from .NET.

  • System.Data.Services.dll
  • System.Data.Services.Client.dll
  • System.Runtime.Serialization.dll

DEVICE REGISTRATION

You can skip this next step and jump to CREATE EARLY BOUND TYPES if you follow the enhancement in the Eliminating manual device registration post.

There’s a section titled “To Generate your individual device ID and password” in the Create Early Bound Entity Classes with the Code Generation Tool (CrmSvcUtil.exe), but it’s easy to miss.  Follow the instructions from that section:

  • Open and build the DeviceRegistration project: SDKToolsDeviceRegistrationDeviceRegistration.csproj.
  • Run the executable file from the command line. To register your device, set the /operation parameter to Register.
  • C:deviceregistration.exe /operation:Register
  • Copy the displayed device ID and password values and use them as the deviceid and devicepassword parameter values when you run the CrmSvcUtil tool.

The easiest way to copy the Device ID and the Device Password from the console is to right-click and select all:

image

…right-click again (this will copy the contents) and paste into notepad.  Then you can copy the actual values. 

CREATE EARLY BOUND TYPES

Let’s create some early bound types as described in step one of Walkthrough: Build a Web Application That Connects to Microsoft Dynamics CRM 2011 Using Developer Extensions.  However, we need to remove the domain flag:

CrmSvcUtil.exe /codeCustomization:"Microsoft.Xrm.Client.CodeGeneration.CodeCustomization, Microsoft.Xrm.Client.CodeGeneration" /out:Xrm.cs /url:[YOUR_CRM_ROOT]/XRMServices/2011/Organization.svc /username:[YOUR_USERNAME] /password:[YOUR_PASSWORD] /namespace:Xrm /serviceContextName:XrmServiceContext

Now add the generated file to your Visual Studio project.  Now is a good time to build.  If the build fails, you are likely missing an assembly reference.  Now let’s add a Web Form called Default.aspx:

image

Add a GridView to the markup of Default.aspx:

image

The next steps are slightly different if you decided to use the approach in my Eliminating manual device registration post.  Switch to Default.aspx.cs and paste the following code into the Page_Load handler:

var connection = new CrmConnection("CrmOnline");

var context = new XrmServiceContext(connection);

 

var query = from a in context.AccountSet

            select new

            {

                a.Name,

                a.AccountNumber,

                a.Address1_City

            };

 

GridView1.DataSource = query.ToList();

GridView1.DataBind();

The final step is to put the CrmOnline connection string into your web.config:

<connectionStrings>

    <add name="CrmOnline" connectionString="Url=[YOUR_CRM_ORG_URL]; Username=[YOUR_LIVE_ID]; Password=[YOUR_PASSWORD]; DeviceId=[DEVICE_ID_FROM_CONSOLE]; DevicePassword=[DEVICE_PWD_FROM_CONSOLE]"/>

    </connectionStrings>

You should be able to run the app now and see data in the GridView.

@devkeydet