Saturday, December 20, 2008

MS CRM 4.0 Filtered Lookups

I just finished implementing a number of filtered lookups with Michael Höhne's Stunnware Filtered Lookup 4.0. Like most developers I have to think seriously about whether I want to build something vs. pay for someone else's work.

There has been a lot of work put into this product since its introduction with CRM 3.0. The 4.0 release seems pretty well sorted and really makes creating filtered lookups an easy task to accomplish. Supporting the Outlook client, multiple languages, multi-tenancy, and IFD makes this a much better solution then most(probably all) of us would take the time to build on our own.

Filtered Lookups have been reinvented far too many times and become maintenance issues for many upgrading from CRM 3.0 to CRM 4.0. I would rather not have to dig through another guy's failed attempt at this functionality in the future. I personally believe MS should pull this functionality into its product, but barring that, I think using a product like this is the best way to go.

I'm currently using it for a growing list of filtered Lookups for one my customers.  This includes some pretty standard lookups like the Account: Primary Contact Lookup, the Case: Responsible Contact, and the Email To:, CC:, and BCC: fields, as well as some for custom fields.

What is typically involved in making a filtered lookup with this tool is the following:

  1. Settings -> Filtered Lookup: Create a Retrieve Multiple Queries entity
    • This may involve using the Fetch Wizard to generate the query.
  2. Settings -> Filtered Lookup: Create a Single-View Lookup entity
  3. Settings -> Customizations: Add JavaScript to your entity's OnLoad event.

Here is a simple example:

I needed a filtered list for Reseller Accounts on an opportunity.

image

So I created a new Retrieve Multiple Query entity,image

copied the Query from the Active Accounts example and added a filter for an industrycode picklist value of 21,image

created a Single-View Lookup using the Query above,image

setup the columns for that Lookup,image 

set the Labels to English,image

added this JavaScript to my Opportunity's OnLoad Event. Published the opportunity and I was done.

SW_IS_LICENSED_USER = false; try
{ var httpRequest = new ActiveXObject("Msxml2.XMLHTTP"); httpRequest.open("GET", prependOrgName("/isv/stunnware.com/cld4/cld4.aspx?orgname=" + ORG_UNIQUE_NAME), false); httpRequest.send(null); eval(httpRequest.responseText); } catch(e) { } if (SW_IS_LICENSED_USER) { var resellerAccountLookup = new SwSingleLookup("new_resellerid"); if (resellerAccountLookup.existsOnForm()) { resellerAccountLookup.setLookupClass("ResellerAccounts"); } }

All in all that took maybe 10 minutes including testing.

That was a very simple case. The examples in the help file show you how to create the Account: Primary Contact filtered lookup showing only Contacts associated with that Account. Implementing this only required cutting from the example and pasting into the OnLoad Event of the account form. Another example gave me the information I needed to to implement the Case Responsible Contacts Filter where the Filtered Lookup changes when the case's account is changed. This required a little JavaScript added to the OnChange for the customerid as well as the Onload for the form.

The email modifications I made were also very easy to implement as shown in the code that follows. Since the Account is almost always preloaded in the To: field, it was easy to overload the lookups for the target selections to default to the Contacts related to that Account.

SW_IS_LICENSED_USER = false;

try {
    var httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    httpRequest.open("GET", prependOrgName("/isv/stunnware.com/cld4/cld4.aspx?orgname=" + ORG_UNIQUE_NAME), false);
    httpRequest.send(null);
    eval(httpRequest.responseText);
}

catch(e) {
}

if (SW_IS_LICENSED_USER) 
{  
   if ( crmForm.all.to.DataValue != null )
   {
     var accountid =  crmForm.all.to.DataValue[0].id;
    
     var toContactLookup = new SwSingleLookup("to");
     toContactLookup.setParameter("parentcustomerid", accountid);
     toContactLookup.setLookupClass("AssociatedContacts");

     var ccContactLookup = new SwSingleLookup("cc");
     ccContactLookup.setParameter("parentcustomerid", accountid);
     ccContactLookup.setLookupClass("AssociatedContacts");

    var bccContactLookup = new SwSingleLookup("bcc");
    bccContactLookup.setParameter("parentcustomerid", accountid);
    bccContactLookup.setLookupClass("AssociatedContacts");
   }

}

For this instance I am still relying on the Form Assistant if the end user wants to select multiple Contacts for a single field or add a CRM user, which is a rare case for this customer. The Form Assistant is not modified by these lookups in any way.

Understanding 3 Important Lines of Code
There are three important lines of code to that you will use to create a filtered lookup.

1. Create a new lookup to override the existing lookup for specified field on the form. In this case the "to" field on the email form. 

      var toContactLookup = new SwSingleLookup("to");

2. Set a filter parameter that is defined in your Fetch and assign a value. 

      toContactLookup.setParameter("parentcustomerid", accountid);

Below is the Fetch that you can access under settings. Notice the parentcustomerid field in the filter section. You are not limited to a single parameter if you need additional criteria for your filter.

<fetch mapping="logical">
   <entity name="contact">
      <attribute name="emailaddress1" />
      <attribute name="telephone1" />
      <attribute name="fullname" />
      <order attribute="fullname" />
      <filter>
         <condition attribute="statecode" operator="eq" value="0" />
         <condition attribute="parentcustomerid" operator="eq" param="parentcustomerid" />
      </filter>
   </entity>
</fetch>


3. Set the class that you are using.  

     toContactLookup.setLookupClass("AssociatedContacts");

That's it, just save your customization and publish. You now have a working filtered Lookup.

Just the Beginning 
These are some very simple examples, but it is obvious how easily much more complex filters could be. If you are going to create complex Retrieve queries, it is worth downloading the Fetch Wizard as well, which is a really easy way to create the XML needed for the fetches that this tool relies on. The Fetch Wizard is part of the Stunnware Tools 4.0. that are available for download at no charge.

What you will see Under Settings-> Filtered Lookup are the following which will allow you to configure everything but the little bit of JavaScript that you will add to your forms, and there are even tools for tracing your filtered lookups if something isn't working quite right.image

This tool is made for a developer to use, but a person comfortable with customizing CRM and with some JavaScript experience should be able to get by. It requires an installation process that can require you to manually update the XML of your ISV.config and SiteMap, but if you read the instructions, it will all make sense.

If you are playing with the MS CRM 4.0 VPC Image, there is a license for that image that you can download at no cost to test this system out.

If you are considering reinventing this wheel, I seriously recommend trying this solution out first.

Monday, December 15, 2008

MS CRM 4.0 Plug-ins vs. CRM 3.0 Callouts

Plug-ins are a huge improvement over the callouts available in MS CRM 3.0. They are much more flexible, much easier to deploy, and have a many more triggers allowing a lot more access to the inner operation of your CRM system.

The following comparison shows some of the key differences:

Callout

Plug-in

Deployment

Requires Workflow service to be restarted to re-read the callout.config.xml which defines which assemblies and classes to call for each triggered event.

Requires assemblies be copied to each CRM server in your web server farm. Because those assemblies can be busy this can require:

  1. Draining each server in an NLB configuration.
  2. An IIS reset
  3. Stopping the Workflow service.
  • Allows deployment to the database in a single step for all CRM servers.
  • Or deployment to the GAC on each server.
  • Or deployment to a folder for debugging.

 

  • Requires a strongly typed assembly.

Flexibility

Requires a different interface for each Message. A PreCreate has a different interface from a PostCreate, or a PreUpdate, or a PostUpdate. Has the same interface for all messages.

Supports many more messages
Supported Messages for Plug-ins

  Has parent and child pipeline feature
  Has the ability to watch for endless loops.

Coding

Requires parsing Xml images to see the contents of the entity generating the message: preImageEntityXml, postImageEntityXml

Returns a Dynamic Entity or Moniker instead.

var entity = (DynamicEntity)context.InputParameters.Properties[ParameterName.Target];

var myMoniker = (Moniker)context.InputParameters.Properties[ParameterName.Target];

Allows validity checking and stopping a change to the database in a preCallout

errorMessage = "Validation Failed."; return PreCalloutReturnValue.Abort;
                

Allows validity checking and stopping an operation in a Pre message.

throw new InvalidPluginExecutionException(
"Validity Failed. {1}");

Allows modifying data before it is written to the database in a  preCallout.

Parse and Modify the entityXML               

Allows modifying data before it is written to the database in a Pre message.

Modify the Dynamic entity.

String nameProperty = entity.Properties["name"] as String;
nameProperty = "new name";


Resources

VS Plug-in template This template will create a basic Plug-in Project and is a good starting point.

  • The example it creates is for a Dynamic Entity Target. This is useful for Create and Update messages, but many other messages will return a Moniker which gives you the id of the calling entity.
  • The template includes code to call a customized crmservice which can be useful, although it is faster and a better practice to call your service from the context as shown below if you are comfortable with Dynamic Entities:

    ICrmService service = context.CreateCrmService(true);

Plugin Deployment Tool This tool will allow you a simple way to deploy your plug-ins. and has an easy to use UI.

With plug-ins it is important to learn about Dynamic Entities.

If you purchase David Yak's CRM as a Rapid Development Platform, it comes with a number of helper classes for Dynamic Entities, and plug-ins as well as other useful tools that are interesting. His chapters on plug-ins and dynamic entities are useful, and the code he provides is worth downloading and referring to. He also has chapters specific to using his plug-in framework for debugging and other tasks which are not CRM development generic.

I recently ordered a copy of Programming Microsoft Dynamics CRM 4.0, and I'll try to remember to update this post after I have looked over its coverage of Plug-ins and dynamic entities.