Dynamics CRM - Set default unit on product line

24 August 2010

When picking a product on a CRM entity like quote product or order product the user also has to pick a unit of measure for the product. Wouldn't it be useful if CRM filled in the unit of measure field for the user based on the default unit for the product? Of course it would, but it doesn't do this out of the box.

My solution for this involves using a generic handler to get the default unit for a specific product and some JavaScript to call the handler asynchronously.

The JavaScript that is placed in the onchange event of the productid field looks like this:

var lookupItem = new Array;
// Get the lookup for the primarycontactid attribute on the account form.
lookupItem = crmForm.all.productid.DataValue;
if (lookupItem[0] != null)
{
  InitXmlHttp();
  xmlhttp.onreadystatechange= XMLHttpRequestCompleted;
  xmlhttp.open("GET", "../../../TSG.CRM.GenericHandlers/QuoteProductSetDefaultUnits.ashx?
id="+lookupItem[0].id , true );
  xmlhttp.send(null);
}
 
function InitXmlHttp() {
  // Attempt to initialize xmlhttp object
    try
    {
        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (e)
    {
        // Try to use different activex object
        try
        {
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch (E)
        {
            xmlhttp = false;
        }
    }
    
    // If not initialized, create XMLHttpRequest object
    if (!xmlhttp && typeof XMLHttpRequest!='undefined')
      {     
            xmlhttp = new XMLHttpRequest();
      }    
}
 
function XMLHttpRequestCompleted()
{
        if (xmlhttp.readyState==4)
    {
        try
        {
           var lookupData = new Array();
           var lookupItem = new Object();
           var response = new Array();
           response = xmlhttp.responseText.split(',');
                             
           lookupItem.id = response[0];
           lookupItem.typename = response[1];
           lookupItem.name = response[2];
           lookupData [0] = lookupItem;
           crmForm.all.uomid.DataValue = lookupData ;
         }
        catch (e)
        {
           alert('Something has gone wrong!');
        }
    }
}
 

The generic handler code looks like this:

using System;
using System.Collections;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;
using System.Text;
using CRM.GenericHandlers.CRMService;
using System.Configuration;
 
namespace CRM.GenericHandlers
{
    /// <summary>
    /// Summary description for $codebehindclassname$
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class QuoteProductSetDefaultUnits : IHttpHandler
    {
 
        public void ProcessRequest(HttpContext context)
        {
            string productid = context.Request.QueryString["id"];
 
            try
            {
                CrmAuthenticationToken token = new CrmAuthenticationToken();
                token.AuthenticationType = 0; 
                //add an <appsetting> to web.config for your organisation name
                token.OrganizationName = ConfigurationManager.AppSettings["OrgName"].ToString();
 
                CrmService service = new CrmService();
                service.CrmAuthenticationTokenValue = token;
                service.Credentials = System.Net.CredentialCache.DefaultCredentials;
 
                //create the ColumnSet that indicates properties to be retrieved
                ColumnSet productCols = new ColumnSet();
                ColumnSet uomCols = new ColumnSet();
               
                //set the properties of the ColumnSet
                productCols.Attributes = new string[] { "productid", "defaultuomid" };
                uomCols.Attributes = new string[] { "uomid", "name" };
                
                //retrieve quote
                Guid ProductID = new Guid(productid);
                product product = (product)service.Retrieve("product", ProductID, productCols);
 
                Guid uomID = product.defaultuomid.Value;
                uom productUom = (uom)service.Retrieve("uom", uomID, uomCols);
 
                string productUomName = productUom.name;
 
                string[] arrLookupItem = new string[3];
                arrLookupItem[0] = uomID.ToString();
                arrLookupItem[1] = "uom";
                arrLookupItem[2] = productUomName;
 
               context.Response.Write("{"+uomID+"},uom,"+productUomName); 
 
            }
            catch (SoapException ex)
            {
                context.Response.Write(ex.Message);
            }
 
        }
 
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}
 

XmlHttpRequest Cache Busters

04 August 2010

The call to a generic handler in the post Using generic handlers in Dynamics CRM seemed to only be firing once. The alert was being displayed to say that the code had run successfully but I could see that after the first call it wasn't copying the entity as it should have.

It turns out this was because the xmlhttprequest was being cached and the fix was fairly simple. By adding an extra parameter to the url called by the request we can trick it into thinking that a completely new call is being made which prevents the cached result from being used.

Simply add the following to the query string: "bustCache=" + Math.random();