Saturday, January 19, 2008

Dynamically accessing the field mappings area between Opportunity Product, Quote Product, Salesorder Product and Invoice Product

In a lot of projects we do for our customers with Microsoft Dynamics CRM 3.0 I find it very usefull that, though the relationships are not existing, it is still possible to access the field mappings areas to specify mappings between Opportunity Product, Quote Product, Salesorder Product and Invoice Product. This Microsoft Support Article tells how to do this.

(Custom) mappings between those entities are wanted when custom fields are added to them. When converting an opportunity to a quote, or quote to salesorder or salesorder to invoice, you want the underlying details to be copied completely, including your own custom fields.

But, it is not that easy to do. You have to open pretty complex webpages (i.e. webservice descriptions and webservice result xml) which most (functional) consultants haven't seen before and than copy and paste guid's (who want's to deal with guids?) into the final field mappings area url to do the job. Above all, this has to be done physically on the CRM server itself (at least the first steps).
So, I decided to include the (links to the) mapping pages into our standard product settings module. Pretty easy to do. Nothing else than hyperlinks to the following url (as documented in the Microsoft Support Article):

http://crmserver:5555/tools/systemcustomization/relationships/mappings/mappinglist.aspx?mappingid={mapid}
where crmserver:5555 needs to be replaced by the correct server:port address. And {mapid} must contain the id (guid) of the specific field mapping area.

But: how to dynamically obtain the correct mapid? (Microsoft Support says in their article mentioned that those id's differ for each environment!).

The Microsoft Support Article describes how to obtain the mapid's:
  1. open a url displaying the description for webservice "EntityMap"
  2. click a hyperlink to display the webmethod "RetrieveEntityMaps"
  3. as test parameter, fill in the specific source entityname and click "Invoke"
  4. find in the displayed result xml the correct mapid and copy this
  5. (now you can paste the mapid into the final url (see above) to bring up the desired mappings area)

Step 1 to 4 can be easily automated in a C# method which returns the mapid (e.g. a webmethod which can be called from Javascript, or inside an .aspx code-behind page). Here's the code:

string getMapId(string entityFrom, string entityTo)
{
    try
    {
        //create httprequest to call the "RetrieveEntityMaps" webmethod
        string urlFromRegistry = (string)Registry.LocalMachine.CreateSubKey("SOFTWARE\\Microsoft\\MSCRM").GetValue("ServerURL");
        string uri = Path.Combine(urlFromRegistry, "entitymap.asmx/RetrieveEntityMaps");
        string data = "entityName=" + entityFrom;
        HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
        req.Method = WebRequestMethods.Http.Post;                
        req.UseDefaultCredentials = true;
        req.ContentLength = data.Length;
        req.ContentType = "application/x-www-form-urlencoded";
        //and add the parameter
        StreamWriter wr = new StreamWriter(req.GetRequestStream());
        wr.Write(data);
        wr.Close();
                
        //get the response
        HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
        //load it into a XmlDocument
        XmlDocument doc = new XmlDocument();
        doc.Load(resp.GetResponseStream()); 
        resp.Close();
        //Select the mapid using a Xpath expression
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
        nsmgr.AddNamespace("ns""http://schemas.microsoft.com/crm/2006/WebServices");
        string xPath = "//ns:entitymap[ns:targetentityname = '" + entityTo + "']/ns:entitymapid";
        XmlNode node = doc.SelectSingleNode(xPath, nsmgr);
        //Return the InnerText of the found node
        if (node != null) return node.InnerText;
        return "";
    }
    catch (Exception e)
    {
        //probably an authorization error...
        return "";
    }
}

Note that this code uses undocumented CRM features (at least not documented in the SDK). This makes it unsupported (but I bet it will continue working in 4.0, though I haven't tested it yet).

0 comments: