sábado, 12 de septiembre de 2009

Utilizando conexiones compartidas (UnsafeAuthenticatedConnectionSharing)

En este artículo intentaré explicar un poco como funcionan las peticiones que realizamos a los Web services de CRM, y como podemos "compartir" conexiones para que estas sean mas rápidas.
En primer lugar, al realizar una petición a un web service de CRM, hay que pasarle las credenciales de red del usuario con el que queremos realizar esta petición.
El IIS del CRM entonces lo primero que realiza es abrir una conexión con esas credenciales, antes de ejecutar el propio código del Web Service.
Por cada una de las peticiones que realizamos entonces el IIS hace:
1) Crear y abrir la conexión con las credenciales de red.
2) Ejecutar el método del web service y dar la respuesta correspondiente.
Mediante la propiedad UnsafeAuthenticatedConnectionSharing se podrá evitar que se realice el primer paso por cada una de las llamadas, por lo tanto se ejcutarán de forma mas rápida.
La forma de utilizar este método es la siguiente:

CrmService _localService = new CrmService();
_localService.Credentials = _crmService.Credentials;
_localService.Url = _crmService.Url;

_localService.UnsafeAuthenticatedConnectionSharing = true;

De esta forma, al realizar una petición con el usuario "A", una vez ejecutado el web service, la conexión se mantendrá abierta en vez de cerrarse como haría normalmente. Entonces luego al ejecutarse el con el usuario "B", este accederá con las credenciales de la conexión previamente por el usuario "A".

Obviamente, este código solo debe utilizarse en caso de nuestro desarrollo deba "impersonalizar" la ejecución, ya que sino estaríamos perdiendo el usuario de contexto que ejecuta los Web Services, y adicionalmente
Adicinalmente se deberán tener en consideracion las posibles peligros de seguridad que este funcionamiento puede provocar.

Para más información acerca de esto recomiendo este artículo:
HttpWebRequest.UnsafeAuthenticatedConnectionSharing Property

jueves, 10 de septiembre de 2009

Web services de CRM desde Javascript

Muchas veces necesitamos desde el propio formulario del CRM acceder al servidor a través del Javascript.
Como todos sabemos, es posible acceder a los web services desde el javascript de los formularios. Lo interesante es que además de ser posible, es una funcionalidad totalmente soportada por CRM, así que vamos a utilizarla!
La documentación en la SDK esta aquí: Accessing Microsoft Dynamics CRM Web Services
Ahora intentaré poner algunas funciones en Javascript que pueden ser de gran utilidad para dar mas "Vida" a nuestras implantaciones de CRM.

La siguiente función nos devuelve en un "Alert()" el Guid del Usuario, de la Unidad de negocio y de la organización:

function GetCurrentUserInfo()
{
var SERVER_URL = "http://localhost";
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
xmlhttp.open("POST", SERVER_URL + "/mscrmservices/2007/crmservice.asmx", false);
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");

var soapBody = ""+
""+
""+
"
";

var soapXml = " "xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' "+
"xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "+
"xmlns:xsd='http://www.w3.org/2001/XMLSchema'>";

soapXml += GenerateAuthenticationHeader();
soapXml += soapBody;
soapXml += "
";

xmlhttp.send(soapXml);
xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async=false;
xmlDoc.loadXML(xmlhttp.responseXML.xml);

var userid = xmlDoc.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
var buid = xmlDoc.getElementsByTagName("BusinessUnitId")[0].childNodes[0].nodeValue;
var orgid = xmlDoc.getElementsByTagName("OrganizationId")[0].childNodes[0].nodeValue;

alert("UserId: " + userid + "\r\nBusinessUnitId: " + buid + "\r\nOrganizationId: " + orgid);

}

La siguiente función dispara un Workflow desde Javascript:

ExecuteWorkflow = function(entityId, workflowId)
{
var xml = "" +
"" +
"" +
GenerateAuthenticationHeader() +
" " +
" " +
" " +
" " + entityId + "" +
" " + workflowId + "" +
"
" +
"
" +
"
" +
"
" +
"";

var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/Execute");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
var resultXml = xmlHttpRequest.responseXML;
return(resultXml.xml);
}

/* la llamada a la función*/
var theWorkflowId = "3FD2DD58-4708-43D7-A21B-F0F90A0AA9F2"; //Id del workflow
ExecuteWorkflow(crmForm.ObjectId, theWorkflowId);

La siguiente función recoge un valor de un atributo de una entidad pasándole su Guid:

alert(GetAttributeValueFromID("systemuser", "09DF2AB7-E16D-DD11-88F3-0003FF884968", "internalemailaddress", "systemuserid"));

function GetAttributeValueFromID(sEntityName, sGUID, sAttributeName, sID)
{
var xml = "" +
"" +
"" +
GenerateAuthenticationHeader() +
" " +
" " +
" " +
" "+sEntityName+"" +
" " +
" " +
" "+sAttributeName+"" +
"
" +
"
" +
" false" +
" " +
" 1" +
" 1" +
"
" +
" " +
" And" +
" " +
" " +
" "+sID+"" +
" Equal" +
" " +
" "+sGUID+"" +
"
" +
"
" +
"
" +
"
" +
"
" +
"
" +
"
" +
"
" +
"";

var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");

xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);

// obtiene la respuesta y busca el valor del atributo
var result = xmlHttpRequest.responseXML.selectSingleNode("//q1:" + sAttributeName);
if (result == null)
return "";
else
return result.text;
}

Estas funciones de javascript las he recogido desde el Blog de Jim Wang's, que creo que es uno de los que mejores ejemplos de código de javascript tiene publicados en su blog.

Un abrazo y espera les sirvan,

miércoles, 9 de septiembre de 2009

Error al imprimir informes "Unable to load client print control"

Este error ocurre cuando se ejecuta un informe desde Microsoft Dynamics CRM 4.0 y una vez abierto, se pincha en el botón de "Imprimir".
El error que aparece tiene el siguiente aspecto:

Este problema ocurre cuando se instala la actualización de SQL Server 2005 SP2 (GDR).
Se soluciona simplemente instalando el Report Viewer Redistributable 2005 Service Pack 1
y reiniciando el servidor (si no se reinicia puede ser que no se solucione).
El Artículo de soporte de Microsoft que documenta esta incidencia es el siguiente:
http://support.microsoft.com/kb/961994/en-us

Un saludo,

martes, 8 de septiembre de 2009

Pestañas desde Javascript

En este artículo explicaré un par de formas de trabajo con las pestañas desde el propio javascript del formulario.
Las pestañas en el formulario tienen nombres con un numero que se incrementa de la siguiente forma: "tab0Tab", "tab1Tab", "tab2Tag", etc.
Utilizando el metodo document.getElementById podemos recoger el objeto y utilizarlo. Pongo un par de ejemplos:

Seleccionar una pestaña:

var tabIndex = 2; //Tercera pestaña
var tab = document.getElementById("tab" + tabIndex + "Tab");
if( tab ) tab.click();

Ocultar una pestaña:

var tabIndex = 1; //Segunda pestaña
var tab = document.getElementById("tab" + tabIndex + "Tab");
if( tab ) tab.style.display = 'none'; //'inline' si se quiere mostrar

jueves, 3 de septiembre de 2009

Descarga de adjuntos desde SQL Server

La forma soportada de descargar ficheros documentada en la SDK de CRM 4.0 nos obliga a una petición por cada uno de los ficheros adjuntos que deseamos descargar, lo cual podría ser bastante lento.
Por eso he intentado buscar alguna forma no soportada de realizarlo, ya que al final los ficheros en CRM están almacenados en el SQL Server.
Los adjuntos se encuentran en el campo "DocumentBody" de la tabla "Annotation" y están almacenados en campos de tipo "text" en formato Base64.
Para accededer a los mismos hay que hacer una consulta SQL como la siguiente:

SELECT FileName,FileSize, DocumentBody
FROM FilteredAnnotation
WHERE FileName is not null
AND ObjectId=<Guid de la entidad>

Así, una vez recogidos los resultados de la consulta, hay que convertir esos ficheros en Base64 a ficheros físicos en el disco duro.

Byte[] filebytes = Convert.FromBase64String(Registro["DocumentBody"].ToString());
FileStream fs = new FileStream("C:\\Adjuntos\\" + Registro["FileName"].ToString() , FileMode.CreateNew, FileAccess.Write, FileShare.None);
fs.Write(filebytes, 0, filebytes.Length);
fs.Close();

En este ejemplo de código "Registro" es un DataRow, que se ha recogido de hacer la consulta SQL anterior, y los ficheros son almacenados en la carpeta "C:\Adjuntos\".

un saludo

miércoles, 2 de septiembre de 2009

Mostrar cantidad de Actividades/Historial en el formulario

He encontrado en el Blog de Jim Wang un pequeño desarrollo que me parece bastante útil.
Es simplemente un Javascript que sirve para que en el mismo "onLoad()" del formulario, nos muestre la cantidad de registros relacionados de Actividades (tanto programadas como cerradas).

Así es como se ve finalmente así por ejemplo en el formulario de "Contactos":


Lo único que hay que hacer es pegar el siguiente código en el "Onload()" de la entidad que se desea:

var buXml = GetRegardingActivity();

if(buXml != null)
{
var buNodes = buXml.selectNodes("//BusinessEntity/q1:statecode"); // CRM 4.0
var iActivity = 0;
var iHistory = 0;
if (buNodes != null) { /*get values*/

for (i = 0; i < buNodes.length; i++) {
switch (buNodes[i].text) {
case "Abierto": iActivity++; break;
case "Programado": iActivity++; break;
case "Completado": iHistory++; break;
case "Cancelado": iHistory++; break;
}
}
if (document.getElementById('navActivities') != null)
{
document.getElementById('navActivities').getElementsByTagName('NOBR')[0].innerText = document.getElementById('navActivities').getElementsByTagName('NOBR')[0].innerText + " (" + iActivity + ")";
}
if (document.getElementById('navActivityHistory') != null) {
document.getElementById('navActivityHistory').getElementsByTagName('NOBR')[0].innerText = document.getElementById('navActivityHistory').getElementsByTagName('NOBR')[0].innerText + " (" + iHistory + ")";
}
}
}
function GetRegardingActivity() {
var xml = "" + "" + "" + " " + " " + " activitypointer" + " " + " " + " statecode" + " " + " " + " false" + " " + " And" + " " + " " + " regardingobjectid" + " Equal" + " " + " " + crmForm.ObjectId + "" + " " + " " + " " + " " + " " + " " + "" + "";
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2006/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2006/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
var resultXml = xmlHttpRequest.responseXML;
return resultXml;
}

Como se ve en el código, hace una llamada a los Web Services de CRM, para buscar en la entidad "ActivityPointer" todas las actividades que tiene relacionadas el registro en el que nos encontramos ("crmForm.ObjectId").
Luego, recorre los resultados para contar cuantas de esas actividades estan abiertas (iActivity) y cuantas estan completadas (iHistory).

Finalmente, hace algo "NO Soportado" como es el de "inyectar" html en el formulario de CRM para que muestre las cantidades entre paréntesis.

Espero les sirva.

un saludo

martes, 1 de septiembre de 2009

Curso Online introductorio de desarrollo sobre Dynamics CRM

En el último boletín MSDN Flash de esta semana han comunicado de un nuevo curso online sobre desarrolo para Microsoft Dynamics CRM.
Lo ha realizado el MVP de Microsoft Daniel Sabater y la verdad que me pareció muy bueno, tanto para personas que estan empezando con el desarrollo en Microsoft CRM como también para gente con mas experiencia en la plataforma.

Está publicado en la Web de Microsoft como un evento de tipo "WebCast a Petición" y la dirección URL de acceso es la siguiente:Curso introductorio de desarrollo sobre Dynamics CRM

Además da la opción de poder descargárselo completo (alrededor de 80 MB), tiene videos y ejemplos.

Un saludo,