viernes, 25 de septiembre de 2009

Como incluir ficheros javascript externos (.JS) en formularios

Una crítica que solemos hacer los que trabajamos mucho con el CRM de Microsoft, es relacionado con el editor de Javascript que tiene el CRM.
Es pequeño, sin tabulaciones, y para probar nuestros cambios, necesitamos entrar en la entidad, y probar todo allí, o si queremos verlo con datos reales, publicarlo y luego verlo en real.
Una pequeña ayuda, aunque no soportada, es la de incluir un fichero externo con todo nuestro Javascript en el "Onload" del formulario, y luego trabajar simplemente con dicho Javascript mediante Visual Studio (un editor de verdad).
Así no tendremos más incluir el siguiente código en el Onload de la entidad que deseamos:

var script = document.createElement('script');
script.language = 'javascript';
script.src = 'http://localhost/EntidadLoad.js';
script.onreadystatechange = OnScriptReadyState;
document.getElementsByTagName('head')[0].appendChild(script);

function OnScriptReadyState()
{
if (event.srcElement.readyState == "loaded" || event.srcElement.readyState == "complete")
{
EjecutoLoad(); // una funcion del fichero "EntidadLoad.js"
}
}

Al cargar un Javascript "en caliente" debemos controlar que antes de llamar a cualquier función dentro de dicho Javascript, el mismo se haya terminado de descargar, ya que sino podemos provocar un error de Javascript. Por eso debemos controlar el evento OnScriptReadyState.
De esta forma podriamos crearnos un javascript (en este caso "EntidadLoad.js") por cada "Load()" y utilizarlo en cada entidad.

Si intentamos hacer algo más genérico, podríamos también crear un solo javascript genérico para todas las entidades, al cual siempre le pasaremos el nombre de la entidad de que se trate, para que así no tengamos muchos ficheros "Js", uno por cada entidad. Simplemente añadiríamos a la llamada a la funcion "EjecutoLoad();" el parámetro del nombre de la entidad y luego controlar dicho parámetro en nuestro fichero js:

EjecutoLoad(crmForm.ObjectTypeName);

Con la inclusión de javascript debemos tener en cuenta, que los mismos no se incluirán en una posible "exportación/importación" de personalizaciones, por eso, recomiendo utilizar esto solo en un servidor de desarrollo, para luego subirlo correctamente en producción.

Espero les pueda ser de utilidad,
Un abrazo

Dynamics CRM "Statement of direction"

Microsoft a publicado un artículo que considero que es bastante interesante en relación con el estado actual de Microsoft Dynamics CRM 4.0 y cuales son las lineas a seguir la la próxima versión que verá la la luz en 2010.
En este artículo nos muestra información relacionada con Microsoft CRM y mobilidad, acceleradores e integraciones con otras soluciones de Microsoft.
Pero lo que considero mas interesante a tener en cuenta son los puntos que aparecen en el capítulo de "Microsoft Dynamics CRM “V.Next”", donde nos aparecen los puntos que tendrán en cuenta para la próxima versión de CRM:
  • Mejorar la funcionalidad del cliente de Outlook para CRM

  • Utilización del menú "ribbon" como el de Office

  • Reducción del número de ventanas que se abren y el número de clicks

  • Integración con Sharepoint

  • Mejora de las herramientas para el análisis y explotación de la información

  • etc...

  • Este PDF pueden descargárselo de aquí:
    PartnerSource: https://mbs.microsoft.com/partnersource/marketing/statementofdirection/MD_CRM_SOD.htm
    o de aquí:
    CustomerSource: https://mbs.microsoft.com/customersource/documentation/whitepapers/MSD_CRM4StatementOfDirection.htm

    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,