"I want to be able to open a record in CRM, push the button and import the information as a contact into Outlook. Then, after synchronising with my PDA, I'll have this contact card available while on the road."
That was, in a nutshell, the requirement from one of the CRM users. This particular CRM deployment contained over 10,000 account and contact records imported from the industry database and taking them all offline would have been a challenge not to mention impractical. After some brainstorming the following alternatives emerged:
- Offline CRM client synchronising a subset of records. Apart from the fact that user did not have a laptop with them all the time, process of adding a selected individual account/contact would have become quite complicated. We could have built a view selecting accounts/contacts based on some criteria, e.g. custom flag and then ask user to check this flag for the selected record and then go offline. Cumbersome and would not work that well when user was on the road.
- CRM 3.0 Mobile. Same issues as above. In addition user wanted the records to be on both laptop and PDA.
- CRM Mobile Express. Perhaps that would have worked if user had a reliable phone reception everywhere they went. Unfortunately, due to the nature of their business quite often they ended up in places which fall into unlucky 2% of Australian population without reliable mobile coverage. If anyone thinks that satellite phone is an option, just wait until your first bill.
- Export/import facility. Outlook has ability to import contact information from vCards so all we have to do is to export CRM information as a vCard. Now we were cooking...
If you cannot wait to try it out, download the binaries and the source (Visual Studio 2008), and follow instructions in a readme to deploy. The final result:
Note that the sample account name contains some Cyrillic and Simplified Chinese (I think :-) characters - this is just to test cosmopolitan nature of the implementation.
The anatomy of the solution is very simple:
- Custom toolbar button links to getvcard.aspx page and passes parameters that define entity to be exported.
- Code on getvcard.aspx connects to CRM (working out version in the process) and extracts CRM record as a DynamicEntity instance.
- Based on the entity type, vCard template is loaded and slugs are replaced using properties of the extracted DynamicEntity.
- The content is marked as text/x-vCard and sent to the browser as a file attachment with extension vcf.
- Browser prompts to Open/Save and, if Open is selected, Outlook takes over and opens the file as a new contact record.
Works on both CRM 3 and CRM 4.
vCard and i18n
In 1996 Internet Mail Consortium took on vCard and vCalendar technologies from versit consortium. Since then vCard standard evolved to couple RFCs however Outlook, even in 2007 version, still seems to support only 2.1 specifications (last non-RFC version) which is reasonably documented. As the only application we had to concern ourselves with was Outlook, we decided to stick with version 2.1.
In a nutshell, vCard is a list of fields and values in the following format:
PropertyName [‘;‘ PropertyParameters] ’:‘ PropertyValue
It's all nice and simple and we even managed to implement vCard generation using templates with slug replacement. But what an implementation of any standard would be without a challenge? As often is the case, encoding needed to be taken care of. You see, vCards are supposed to be transmittable using 7-bit ASCII and property values in vCards therefore have to be encoded using either quoted-printable of base64, when required. As base64 is unreadable, we really wanted to use quoted-printable encoding but could not find any reliable sample code that would handle anything beyond 8-bit ASCII. Throw in character sets definitions supported by vCard specifications and you've got one big i18n mess.
I won't bother you with research details but since we never transmitted any of vCards and only had to concern ourselves with Outlook, the solution turned out to be quite simple. Outlook was happy to digest any data from CRM as long as we recorded them using UTF-8 encoding. To pass through line breaks, '=' and sometimes ';' we still had to use quoted-printable encoding but we limited our implementation to these special cases. The end result is not quite what would you call quoted-printable but it seems to work just fine.
Dual binary support
Another problem was rather self-inflicted. As the implementation turned out to be quite straightforward, we challenged ourselves with producing single binary that would support both CRM 3 and CRM 4. Simpler solution would have been a single code base with some conditional compilation here and there but we wanted to see what would it take to make same binaries to run on both versions.
The code was compiled using Microsoft.Crm.Sdk and Microsoft.Crm.SdkTypeProxy libraries, finalised and tested against CRM 4. Once it was working, we pointed code to CRM 3 endpoint, tried to execute it "as is" and immediately run into problems with the line:
RetrieveResponse retrieved = service.Execute(retrieve) as RetrieveResponse;
which threw the following SOAPException:
"Server did not recognize the value of HTTP Header SOAPAction: http://schemas.microsoft.com/crm/2007/WebServices/Execute."
Close peek into Microsoft.Crm.SdkTypeProxy library using Reflector explained what was happening. All SOAP methods in this library are marked with http://schemas.microsoft.com/crm/2007/WebServices namespace while CRM 3 expected http://schemas.microsoft.com/crm/2006/WebServices. Microsoft.Crm.Sdk library, on the other hand, uses 2006 namespaces throughout, apart from new classes and methods introduced in CRM 4, e.g. CrmAuthenticationToken. What that meant that, perhaps, if we didn't engage into some complicated scenarios we might be able to use Microsoft.Crm.Sdk library references against CRM 3.
As far as SdkTypeProxy references are concerned, we only needed a handful of methods and classes so we were able to proxy them manually. We generated proxy code against CRM 3 installation and then carefully stripped out everything that was not required by our code. The end result is Microsoft.Crm.SdkTypeProxy3.cs file containing version 3 of some of classes and methods defined in Microsoft.Crm.SdkTypeProxy.
Our code is designed to run on the CRM server so we use registry value CRM_Server_Version to work out CRM server version at run-time. Apart from different end-point and call to Execute method, CRM 4 also requires some additional code to support impersonation and multiple organisations and IFD scenarios. But once we created instance of DynamicEntity class for the entity, the code relies purely on Microsoft.Crm.Sdk and is identical for both CRM 3 and 4.
One other minor difference between CRM 3 and CRM 4 is the way the parameters are passed into custom URLs. CRM 4 uses id and typename while CRM 3 passes in oId and oTypeName. That, perhaps, was the easiest code fork:
if (CrmHelper.GetServerVersion().Major == 3)
id = new Guid(Request.QueryString["oId"]);
entityName = Request.QueryString["oTypeName"];
id = new Guid(Request.QueryString["id"]);
entityName = Request.QueryString["typename"];
While "single binary" implementation is most certainly an overkill and is unlikely to be required in real life, the challenges of making your code work with both versions of CRM are very common for ISVs and having a single distribution binary for your product is certainly an advantage worth aiming for. As mentioned, easier approach would have been conditional compilation linking code to different proxy libraries.
Despite all the efforts to produce single binary supporting both CRM versions, the installation still differs. The main difference is support for multi-tenancy and IFD in CRM 4 which means that we have to run our code under CRM web site context. In version 3, since our code requires CLR 2.0, we have to create a separate virtual web site under .NET 2.0 application pool not to interfere with CRM server. Unless mentioned otherwise, all installation is done on CRM server.
- Place AlexaCrm.vCard.dll from installation folder into GAC. Drag'n'dropping file into c:\windows\assembly using Windows Explorer usually does the trick, command line interface addicts can use gacutil.exe /i. Note: there is no need to do anything with Microsoft.Crm.*.dll assemblies as they should already be in server's GAC.
- Create new folder, e.g. c:\program files\vcard.
- Copy GetVCard.aspx and web.config files from installation\4 folder into the folder above.
- Create new virtual directory named vCard under ISV folder located in Microsoft Dynamics CRM web site and point it to the folder above. Make sure you don't select Run Scripts permissions to avoid creating a separate application:
- Export ISV Config customizations, add Button entries listed in installation\4\isv.config.xml and import back. No need to publish.
- If you have more than one organisation that you want to enable vCard support for, rinse and repeat step 5.
- Create new folder, e.g. c:\program files\vcard. Create bin subfolder.
- Copy GetVCard.aspx and web.config files from installation\3 folder into the folder above.
- Copy AlexaCrm.vCard.dll, Microsoft.Crm.Sdk.dll and Microsoft.Crm.SdkTypeProxy.dll into c:\program files\vcard\bin folder. As we run our code under a separate web site, there is no need for assemblies to reside in GAC and they can be happily live in the bin folder.
- Create new virtual directory named vCard under Microsoft Dynamics CRM web site and point it to the folder above. Make sure you select Run Scripts permissions to create a separate application:
- Open Properties dialog for the new site and make sure that it's .NET 2.0 application that runs under one of .NET 2.0 application pools:
- Add Button entries listed in installation\3\isv.config.xml to your C:\Program Files\Microsoft CRM\CRMWeb\_Resources\isv.config.xml file. No need to publish but it's a good idea to backup this file prior to making any changes as you can easily render your CRM installation useless.
Download: AlexaCrm.vCard.zip (332.71 kb)
Last revised: 07 Dec, 2012 04:46 PM