Sunday, September 21, 2008

JavaScript Ajax Wrapper

The idea behind this post is not about calling a specific crmservice but a general idea of how you can facilitate calls to any webservice, whether it’s a custom one you built your self or one of crm’s existing services. Although the code introduces working classes they can be further develop to support more advanced requirement.

The OnCrmPageLoad contains several calls using the WebServiceStartInfo and the WebRequest classes both to crmservice and a custom service I made to demonstrate the usage. The concept I follow is comprised of 2 steps. The first step is about filling the information required to make an Ajax call. The second and last step just executes the requests and hands over the results back to you.

The nice thing about the WebServiceStartInfo is that it builds the entire soap message for you. This makes life very easy since you can focus more on the business requirement and less on the technology. Never the less, a good grasp over Ajax is needed in order to fill the StartInfo with the correct data.

Note. The easiest way of getting the soap needed to make a request is to open the asmx file in Internet explorer. This will assist you when you construct complex webmethod parameters using the Parameters.Add method of the WebServiceStartInfo and Parameter classes.

The fist part describes the WebService.aspx code behind:


namespace MyAppServices
{
/// <summary>
/// Summary description for WebService1
/// </summary>
[WebService(Namespace = "http://www.mycompany.com/demo")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
public class WebService1 : System.Web.Services.WebService
{

[WebMethod]
public string Hello()
{
return "Hello";
}

[WebMethod]
public string HelloUser(String userName)
{
return String.Format("Hello {0}", userName);
}

[WebMethod]
public String HelloOnceMore( String[] Parameters )
{
return String.Join( "," , Parameters );
}

[WebMethod]
public string HelloAgain(ComplexParameter userInfo)
{
return String.Format("Hello {0} {1}", userInfo.FirstName, userInfo.LastName);
}

[Serializable]
public class ComplexParameter
{
public String FirstName;
public String LastName;
}
}
}



This part describes the client side usage called from crm onload event / external file.


function OnCrmPageLoad ()
{
/* call the CrmService Fetch method. */
var fetchXml = _HtmlEncode('<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"><entity name="systemuser"><attribute name="fullname"/><attribute name="businessunitid"/><attribute name="title"/><attribute name="address1_telephone1"/><attribute name="systemuserid"/><order attribute="fullname" descending="false"/><filter type="and"><condition attribute="systemuserid" operator="eq-userid"/></filter></entity></fetch>');

var crmInfo = new WebServiceStartInfo();
/* machine.domain:port */
crmInfo.Host = "http://moss.litwareinc.com:5555";
/* crm service default namespace */
crmInfo.XmlNS = "http://schemas.microsoft.com/crm/2007/WebServices";
/* crmservice url */
crmInfo.Asmx = "/mscrmservices/2007/crmservice.asmx"
crmInfo.Method = "Fetch";
/* include a CrmAuthentication Token */
crmInfo.CrmToken = true;
crmInfo.Async = true;
/* call this function when done */
crmInfo.Callback = OnEndRequest;
/* the Fetch method parameter */
crmInfo.Parameters.Add( "fetchXml" , fetchXml );

/* Assign the start info to the webrequest */
var request = new WebRequest( crmInfo );
request.Execute();

// --------------------------------------------------------------
/* this part calls the custom web method above */
var customInfo = new WebServiceStartInfo();
/* the webservice resided on the default website on port 80 */
customInfo.Host = "http://moss.litwareinc.com";
/* the default namespace I gave the WebService1 class */
customInfo.XmlNS = "http://www.mycompany.com/demo";
/* the webservice url */
customInfo.Asmx = "/MyAppServices/WebService1.asmx"
customInfo.Method = "Hello";
/* we don’t need a token since we are not calling crm services */
customInfo.CrmToken = false;
customInfo.Async = true;
customInfo.Callback = OnEndRequest;

request = new WebRequest( customInfo );
request.Execute();

// -------------------------------------------------------------
/*
since we already have a startinfo object I just changed the property
values to show more advance usage in this case the HelloUser method
receives a parameter.

Signature:
[WebMethod]
public string HelloUser( string username );
*/
customInfo.Method = "HelloUser";
customInfo.Parameters.Add( "userName" , "Adi Katz" );

request = new WebRequest( customInfo );
request.Execute();

// -------------------------------------------------------------
/*
The following method signature is comprised of a string array
[WebMethod]
public string HelloOnceMore( string[] Parameters );
*/
customInfo.Method = "HelloOnceMore";
var StringArray = customInfo.Parameters.Add( "Parameters" );
StringArray.Parameters.Add( "string" , "Adi" );
StringArray.Parameters.Add( "string" , "Katz" );
request = new WebRequest( customInfo );
request.Execute();

// -------------------------------------------------------------
/*
The following web method signature is comprised of a complex type
called ComplexParameter which is decorated with the [Serializable]
attribute and contains a FirstName and LastName.

public class ComplexParameter
{
public string FirstName;
public string LastName;
}

Signature:
[WebMethod]
public string HelloAgain( ComplexParameter complexParameter );

*/
customInfo.Method = "HelloAgain";
customInfo.Async = false;

var ComplexParameter = customInfo.Parameters.Add( "userInfo" );
ComplexParameter.Parameters.Add( "FirstName" , "Bill" );
ComplexParameter.Parameters.Add( "LastName" , "Gates" );

request = new WebRequest( customInfo );
var result = request.Execute();
alert(result.text);
}

function OnEndRequest( requestObj )
{
alert( requestObj.responseXML.text );
}

/* Holds all the information needed to call a webservice */
function WebServiceStartInfo()
{
/* WebMethod Name */
this.Method = "";
/* Server Name + Port */
this.Host = ""
/* The Default XML Namespace */
this.XmlNS = "";
/* The WebService Url */
this.Asmx = "";
/* Holds the soap envelop */
this.Soap = new StringBuilder();
this.Async = false;
/* Holds the function (pointer) to call if Async is true */
this.Callback = null;
/* Flag which specify whether to include the crm token */
this.CrmToken = false;
/* Holds the WebMothod parameters list */
this.Parameters = new ParameterCollection();
/* Returns a valid SoapAction */
this.GetSoapAction = function() {
return this.XmlNS + "/" + this.Method;
}
/* Gets the complete url to call */
this.GetUrl = function() {
return this.Host + this.Asmx;
}
/* return the entire soap envelop xml */
this.ToString = function()
{
/* clear the soap former calls */
this.Soap.Clear();

this.Soap.Append( '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' );
if( this.CrmToken )
this.Soap.Append( GenerateAuthenticationHeader() );

this.Soap.Append( '<soap:Body>' );
this.Soap.Append('<').Append(this.Method);
this.Soap.Append(' xmlns="').Append(this.XmlNS).Append('"');

if( this.Parameters.List.length > 0 )
{
this.Soap.Append('>');
for( var i = 0 ; i < this.Parameters.List.length ; i++ )
this.Soap.Append( this.Parameters.List[i].ToString() );
this.Soap.Append('</').Append(this.Method).Append('>');
}
else
{
this.Soap.Append(' />');
}

this.Soap.Append( '</soap:Body>' );
this.Soap.Append( '</soap:Envelope>' );

return this.Soap.ToString();
}
/* A collection of parameters */
function ParameterCollection()
{
/* Parameters list */
this.List = [];
/*
Adds a new parameter and returns a reference so
further child parameters can be added.

for example:
var ComplexParam = Object.Parameters.Add( "ComplexParameter" )
ComplexParam.Parameters.Add( "FirstName" , "Adi" );

Result Xml
<ComplexParam>
<FirstName>Adi</FirstName>
</ComplexParam>

Signature
[WebMethod]
public string MethodName( ComplexParam complexParam );
*/
this.Add = function( name , value )
{
var parameter = new Parameter( name , value );
this.List[ this.List.length ] = parameter;
return parameter;
}
}
/* A single parameter */
function Parameter( name , value )
{
/*
Holds either the Property Name or type if this is an array
for example:
var StringArray = Object.Parameters.Add( "StringArray" );
StringArray.Parameters.Add( "string" , "Adi" );
StringArray.Parameters.Add( "string" , "Katz" );

Result Xml
<StringArray>
<string>Adi</string>
<string>Katz</string>
</StringArray>

Signature:
[WebMethod]
public string MethodName( string[] StringArray );
*/
this.Name = name;
this.Value = value;
/* List of child parameters */
this.Parameters = new ParameterCollection();
/* Internal string builder */
this.Xml = new StringBuilder();

this.ToString = function()
{
this.Xml.Append('<').Append(this.Name).Append('>');
if( this.Value != null ) this.Xml.Append(this.Value);
else if( this.Parameters.List.length > 0 )
for( var i = 0 ; i < this.Parameters.List.length ; i++ )
this.Xml.Append( this.Parameters.List[i].ToString() );
this.Xml.Append('</').Append(this.Name).Append('>');
return this.Xml.ToString();
}
}
/*
Utility class that uses an array to append new strings
and uses the join method to concatenate them
*/
function StringBuilder()
{
/* String elements*/
this.Parts = [];
/*
Appends a new string into the array and
returns a reference to the String Builder so you can write:
Object.Append('String A').Append('String B');
*/
this.Append = function( text ) {
this.Parts[this.Parts.length] = text;
return this;
}
/* Re-Initialized the parts array for consequent calls */
this.Clear = function(){
this.Parts = [];
}
/* returns the parts as string */
this.ToString = function() {
return this.Parts.join("");
}
}
}

function WebRequest( startInfo )
{
var WebInfo = this;
/* Structure that holds the WebService information */
WebInfo.StartInfo = startInfo;
/* Executes the ws call */
WebInfo.Execute = function ( startInfo )
{
if( !isNullOrEmpty( startInfo ) )
WebInfo.StartInfo = startInfo;

var xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttp.open("POST", WebInfo.StartInfo.GetUrl() , WebInfo.StartInfo.Async ); //Sync Request
xmlHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttp.setRequestHeader("SOAPAction", WebInfo.StartInfo.GetSoapAction() );

if( WebInfo.StartInfo.Async )
{
/* Calls the callback */
xmlHttp.onreadystatechange = function() {
/*
Calls the user callback function only when the readystate is complete
so the user does not have to check the readyState him self.
*/
if( xmlHttp.readyState != 4 ) return;
WebInfo.StartInfo.Callback( xmlHttp )
} ;
/* Execute */
xmlHttp.send( WebInfo.StartInfo.ToString() );
}
else
{
xmlHttp.send( WebInfo.StartInfo.ToString() );
return xmlHttp.responseXML;
}
}

function isNullOrEmpty( o ){
return o == null || typeof( o ) == "undefined";
}
}

OnCrmPageLoad();

2 comments:

crm services said...

This was really good.CRM services breaks the major application with the simple strategy and the program was really amazing. thanks for a good review on CRM services. keep on updating.Crm Services

sathish said...

Thanks for the sharing of such information.This is a great stuff of reading. I will pass it on to our audience. Thanking you,CRM Services