Monday, 1 October 2012

CRM Field Guide out today!

I started getting tweets thick and fast into my feed today in regards to the CRM Field Guide being released. I first spotted it on Larry Lentz's feed. I snapped up a copy pretty much immediately after spotting it's release, and just when I received the payment verification from Julie Yack www.crmfieldguide.com crashes. That's just about sums up how popular I expect this book to be! I noticed that crmfieldguide.com (without the www) still seems to be working, so you might be able to grab a copy from that link.

This book, in my opinion, is going to be one of the most popular CRM 2011 books released for the Microsoft Dynamics CRM platform bar none. It's a book written by MVPs in the CRM community for the CRM community and should become a staple part of any CRM 2011 developers/integrators/deployers diet. It looks like it will deal with just about everything from admin to development and pretty much everything in between that you can think about.

Anyway, I'm now left in a limbo of anticipation, because I cannot get onto the darn download page to get my new book, shucks! I may have to just be patient and wait until tomorrow! I sure am looking forward to that read though.

Good luck with the release guys!

Capitalize first letter of a text field in Dynamics CRM

This was asked on a the Dynamics community forum recently, and due to my struggles in posting Javascript as a response I ended up blogging about it instead!

So how do we do this? Firstly, we'll need a webresource that contains our javascript. Create a .JS file (in my example I'm going to call it test.js) and add the following function to it:

function firstToCaps(obj){
    var fieldVal = obj.getEventSource().getValue();

    var newFieldVal = fieldVal.charAt(0).toUpperCase() + fieldVal.slice(1);
    obj.getEventSource().setValue(newFieldVal);
}

Once you have your JS file saved just add that as a new webresource to your solution.

Next, let's discuss how you hook into an onchange event of a field on a form, because this is what we need to do in order to solve this problem. For this example I'm going to use the account form and the name field.

In our solution let's add the account entity to it. Open up the main form of the account and check out the Form Properties:


You'll notice I have added a library called "crm_tests.js" to the list of Form Libraries. You'll also notice that in the Event Handlers I have selected "Account Name" and "OnChange". This allows me to hook into this event and call a function when required. In my case I have added a call to my function "firstToCaps":


The important part of this event is that I have checked the option to pass the execution context as the first parameter. This gives me access to the field that fired the event.

So, let's go back to that function and take a quick look over what it does:

function firstToCaps(obj){
    var fieldVal = obj.getEventSource().getValue();

    var newFieldVal = fieldVal.charAt(0).toUpperCase() + fieldVal.slice(1);
    obj.getEventSource().setValue(newFieldVal);
}


You've probably put 2 and 2 together at this stage and can see I'm using the execution context "obj" and its function getEventSource to allow me to get and set the value of the calling field.

Save and publish all of that and watch your field auto capitalize!



Friday, 17 August 2012

IE Crashes when I close a CRM window

This is probably one of the first things you'll come across when you've upgraded to Internet Exporer 9 and are closing Microsoft Dynamics CRM pages before they've fully loaded. And it's dreadfully annoying!

Microsoft have noticed this problem and published a work around. Which is a simple registry change:



  1. Point to the Start menu, click Run, and then type regedit.
  2. Navigate to HKEY_Current_User\Software\Microsoft\Internet Explorer\Main
  3. Right-click Main, and then select new DWORD (32-bit) Value
  4. Enter HangRecovery as the name.
  5. The default value will be 0.


For full details on the problem see here: http://support.microsoft.com/kb/2698959

Tuesday, 31 July 2012

Advanced find and many-to-many relationships

Here's one that caught me out for a little while today. Earlier today I set up a new entity called Media Code with a many-to-many relationship with Sales Literature. I put in my required settings, saved it, and then went about my daily business.

Later on in the day I want to create a Fetch Query to locate any Sales Literature that is linked to a particular  Media Code. So, in typical developer mode and having forgotten everything I did earlier in the day, I open up an advanced find, select Sales Literature from the list, look to filter by Media Code... but low and behold there's no Media Code option in the related list?!? To double check I haven't gone insane I change my advanced find and select Media Code from the list of entities... and to my complete amazement Sales Literature is there right in front of my eyes, as bold as brass, under related entities.

What on earth is going on at all I hear you ask! Well, today I learnt something new about Microsoft Dynamics CRM and it was this here:



I did not know until today that this option also blocks it from the advanced find. Shock horror!

Hopefully this saves somebody else 30 minutes of their day pulling hair out of their head...

Thursday, 19 July 2012

Hey dynamics, where's my cross browser support?

So, we've hit Q3 and I must have had my head buried in the sand, because I never noticed until recently that the cross browser support for Microsoft Dynamics CRM 2011 never hit in Q2. So what happened? Where is it?!?

Looks like MS hit some complications and this got pulled from the Q2 release. Thankfully, it's still on the agenda but has been delayed until Q4:

https://community.dynamics.com/product/crm/crmnontechnical/b/crmconnection/archive/2012/07/06/q2-2012-service-update-new-delivery-schedule.aspx

Must keep my eye a little closer to the ball!

Wednesday, 18 July 2012

Importing Marketing List Members... the fast way

A common bane in many developers lives is the inability to import marketing list members directly using an asynchronous import. Even in Microsoft Dynamics CRM 2011 this is still an issue. If you're reading this you've probably come across this too. The most common solution I've seen (and read) for this is importing one by one. A piece of code I came across recently did exactly this and (a stripped down version...) looked something like the following:


while (true)
{
    var record = MarketingListCSVFile.GetNextCSVRecord();

    if (record == null)
    {
        // eof - no more records to process
        break;
    }

    Entity contact = GetContact(record[ContactField]);
    
 AddListMembersListRequest request = new AddListMembersListRequest { ListId = MarketingList.Id, MemberIds = new Guid[] { contact.Id } };
    AddListMembersListResponse response = (AddListMembersListResponse)this.Service.Execute(request);
    // ...
}

AttachMarketingListToCampaign(MarketingList, MarketingListCSVFile.CampaignActivityCode);


... and so on. In practice this works, but there's a lot wrong with it. Aside from the fact that it's going to be slow due to hitting the server every time for each member, it's going to hammer that server until it get's all those members imported.

A better, and quite possibly the fastest way to get this imported is by using a custom "holding" entity, drive a Dynamic Marketing List off this entity and then convert this Dynamic Marketing List to a static marketing list (if required).


The Holding Entity 

Firstly, we need to create an entity that has all the required fields on it that will allow us to drive a query off. The only requirement here is that it has a relationship to the Cotact entity, because a query for a Dynamic Marketing list must return a list of contacts. In my case I needed to link these contacts back to a campaign activity, which involved importing a code to give me the ability to do this. All in all my new custom entity contains the following:

  • Contact (Lookup to contact) 
  • Campaign Activity Code (string) 


The query

Next we need to make sure we can drive the correct query off this. Pop open an advanced find and select the contact entity type from the list. My query looked like this:


This will bring back all the contacts required for my marketing list. All good so far.


The import

Next up, how do we kick off an asynchronous import via the code? If you haven't done this before it's worthwhile having a read of the following links first. Once you've digested all, or at least the applicable parts of this you're ready to write your import:

Sample: Import Data Using Complex Data Map
Data Import Entities


You'll notice another problem I ran into when researching this, and it's how everyone has gone dog crazy on early bound objects. I'm not saying they're bad, but I'm not as big a fan as most. Or maybe I'm just a freak for late bound objects. Mainly because it saves me the pain of:
  1. Making sure everyone has the latest and greatest definitions in their project
  2. Waiting for some other bloke to create his entity before I can write my "something or other" that  relies on just 1 field in that entity... 

Several ways around the above, but as you may have gathered by now, my favourite is use late binding ;)


So, we can break an import and what needs to happen down to about 7 basic steps (8 if you want to wait for the import to complete):

  1. Create an import map
  2. Create all your column mappings linked to your import map
  3. Create the import
  4. Create the import file linked to the import and import map
  5. Kick off the parse step
  6. Kick off the transform step
  7. Kick off the physical import.


An interesting point to note is that you don't have to wait for the parse to complete before kicking off the transform and import. When MS CRM receives these requests will just queue them up until the others have completed.

When you're done you'll end up with code that looks something like this (You'll notice that this doesn't look exactly like the sites I linked above due to late binding):


var importMap = new Entity("importmap");
importMap.Attributes["name"] = "Import Map Name";
importMap.Attributes["source"] = CsvFileName;
importMap.Attributes["description"] = "Import Description...";
importMap.Attributes["entitiesperfile"] = new OptionSetValue(1); // 1 = Single Entity Per File
Guid importMapId = service.Create(importMap);

// Create a column mapping for the contact lookup field.
var contactColumnMapping = new Entity("columnmapping");
contactColumnMapping.Attributes["sourceattributename"] = "Contact";
contactColumnMapping.Attributes["sourceentityname"] = "Contact_1";
contactColumnMapping.Attributes["targetattributename"] = "new_contact";
contactColumnMapping.Attributes["targetentityname"] = "new_marketinglistcontact";
contactColumnMapping.Attributes["importmapid"] = new EntityReference("importmap", importMapId);
contactColumnMapping.Attributes["processcode"] = new OptionSetValue(1); // 1 = Process
Guid contactColumnMappingId = service.Create(contactColumnMapping);

// If you have special codes you may need a lookup mapping for the contact
var contactLookupMapping = new Entity("lookupmapping");
contactLookupMapping.Attributes["columnmappingid"] = new EntityReference("columnmapping", urnColumnMappingId);
contactLookupMapping.Attributes["processcode"] = new OptionSetValue(1); // 1 = Process
contactLookupMapping.Attributes["lookupentityname"] = "contact";
contactLookupMapping.Attributes["lookupattributename"] = "new_code";
contactLookupMapping.Attributes["lookupsourcecode"] = new OptionSetValue(1); // 1 = Source
Guid contactLookupMappingId = service.Create(contactLookupMapping);

// Create a column mapping for the campaign activity code field.
var campaignActivityColumnMapping = new Entity("columnmapping");
campaignActivityColumnMapping.Attributes["sourceattributename"] = "Campaign Activity Code";
campaignActivityColumnMapping.Attributes["sourceentityname"] = "Contact_1";
campaignActivityColumnMapping.Attributes["targetattributename"] = "new_campaignactivityid";
campaignActivityColumnMapping.Attributes["targetentityname"] = "new_marketinglistcontact";
campaignActivityColumnMapping.Attributes["importmapid"] = new EntityReference("importmap", importMapId);
campaignActivityColumnMapping.Attributes["processcode"] = new OptionSetValue(1); // 1 = Process
Guid campaignActivityColumnMappingId = service.Create(campaignActivityColumnMapping);

// Create a column mapping for the name field.
var nameColumnMapping = new Entity("columnmapping");
nameColumnMapping.Attributes["sourceattributename"] = "Name";
nameColumnMapping.Attributes["sourceentityname"] = "Contact_1";
nameColumnMapping.Attributes["targetattributename"] = "new_name";
nameColumnMapping.Attributes["targetentityname"] = "new_marketinglistcontact";
nameColumnMapping.Attributes["importmapid"] = new EntityReference("importmap", importMapId);
nameColumnMapping.Attributes["processcode"] = new OptionSetValue(1); // 1 = Process
Guid nameColumnMappingId = service.Create(nameColumnMapping);

// Create Import
var import = new Entity("import");
import.Attributes["modecode"] = new OptionSetValue(0);
import.Attributes["name"] = "Importing data";
Guid importId = service.Create(import);

// Create the actual file...
var file = new Entity("importfile");
file.Attributes["content"] = File.ReadAllText(CsvFileName);
file.Attributes["name"] = CsvFileName;
file.Attributes["isfirstrowheader"] = true;
file.Attributes["source"] = CsvFileLocation;
file.Attributes["sourceentityname"] = "Contact_1";
file.Attributes["importmapid"] = new EntityReference("importmap", importMapId);
file.Attributes["importid"] = new EntityReference("import", importId);
file.Attributes["targetentityname"] = "new_marketinglistcontact";
file.Attributes["size"] = ((string)file.Attributes["content"]).Length.ToString();
file.Attributes["fielddelimitercode"] = new OptionSetValue(2); // 2 = Comma
file.Attributes["datadelimitercode"] = new OptionSetValue(1); // 1 = Double Quote
file.Attributes["processcode"] = new OptionSetValue(1); // 1 = Process
file.Attributes["usesystemmap"] = true;
Guid fileId = service.Create(file);

var parseRequest = new ParseImportRequest { ImportId = importId };
service.Execute(parseRequest);

var transRequest = new TransformImportRequest { ImportId = importId };
service.Execute(transRequest);

// Assign the request the id of the import we want to begin
var request = new ImportRecordsImportRequest { ImportId = importId };
var response = (ImportRecordsImportResponse)service.Execute(request);


You most likely won't end up with all that code in 1 place like this, or at least I hope not! But that's the general gist of what needs to happen.


Dynamic Marketing Lists

So what the above gives you is a very quick way to get the data imported into MS Dynamics CRM. But how do we use this? Let's grab that fetch xml from the earlier query and inject our campaign code into that:


string fetchXml = string.Format(
    "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>" +
    "  <entity name='contact'>" +
    "    <attribute name='fullname' />" +
    "    <attribute name='contactid' />" +
    "    <order attribute='fullname' descending='false' />" +
    "    <link-entity name='new_marketinglistcontact' from='new_contact' to='contactid' alias='aa'>" +
    "      <filter type='and'>" +
    "        <condition attribute='new_campaignactivitycode' operator='eq' value='{0}' />" +
    "      </filter>" +
    "    </link-entity>" +
    "  </entity>" +
    "</fetch>",
    campaignActivityCode);

Set up a new Marketing List, pop that into the "query" field of a Marketing list, and set the "type" to dynamic (1) and off we go.


Converting from a Dynamic to a Static Marketing List

Final step is to convert this to a Static Marketing List. This is easily achievable using the "CopyDynamicListToStaticRequest":

var copyDynamicListToStaticRequest = new CopyDynamicListToStaticRequest { ListId = ml.Id };
var response = (CopyDynamicListToStaticResponse)ml.Service.Execute(copyDynamicListToStaticRequest);
staticMarketingList = new MarketingList(ml.Service, response.StaticListId) 
    { Name = MarketingListCSVFile.MarketingListName, Locked = false };


Job Done.

I'd like to take this opportunity to thank that Hetfield dude for being frikken awesome and passing on the awesome. More specifically, his idea of using a dynamic marketing list.

Word.

Tuesday, 13 March 2012

ExecutionContext.CallerOrigin in Microsoft Dynamics CRM 2011?

I only realised this a couple of days ago... but this property has been made obsolete, supposedly because it was unreliable. My delayed realisation is probably due to my absence from the CRM scene until about 6 months ago!

Anyway, there were a few reasons why we needed the CallerOrigin in MSCRM 4.0, so it's a pity it was removed. But there are ways around each scenario where we needed it:


Prevent infinite loops (over 2 plugins)

This was the reason I was looking for the CallerOrigin and I started to ask this question. I only wanted the plugin to fire if it came from a user on the form, not from another plugin (i.e. asyc process/webservice). In my case the distinction of it being "over 2 plugins" is quite important, because I cannot use InputParameters to solve the problem. My example was similar to the following:

  • Update Plugin for "Parent" Entity. If optionset called "Status" on the parent entity was set to "Approved" I subsequently wanted to set a status on all the child entities to "Approved" as well.
  • Update Plugin for "Child" Entity. If the optionset called "Status" on the child entity was set to "approved", and all other children of the same parent has this set to "Approved" I needed to update the Status on the parent to approved as well.

This causes an infinite loop if you don't protect yourself against it. You also cannot use the InputParameters to solve it. Solution? Use depth checking:

context.PluginExecutionContext.Depth

If this is greater than 1 it's been called by another plugin.


Prevent syncing issues from an offline client

We've been given different properties to help us distinguish these ones. Use these instead:

context.PluginExecutionContext.IsExecutingOffline
context.PluginExecutionContext.IsOfflinePlayback


Reacting differently depending on what the origin is

Ok, so this is the only scenario where we really do need the CallerOrigin. The only way I think you'd be able to do this is by checking the type of the PluginExecutionContext itself. I know for async it's the type:

Microsoft.Crm.Asynchronous.AsyncExecutionContext

and for plugins it seems to be:

Microsoft.Crm.Extensibility.PipelineExecutionContext

Outside of that you would probably have to check the

PluginExecutionContext.ParentContext


Alternatives?

The only other method I've come across for detecting where the update came from is using a custom flag on the form. So you could create an OptionSet called "OriginOfChange" (or something similar) with the options

  • CRM Form (Javascript onsave)
  • Workflow
  • Plugin
  • etc.

Then what ever updates the entity sets this field during the update. In this way you could check the Input Parameters each time to see where the update has come from.