Wednesday, September 3, 2008

Quick MS CRM CTI Integration

Although this is just a partial cti solution I wanted to demonstrate how easy it is to create cti integration app using Microsoft crm.

I added a single aspx page that I call cti.aspx inside the ISV folder. In order to avoid using CrmService Proxy or creating a new asp.net application I used my former post “Ajax using fetch message” to call CrmService.Fetch. The cti.aspx page receives a parameter which reflects the type of search to make e.g. p for phone numbers or n for names and the organization name. I only implemented the phone number search for the sake of this example.

The page (Page_Load) constructs a FetchXml using the received parameter (p or n) value and hands it over to the client side Fetch function. The function returns a FetchResult. If the result contains zero accounts then a new account page is opened inside the iframe, if a single result is returned then the accountid is taken from the result and concatenated into a valid account url. If there is more then 1 account then the iframe is loaded with an advanced find page which uses the original fetch to build and execute the advanced find form dynamically.

I didn’t add the functions from my former post “Ajax using fetch message” so just copy the functions without the OnCrmPageLoad function to this implementation.

A few tips before you start:
1. Construct a fetchxml using the crm advanced find window. Again my former post shows exactly how to accomplish that.
2. After you create an advanced find query copy the results to a notepad and replace all (“) with (‘)
3. The fetchxml is assigned to a client side function so it should be contained in a single line.
4. This app uses a valid fetchxml with an OR filter for all the account telephone fields . Try passing the cti.aspx?p=[a number that exist more then once] to see the fetch query inside the advanced find form.
5. The advanced find url built into this example is taken from my test environment. you need to replace it with a valid QueryId GUID.

It took me 10 minutes to write this down which was the whole idea to begin with.
I hope you find this useful.

Page Url: http://[CRM]/[ORG]/ISV/cti.aspx?p=555-555-0100&o=[ORG]

Here is the code devided into logical sections(c#,js,html)


<%@ Page Language="C#" AUTOEVENTWIREUP="true"%>

<script runat="server">

private String FetchXml = String.Empty;
private String OrgName = String.Empty;

protected void Page_Load( Object sender , EventArgs e )
{
try
{
OrgName = Request.QueryString["o"] + "";
switch(Request.QueryString.Keys[0])
{
case "n": // n for name
//Assemble FetchXml
break;
case "p": // p for phone
FetchXml = String.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'><entity name='account'><attribute name='accountid'/><order attribute='name' descending='false'/><filter type='and'><filter type='or'><condition attribute='telephone1' operator='eq' value='{0}'/><condition attribute='address1_telephone2' operator='eq' value='{0}'/><condition attribute='address1_telephone3' operator='eq' value='{0}'/><condition attribute='address2_telephone1' operator='eq' value='{0}'/><condition attribute='address2_telephone2' operator='eq' value='{0}'/><condition attribute='address2_telephone3' operator='eq' value='{0}'/></filter></filter></entity></fetch>", Request.QueryString[0]);
break;
}
}
catch( Exception ex )
{
Response.Write( ex.ToString() );
}
}
</script>




<script>
var resultFrame;

function window.onload()
{
resultFrame = document.all.resultFrame;
var FetchResult = Fetch( "<%=FetchXml%>" );
switch( FetchResult.documentElement.childNodes.length )
{
case 0:
resultFrame.src = "/<%=OrgName%>/sfa/accts/edit.aspx#";
break;
case 1:
resultFrame.src = "/<%=OrgName%>/sfa/accts/edit.aspx?id=" + FetchResult.selectSingleNode("//accountid").text + "#";
break;
default:
resultFrame.src = "/<%=OrgName%>/AdvancedFind/AdvFind.aspx?EntityCode=1&QueryId={00000000-0000-0000-00AA-000010001001}&ViewType=1039"
resultFrame.onreadystatechange = OnAdvancedFindIframeReady;
break;
}
}

function OnAdvancedFindIframeReady()
{
if( resultFrame.readyState != 'complete' ) return;

resultFrame.contentWindow.document.all.advFind.FetchXml = "<%=FetchXml%>";
window.setTimeout(Run,1000);
}

function Run()
{
resultFrame.contentWindow.document.all.btnRun.click();
}

/*
you can either refere to an opener (if one exists) to get the AuthenticationHeader or
construct it your self like so.
*/
function GenerateAuthenticationHeader()
{
var token = "<soap:Header>";
token += "<CrmAuthenticationToken xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">";
token += "<AuthenticationType xmlns=\"http://schemas.microsoft.com/crm/2007/CoreTypes\">0</AuthenticationType>";
token += "<OrganizationName xmlns=\"http://schemas.microsoft.com/crm/2007/CoreTypes\">";
token += "<%=OrgName%>";
token += "</OrganizationName>";
token += "<CallerId xmlns=\"http://schemas.microsoft.com/crm/2007/CoreTypes\">00000000-0000-0000-0000-000000000000</CallerId>";
token += "</CrmAuthenticationToken>";
token += "</soap:Header>";
return token;
}

/* Add the “ajax using Fetch message functions here */

</script>




<html>
<head runat="server">
<title>Untitled Page</title>
</head>
<body scroll="no" style="margin:0px">
<iframe id="resultFrame" src="about:blank" style="width:100%;height:100%"></iframe>
</body>
</html>

3 comments:

Anonymous said...

Thanks for this great job. It saves me a lot of time.

Jean-Manuel.

Dustin said...

Adi, do you see this working in a hosted environment as well? We have control over the webpages, but would it be feasible to pass the authentication from a user who is not on the same domain?

Anonymous said...

hi Adi- this is Anil here and I talked to you in around 2011 regarding Dynamics CRM. Can you please send me a test mail at anil.sonsoft26@hotmail.com