domingo, 27 de diciembre de 2009

Autenticación en un ASPX con el acceso IFD activado

Al activar el acceso a través de IFD (Internet Facing Deployment) al CRM, lo que se permite es que los usuarios accedan al CRM a través de la autenticación por formulario. De esta forma, al acceder al CRM en vez de pedir la típica ventana de autenticación del directorio activo, aparece un formulario web, en donde hay que introducir usuario y contraseña. Esto suele utilizarse cuando se va habilitar acceso al CRM a través de internet desde diferentes ubicaciones.
Para ver como configurar el acceso a través de IFD recomiendo ir a:
http://support.microsoft.com/kb/948779

Una vez configurado y activos el acceso por IFD, se produce un cambio muy importante en el IIS (Internet Information Services) del servidor web, que habilita el acceso anonómimo al sitio web. De esta forma, el usuario que ejecuta la aplicación, es el usuario anónonimo y no el usuario conectado a traves de las credenciales del directorio activo.

Esto trae como consecuencia, que cualquier desarrollo web nuestro (formularios aspx) en los cuales nos hemos conectado a los web services de CRM mediante las "DefaulCredentials", provocará un error de autenticación ya que el usuario anónimo no es usuario de CRM.
Veamos como sería la autenticación en una implantación "On Premise" (sin IFD):

CrmService crmService = new CrmService();
crmService.Url = orgInfo.CrmServiceUrl;
crmService.CrmAuthenticationTokenValue = token;
crmService.Credentials = System.Net.CredentialCache.DefaultCredentials;

account account = new account();
account.name = "Nueva cuenta: " + DateTime.Now.TimeOfDay.ToString();
service.Create(account);

Como puede verse, simplemente se recogen las "DefaultCredentials" que son las del usuario conectado, ya que al no estar activado el acceso por IFD, el sitio web recoge las credenciales del usuario conectado del directorio activo.

En caso de activar el acceso por IFD, esas "DefaultCredentials" devuelven al usuario anónimo, por lo tanto da un error de autenticación.
La forma de hacer lo mismo en una implantación con IFD activado sería:

using (new CrmImpersonator())
{
CrmAuthenticationToken token;
token = CrmAuthenticationToken.ExtractCrmAuthenticationToken(Context, orgname);
token.OrganizationName = orgname;
token.AuthenticationType = 0;

CrmService service = new CrmService();
service.Credentials = System.Net.CredentialCache.DefaultCredentials;
service.CrmAuthenticationTokenValue = token;
service.Url = crmurl;

account account = new account();
account.name = "Nueva cuenta: " + DateTime.Now.TimeOfDay.ToString();
service.Create(account);
}

El "CrmImpersonator" lo que hace es que todo el código dentro del "using" se ejecute con el entorno del usuario conectado (usuario de CRM) y no con el usuario anónimo. De hecho, si quisiésemos recoger el id del usuario del CRM lo tenemos utilizando rl "token.CallerId".
De esta forma funcionaría nuestro código con IFD activado. De hecho este código funciona tando con "IFD" como con "On Premise", con lo cual sugiero que en vuestros desarrollos web, intenten utilizar este tipo de código, y que lo prueben bien, ya que nuestros desarrollos web pueden cambiar sus funcionamientos en caso de acceder a traves de IFD.

Espero les haya servido,

un abrazo

lunes, 21 de diciembre de 2009

Han publicado el Rollup 8

El pasado jueves 17, han publicado una nueva actualización para el CRM 4.0, (y ya van 8!).
Para descargarlo:
http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c53b2916-6b93-4092-bdd3-a394c96ca000
Creo que el principal cambio recae en que ahora el CRM funcionará correctamente con Microsoft Office 2010, y el Email Router está totalmente compatible con Microsoft Exchange 2010.

Como siempre, recomiendo antes de instalarlo en producción, probarlo bien y a fondo en especial con nuestros desarrollos para estar seguros que esta actualización no nos vaya a "romper" nada.

un saludo

domingo, 20 de diciembre de 2009

Mostrando las entidades relacionadas en el formulario

A veces necesitamos que las vistas con la información relacionada de una entidad, aparezcan en el mismo formulario (en un IFRAME) sin tener que ir al menú de la izquierda.
Existe una forma mas o menos no soportada de hacer esto, lo voy a mostrar con un ejemplo recogido del Blog de Jian Wang, que creo que es la mejor forma de hacerlo.
Bien, lo primero que hay que hacer es crear en el formulario un IFRAME por ejemplo con el nombre "IFRAME_view".
Luego debemos copiar el siguiente código en el evento OnLoad del formulario:

var navId = "nav_new_new_myentity_account";

if(document.getElementById(navId) != null)
{
var tmp = document.getElementById(navId).onclick.toString();
tmp = tmp.substring(tmp.indexOf("'")+1, tmp.indexOf(";"));
var loadArea = tmp.substring(0, tmp.indexOf("'"));
var roleOrd = (tmp.indexOf("roleOrd") == -1) ? -1 : tmp.substring( tmp.indexOf("roleOrd"), tmp.lastIndexOf("'")).replace("\\x3d", "=");
crmForm.all.IFRAME_view.src = (roleOrd == -1) ? GetFrameSrc(loadArea) : GetFrameSrc(loadArea) + "&" + roleOrd;

}

function GetFrameSrc(tabSet)
{
if (crmForm.ObjectId != null)
{
var id = crmForm.ObjectId;
var type = crmForm.ObjectTypeCode;
var security = crmFormSubmit.crmFormSubmitSecurity.value;
var path = document.location.pathname.substring(0, document.location.pathname.indexOf("edit.aspx")) + "areas.aspx?";

return (path + "oId=" + id + "&oType=" + type + "&security=" + security + "&tabSet=" + tabSet);
}
else
{
return "about:blank";
}
}

Lo unico que hay que modificar de este código para que funcione, es simplemente modificar el nombre de la variable "navId" (en la primera línea) con el nombre de la relacion que se desea.
Si tienen dudas en relación con el nombre, pueden ver el "id" del objeto HTML que tiene el enlace de la parte izquierda del formulario. Para recoger ese id de forma sencilla, se puede utilizar el depurador de HTML que tiene el Internet Explorer 8 (presionando F12), y así pudiendo seleccionar dicho enlace y ver su "id".

un saludo

sábado, 12 de diciembre de 2009

Cambios en descargas de adjuntos con Rollup 7

A veces nos preguntamos por que la vida y evolución del CRM nos hace la vida tan difíciles, por ejemplo sacando "Update Rollups" cada 2 meses y obligándonos a estar a la última.
En general estas actualizaciones no hacen cambios estructurales importantes ni afectan a nuestros desarrollos, pero ya me ha ocurrido mas de una vez que sí afectan funcionamientos, que incluso pueden no estar documentados.
Siempre intento publicar los últimos Rollups ni bien salen, ya que es interesante estar al día y con los últimos "bugs" arreglados, pero como siempre digo, estas actualizaciones deben ser probadas mil y una veces en servidores de desarrollo y pre-producción antes de su subida final a servidores de producción.
Bien, en este caso voy a relatar lo ocurrido con el último Rollup 7 del CRM en relación con el download de ficheros adjuntos del CRM a través de la SDK.
La cronología de lo ocurrido ha sido la siguiente:
1) En Octubre de 2009 sale la versión de SDK de 4.0.10 (desde junio que no se actualizadaba).
2) El 5 de noviembre se publica el "Update Rollup 7" de CRM 4.0.
3) El 24 de noviembre se publica una nueva versión de la SDK 4.0.11 (sólo un mes despues de la anterior).

Mi sorpresa surgió cuando ví que en la nueva SDK habia una sola modificación, en relación a como se descargan los ficheros desde el CRM. Y al lado el siguiente mesaje: "Updated the sample code for downloading an attachment. This new method works with the security enhancements added to Microsoft Dynamics CRM 4.0 Update Rollup 7." (aclarando que dicho documento solo funciona con el Rollup 7)

Bien, lo que ha ocurrido realmente es que han modificado la manera de descargarse ficheros con el Rollup 7 (ya que la forma anterior ya no funciona correctamente), y por esto han tenido que sacar una actualización de la SDK.

La forma antigua para descargar un fichero que funcionaba hasta el Rollup 7 era la siguiente:

string attachid = "{61BB601D-C43F-4738-BD1F-AD22DC8E7F0E}";
string objecttypecode = "1070"; //SaleLiteratureItem
string url = "http://mycrm/Activities/Attachment/download.aspx?AttachmentType=" + objecttypecode + "&AttachmentId=" + attachid;
System.Net.WebClient myWebClient = new System.Net.WebClient();
myWebClient.Credentials = System.Net.CredentialCache.DefaultCredentials;
myWebClient.DownloadFile(url,"C:\\myfile.txt");

Y la nueva forma documentada en la SDK 4.0.11 es:

Guid annotationId = setupAnnotationId;
// Define the columns to retrieve from the annotation record.
ColumnSet cols1 = new ColumnSet();
cols1.Attributes = new string[] { "filename", "documentbody" };
// Retrieve the annotation record.
annotation annotationAttachment = (annotation)service.Retrieve(EntityName.annotation.ToString(), annotationId, cols1);
// Download the attachment in the current execution folder.
using (FileStream fileStream = new FileStream(annotationAttachment.filename, FileMode.OpenOrCreate))
{
byte[] fileContent = new UTF8Encoding(true).GetBytes(annotationAttachment.documentbody);
fileStream.Write(fileContent, 0, fileContent.Length);
}

A pesar de que esto podría parecer que ya está solucionado, no es así, ni mucho menos. Este código que han sacado para la SDK nueva, es incorrecto y no funciona!
Para hacer que funcione el código correcto para descargar ficheros con el Rollup 7 es el siguiente:

Guid annotationId = setupAnnotationId;
ColumnSet cols1 = new ColumnSet();
cols1.Attributes = new string[] { "filename", "documentbody" };
annotation annotationAttachment = (annotation)service.Retrieve(EntityName.annotation.ToString(), annotationId, cols1);
using (FileStream fileStream = new FileStream(annotationAttachment.filename, FileMode.OpenOrCreate))
{
//la siguiente línea es la que estaba mal en la SDK que debería ser:
byte[] filebytes = Convert.FromBase64String(annotationAttachment.documentbody);

fileStream.Write(filebytes, 0, filebytes.Length);
}

Realmente el error recae en que "annotationAttachment.documentbody" contiene el texto en base64 del fichero que es el mismo campo que esta almcenado en la base de datos (ver mi artículo sobre el tema: Descarga de adjuntos desde SQL Server).

Bueno, como siempre digo, cuidado con las actualizaciones, estar al día con todo (SDKs, Rollups, etc) he informarse de los cambios, es la mejor práctica para evitar posibles dolores de cabeza.

Espero les sea útil...

un saludo

miércoles, 9 de diciembre de 2009

Como ocultar la barra de navegación de y botonera del formulario de CRM

Es posible que por temas de espacio o de funcionalidad deban tener que ocultar ciertas partes del formulario de CRM.
A pesar de que es algo NO SOPORTADO, los usuarios por un lado y las limitaciones del CRM a veces nos obligan a hacer este tipo de cosas.
Para ocultar esto les dejo un ejemplo de código que hay que copiar en el OnLoad del formulario de CRM:

if (crmForm.FormType!=1)//en la creación dejo todo visible
{
//Oculta la barra de menu (botonera superior):
document.all.crmMenuBar.style.display="none";

//Oculta la barra de navegacion (menú de la izquierda)
document.body.firstChild.firstChild.firstChild.width=1;
}

Si lo ponemos por ejemplo en el Contacto, el formulario quedaría de la siguiente forma (a que es mas bonito así??) :


Se ve mas como una "Ficha" de Contacto, sin acciones que hacer, solo acceso a la información.

martes, 8 de diciembre de 2009

Como evitar el mensaje de confirmación de cierre

En algunos casos, necesitamos que los formularios de CRM se cierren sin confirmación mediante la "X" del explorador, a pesar de que se hayan modificado atributos y no se haya guardado.
Como saben, es una funcionalidad estándar del CRM el avisarte con un mensaje de confirmación de si está seguro que quiere cerrar la ventana y perder los datos no guardados.
Para esto creo que es necesario comentar un poco como funciona "internamente" el javascript del formulario del CRM.
En primer lugar lo que hace es que en el evento "onbeforeunload" del fomulario, verifica si el formulario esta "sucio" (crmForm.IsDirty).
Esa función lo que hace es verificar si alguno de los atributos del formulario está "sucio" (recorriendo uno por uno), con que uno solo esté sucio, el crmForm.IsDirty devolverá "true".
Para verificar si un atributo está "sucio", simplemente verifica el valor actual de cada uno de los atributos, comparándolos con el valor que tenían al abrir el formulario ("load" del formulario).
Bien, ahora una aclaración en relación con este funcionamiento: SOLO VERIFICA SI SE HAN MODIFICADO ATRIBUTOS QUE ESTÉN "HABILITADOS".
De esta forma, si quisiésemos que el formulario se cierre sin advertencia, lo que deberíamos de hacer, es simplemente deshabilitar todos los atributos antes de que se se ejecute el "onbeforeunload" del CRM.
Para esto lo que hay que hacer es pegar este código en el evento OnLoad del formulario:

window.onbeforeunload = function()
{
var atributos=new Array();
atributos[0]="new_atributo1";
atributos[1]="new_atributo2";
atributos[2]="new_atributo3";
atributos[3]="new_atributo4";

deshabilito(atributos);
}


/************* FUNCIONES Adicionales **************/
function deshabilito(atributos)
{
for (attr in atributos)
{
crmForm.all[atributos[attr]].Disabled=true;
}
}
/*********** FIN FUNCIONES Adicionales **************/

Obviamente que es una modificación no soportada, pero que por ahora, funciona correctamente.
Simplemente lo que hace el código es deshabilitar los atributos que le digamos, antes de verificar si hay algo pendiente de guardar, entonces, cuando luego lo verifique, no habrá nada pendiente de guardar y el formulario se cerrará sin mas.

lunes, 7 de diciembre de 2009

Como hacer que un flujo de trabajo espere un tiempo determinado

En los flujos de trabajo, se puede hacer que en determinado paso, se quede esperando por una determinada cantidad de tiempo.
Hay un forma fácil de hacer, que sería decirle que espere hasta que se llegue hasta una día/hora determinado en un atributo de la entidad.
Pero muchas veces realmente lo que simplemente necesitamos es que el flujo de trabajo se espere por ejemplo 60 minutos, independientemente de los atributos.
En primer lugar debemos seleccionar una condición de espera:


Luego al definir el tiempo seleccionar Flujo de trabajo/Tiempo de espera/Es igual a/Duración (en "Valores dinámicos"):


Un saludo

domingo, 29 de noviembre de 2009

Personalizando el Mapa del sitio (SiteMap)

Microsoft dynamics CRM, nos da la posibilidad de personalizar el Mapa del sitio, de forma que podamos dejarlo de forma que mas nos interese para las necesidades de los clientes, y ademas para "facilitar" la vida a los usuarios para que solo puedan ver lo que necesitan, ocultando según sus roles lo innecesario.
Los enlaces aparecen en "Groups" y "SubAreas" como lo siguiente:






Para poder ocultar una "SubArea", primero voy a explicar como funciona la visibilidad de las mismas. En primer lugar hay que tener en cuenta que solo se mostrarán aquellas SubAreas que tenga permiso el usuario según sus roles de seguridad asignados. Si el usuario puede ver esa entidad, entonces el CRM verifica si hay "Privilegios" adicionales relacionados con la SubArea. Estos privilegios adicionales, nos dan la posibilidad de ocultar o mostrar una SubArea dependendiendo de los permisos que tenga el usuario.
Si quisiésemos ocultar por ejemplo las "Cuentas" para que sólo lo puedan ver los Administradores, un ejemplo sería este (solo les aparecerá a aquellos que puedan crear usuarios):





Por otro lado también hay "Areas", como por ejemplo Area de trabajo, Ventas, Marketing, Configuración, etc.
Estas áreas solo aparecerán si hay SubAreas dentro para mostrar. De esta forma, si quiesiésemos ocultar un Área, simplemente lo que tenemos que hacer es ocultar todas las SubAreas de hay dentro.
Muchas veces necesitamos también crear una nueva Área, con una serie de enlaces dentros (por ejemplo aplicaciones propias para hacer consultas al ERP)
Voy a dejar un ejemplo de como sé haría esto:
















(se deben reemplazar los tags "Titl_e" por "Title" y "Are_a" por "Area")

De esta forma, podríamos ocultar todo lo que nos interese y dejar el mapa del sitio totalmente modificado.

Para personalizar el SiteMap, recomiendo utilizar las "Stunnware Tools 4.0" (http://www.stunnware.com/).

un saludo

jueves, 26 de noviembre de 2009

Ha salido una nueva versión de la SDK (4.0.11)

Ahora parece que el equipo del CRM de Microsoft está a tope actualizando la SDK (la versión anterior 4.0.10 salió en octubre).
La nueva versión no parece tener cambios importantes, salvo un par de artículos, pero igual como siempre recomiendo tener la última.

Una cosa interesante de esta nueva SDK es un documento con el nombre "Download an Attachment" con un ejemplo de como descargar un fichero adjunto en una "Nota".

Descárguenla desde aquí: http://www.microsoft.com/downloads/details.aspx?FamilyID=82E632A7-FAF9-41E0-8EC1-A2662AAE9DFB&displaylang=en

Un abrazo!

martes, 24 de noviembre de 2009

Foros técnicos de Microsoft CRM

Hola, este post es para recordar que existen una serie de foros técnicos en castellano en donde se pueden plantear dudas, problemas, incidencias, etc. a la "comunidad" de CRM.
Recomiendo utilizar estos foros, la verdad que mucha gente ve resuelta sus dudas de esta forma.
Yo suelo "pasearme" por ellos a ver que hay, y si puedo, intento participar.
Los foros son:

  • http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.es.crm&cat=es_ES_4d003c3d-4b24-4d73-9698-e6970c115e19&lang=es&cr=ES


  • http://social.microsoft.com/Forums/es-ES/crmspanish/threads


  • http://social.technet.microsoft.com/forums/es-ES/dynamicses/threads/


  • Un saludo!

    sábado, 21 de noviembre de 2009

    Usuarios en CRM y directorio activo

    En las implantaciones de CRM, a veces surgen problemas relacionados con los usuarios.
    Los usuarios de CRM deben primero estar creados previamente en el directorio activo del dominio donde esté el CRM.
    Los pasos para crear un nuevo usuario serían:
    1) Crear el usuario en el directorio activo.
    2) Dar de alta el usuario en el CRM.
    3) Añadirle los roles de seguridad que se deseen.

    Hay ciertas consideraciones que deben ser tenidas en cuenta a la hora de la gestión de usuarios en CRM:
  • Cuando se crea un nuevo usuario en CRM, el CRM almacena el "id" interno del directorio activo. El "enlace" entre un usuario de CRM y uno de directorio activo, no se realiza a traves del inicio de sesión, sino a través de dicho "id".

  • Al crear un usuario nuevo, para que pueda acceder debe tener en sus roles, el permiso de "modificar" su configuración de usuario. Esto es así porque al acceder al CRM por primera vez, se debe poder modificar su configuración.

  • Nunca eliminar el usuario en el directorio activo, esto provocará que un usuario habilitado en CRM, no pueda acceder al CRM, y ademas quedará "desenlazado" con el directorio activo.

  • En caso de modificar el inicio de sesión en el directorio activo, el usuario podrá seguir accediendo al CRM y funcionando correctamente. Lo único que si que fallaría serían las consultas a las FilteredViews realizadas con ese usuario, ya que ahí si que lo relaciona por el inicio de sesión.

  • En caso de que se haya modificado el inicio del sesión del directorio activo, se puede acceder al formulario de CRM del usuario en cuestión y actualizar el atributo de inicio de sesión, para que se actualice con el nuevo valor. Primero hay que introducir el inicio de sesión de un usuario cualquiera del directorio activo (que no esté en CRM), guardar, y luego introducir en inicio de sesión correcto y guardar.

  • En relación con las bajas de usuarios, lo que recomiendo es que nunca se eliminen los usuarios del directorio activo. En caso de producirse una baja, lo ideal sería deshabilitar el usuario de CRM y luego desactivarlo del directorio activo.


  • un saludo

    miércoles, 18 de noviembre de 2009

    Trabajando con atributos deshabilitados y concatenaciones

    En este artículo intentaré comentar como trabajar con atributos deshabilitados en los formularios de CRM.
    Para esto lo explicaré con un ejemplo práctico que suele ocurrir mucho en la vida de los proyectos en CRM.
    Muchas veces nos vemos en la necesidad de crear entidades para hacer relaciones N-N (entidades intermedias de relación). Un ejemplo sería si tenemos una entidad de "Paises" y queremos relacionar varios paises a las cuentas para determinar el volumen de ventas de cada cliente por paises.
    En esta entidad intermendia "Ventas por paises", hay un atributo que es nombre de la entidad "new_name", dicho nombre realmente no tiene mucho sentido, pero es recomendable rellenarlo con algun valor, por ejemplo "Nombre de la cuenta" - "Pais", para que quede algo descriptivo en el nombre del registro.
    Para hacer esto deberiamos introducir en el evento "OnChange()":

    var oCuenta=crmForm.all.new_accountid.DataValue;
    var oPais=crmForm.all.new_paisid.DataValue;

    var sCuenta="";
    var sPais="";

    if (oCuenta!=null) sCuenta=oCuenta[0].name;
    if (oPais!=null) sPais=oPais[0].name;

    crmForm.all.new_name.DataValue=sCuenta + " - " + sPais;

    Además, para dejarlo mas completo, vamos a añadir en el "OnLoad()" y en el "OnSave()" lo siguiente:

    crmForm.all.new_pais.FireOnChange();

    Adicionalmente, el atributo "new_name" lo vamos a dejar deshabilitado en el formulario.
    Esto funcionaría correctamente en la creación, pero si intentamos actualizar el formulario, el atributo "new_name" no se actualiza mas.
    Esto ocurre porque los atributos del formulario que estan deshabilitados no son enviados al servidor, aunque hayan sido modificados.

    Para solucionar esto, hay que añadir lo siguiente en el evento "OnSave()" del formulario:

    if (crmForm.all.new_name.IsDirty)
    {
    crmForm.all.new_name.ForceSubmit=true;
    }

    Simplemente estamos preguntando si ese atributo ha sido modificado, y si es así, le decimos al formulario de CRM que envíe la modificación al servidor y actualice el registro.
    Mucho cuidado con esto: si ponemos todos los atributos con el ForceSubmit=true, los registros siempre serán actualizados, a pesar de que no haya cambios, lo que provocaría que siempre se disparen los workflows, los plugins relacionados, y actualizaría las fechas de modificación.

    Por lo tanto, utilizar el "ForceSubmit", pero con mucho cuidado.

    Un saludo!

    sábado, 14 de noviembre de 2009

    Como aplicar seguridad a nivel de atributo

    Esta semana Microsoft ha publicado un artículo muy interesante acerca de como aplicar seguridad a nivel de atributo en CRM.
    Como todos sabrán, Microsoft Dynamics CRM 4.0 no incluye dentro de su modelo de seguridad a la seguridad a nivel de atributo. Solo se pude aplicar seguridad a nivel de registro (segun el propietario, su unidad organizativa, etc.).
    A pesar de esto, las posibilidades de extensibilidad que ofrece el CRM, nos permite desarrollarnos nuestro propio modelo de seguridad, de forma que se aplique según las reglas o condiciones que deseemos nosotros.
    El artículo que esta semana ha publicado Microsoft nos puede servir de guia para ese desarrollo y la verdad que recomiendo tambien leerlo para poder comprender mas a fondo como funciona el CRM en sus "tripas".
    Lo que me ha resultado muy interesante es un gráfico donde explica donde son los "puntos de interacción" del CRM, es decir donde podemos recoger los eventos del CRM:

    Recomiendo leerse detenidamente este artículo, no solo para hacer un desarrollo a nivel de seguridad a nivel de atributos, sino para sacar ideas para hacer futuras cosas con el CRM.

    El documento puede descargarse de aquí: http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=471f8670-47b3-4525-b25d-c11a6774615c

    un saludo

    jueves, 12 de noviembre de 2009

    Atención con los CustomerAddress utilizando DynamicEntity y FilteredViews

    Lo ideal al desarrollar con aplicaciones para CRM, es utilizar las famosas "DynamicEntity".
    De esta forma podemos realizar cualquier tipo de accion (crear, actualizar, etc.) con cualquier tipo de entidad.
    Para formatear correctamente los valores, podemos acceder primero a los MetaDatos de los atributos de la entidad, para saber de que tipo es el registro, y asi crear dinamicamente los atributos de la entidad "Dinámica".
    El problema surge con la entidad "CustomerAddress" en el atributo "objecttypecode". Este atributo es obligatorio y debe ser rellenado siempre para crear un registro de dirección. Al recoger el tipo del atributo, nos dice el CRM que es de tipo "Picklist", cuando en realidad es de tipo "EntityReference". Si intentamos crear una dirección pasando ese atributo como de tipo "Picklist", el CRM da un error y no nos lo permite.
    Para solucionar esto hay que crear una propiedad de tipo "EntityNameReferenceProperty".
    Para recoger el tipo del atributo hay que hacer:

    RetrieveAttributeRequest atrreq = new RetrieveAttributeRequest();
    atrreq.EntityLogicalName = "customeraddress";
    atrreq.LogicalName = "objecttypecode";
    RetrieveAttributeResponse atrresp = (RetrieveAttributeResponse)meta.Execute(atrreq);

    Y luego verificar el valor de "atrresp.AttributeMetadata.AttributeType".

    Nota adicional sobre la FilteredView: Si hacemos una consulta SQL en la vista "FilteredCustomerAddress", el atributo "parentid" que es la referencia a la Cuenta o Contacto relacionado con la dirección, no tiene el campo "parentidname" que deberia tener por ser un atributo de tipo "Customer".

    Conclusión: mucho cuidado al trabajar con "CustomerAddress", que tiene cosillas que no son del todo "Dinámicas" como en el resto de entidades, y podemos encontrarnos cosas raras.

    un saludo

    miércoles, 11 de noviembre de 2009

    Como controlar los ficheros adjuntos

    Como saben, en CRM se pueden añadir ficheros como adjuntos.
    Estos ficheros se adjuntan como "Notas" o como ficheros adjuntos en los correos electrónicos.
    El CRM no tiene ningun proceso de antivirus que controle los ficheros que se suben al CRM y que son almacenados en la base de datos de SQL Server.
    Muchas veces me preguntan como controlar que tipos de ficheros pueden subirse al CRM y como definir el tamaño máximo de dichos ficheros.
    Para definir esto hay que ir a Configuracion->Administración->Configuración del sistema.


    Allí, en la pestaña "General", se puede definir un listado de extensioens de ficheros "bloquedadas":


    Por último en la pestaña de "Correo electrónico" se puede definir el tamaño máximo de los adjuntos:

    viernes, 6 de noviembre de 2009

    Dejando rastro de los plugins

    Muchas veces necesitamos hacer pruebas y depuraciones para ver como llegan los datos en un plugin, en especial en relacion con los input/output parameters y las imágenes.
    Bueno, he preparado un plugin, que registra en una entidad de CRM, lo que ocurre cuando se dispara el plugin y nos deja el XML de esos parametros que "pasan" por el plugin.
    Para esto los pasos son:
    1) hacer un plugin con el siguiente código:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    using System.Xml;
    using System.Xml.Serialization;
    using System.IO;

    namespace Logger
    {

    public class Log : IPlugin
    {
    public void Execute(IPluginExecutionContext context)
    {

    string sInputParameters = serializar(context.InputParameters);
    string sOutputParameters = serializar(context.OutputParameters);
    string sPreEntityImages = serializar(context.PreEntityImages);
    string sPostEntityImages = serializar(context.PostEntityImages);

    StringProperty strname = new StringProperty("new_name", context.PrimaryEntityName);
    StringProperty strmensaje = new StringProperty("new_mensaje", context.MessageName);
    StringProperty strinputparameter = new StringProperty("new_inputparameter", sInputParameters);
    StringProperty stroutputparameter = new StringProperty("new_outputparameter", sOutputParameters);

    StringProperty strpreimages = new StringProperty("new_preentityimages", sPreEntityImages);
    StringProperty strpostimages = new StringProperty("new_postentityimages", sPostEntityImages);

    DynamicEntity dyn = new DynamicEntity();
    dyn.Name = "new_log";
    dyn.Properties.Add(strname);
    dyn.Properties.Add(strmensaje);
    dyn.Properties.Add(strinputparameter);
    dyn.Properties.Add(stroutputparameter);
    dyn.Properties.Add(strpreimages);
    dyn.Properties.Add(strpostimages);

    ICrmService serv=context.CreateCrmService(true);
    serv.Create(dyn);
    }

    public string serializar(PropertyBag obj)
    {
    string sreturn = "";
    XmlSerializer serializer = new XmlSerializer(typeof(PropertyBag));
    if (obj != null)
    {
    using (MemoryStream xmlInputParameters = new MemoryStream())
    {
    serializer.Serialize(xmlInputParameters, obj);
    byte[] buffer = new Byte[xmlInputParameters.Length];
    xmlInputParameters.Seek(0, 0);
    xmlInputParameters.Read(buffer, 0, Convert.ToInt32(xmlInputParameters.Length));
    sreturn = System.Text.Encoding.ASCII.GetString(buffer, 0, Convert.ToInt32(xmlInputParameters.Length));
    }
    }
    if (sreturn.Length > 100000) //el crm no deja campos ntext mas grandes de 100.000 caracteres
    {
    sreturn = sreturn.Substring(0, 100000);
    }
    return sreturn;
    }
    }
    }
    El código como ven es bastante sencillo, lo único que hace es serializar los objetos a mostrar, y crear un registro de "Log" con la información.

    2) Importar las personalizaciones de la entidad donde se registrarán los datos. Descargar la misma desde aquí. (es una sola entidad, con el nombre "Log")

    3) Registrar la dll y añadirla al mensaje que deseamos "depurar" (se debe registrar en tantos eventos como deseemos):


    Los datos se irán registrando de la siguiente forma (nos deja quien diparó el evento, la entidad, el tipo, la hora y los parametros e imágenes):


    Los recursos para esto pueden ser descargados desde aquí:
  • Personalizaciones entidad Log

  • Fichero Dll


  • Espero pueda servirles en su vida con los "plugins".

    un abrazo

    jueves, 5 de noviembre de 2009

    Como poner una búsqueda avanzada en un formulario

    Para poner una búsqueda avanzada en un iframe en un formulario de CRM, simplemente deberemos copiar la URL de la vista y ponerla como un iframe:

    1) Hacer la vista y guardarla.

    2) Ir a "Vistas guardadas" y copiar la url de la Búsqueda avanzada:


    3) Crear un Iframe pegando la URL:

    martes, 3 de noviembre de 2009

    Ha salido una nueva versión de la SDK (4.0.10)

    Luego de mas de 4 meses, Microsoft ha publicado una nueva versión de la documentación de la SDK de Micrsofot Dynamics CRM 4.0.
    Puede ser descargada desde aquí: Microsoft Dynamics CRM 4.0 Software Development Kit (SDK)

    Hay algunos temas a destacar:
    1) Hacen una aclaración especial que es muy importante: "Important: Using scripts to hide HTML form elements that display fields, sections, or tabs is unsupported and is not upgradeable." . Esto hay que tenerlo en cuenta, ya que posiblemente todos hemos ocultado campos alguna vez, y seguramente no funcione esto con "CRM 5".
    2) En la documentación de las "SubArea" del Mapa del sitio (SiteMap) hay unos nuevos atributos documentados de "GetStartedPanePath", para definir el panel inicial del Area. (Solo para CRM Online)
    3) Mejorada la documentación para los "Pre-filtros" de los informes en Reporting Services.
    4) Se ha añadido la clase "Microsoft.Crm.SdkTypeProxy.XmlSerializers.dll" como assembly "Soportado".
    5) En la carpeta "\stylesheet" hay un HTML de ejemplo con sus imágenes y sus CSS de ejemplo de formularios de CRM.
    6) La herramienta para registro de plugins y Assemblies de workflows (PluginRegistrationTool) ha sido actualizada arreglando algunos "bugs".
    7) Artículo interesante para paginaciones de queries: "Using the Paging Cookie"
    8) Otro artículo interesante para desarrolladores: "Choosing Between the WSDL and Assemblies"

    Un saludo

    lunes, 2 de noviembre de 2009

    Como personalizar todas las vistas del CRM (incluso las ocultas)

    Muchas veces necesitamos personalizar vistas que el propio CRM no nos prmite de forma estándar modificar.
    Esto ocurre por ejemplo con la vista de "Todos los integrantes" de las Listas de marketing, que no nos permiten personalizar las columnas a mostrar. Voy a intentar explicar con un ejemplo práctico como sería la forma de hacer esto.
    No estoy seguro de si es algo soportado, pero como ocurre siempre, si tengo dudas considero que es algo no soportado.
    Los pasos a realizar son los siguientes:
    1) Hacer una consulta SQL en la base de datos:

    Select SavedQueryId from savedquerybase
    where name like '%Todos los integrantes%'
    AND ReturnedTypeCode=2
    Con esta consulta recogeremos el GUID de la vista que necesitamos modificar, buscando por el nombre de la vista y el "ReturnedTypeCode" (en este caso contact -2-).
    Una vez que tenemos el id, solamente tenemos que entrar a una URL pasandole dicho GUID y allí se podrá modificar la vista en cuestión:
    http://servidor/empresa/tools/vieweditor/viewManager.aspx?id={2F0D0EDE-D356-4B1E-83BD-E978F10E3EEB}

    Yo lo he probado con varias vistas y me ha funcionado correctamente, no estoy seguro si funcionará con todas las vistas, pero con este en particular si que va bien.

    Esta idea la he recogido del blog de Gryzzly Villoslado

    Un saludo

    jueves, 29 de octubre de 2009

    Guía de diseño de MSCRM 4.0

    Este artículo trata del tema del diseño y de las hojas de estilo del CRM (tipos de letras, tamaños colores, etc).
    En la SDK, en la carpeta raiz hay un fichero con el nombre "crm4 ux design guide.pdf" que muestra todo el diseño del CRM, para que podamos hacer nuestros desarrollos con una apariencia lo mas parecido al CRM.
    Lo puedes descargar de aqui:
    crm4uxdesignguide.pdf



    un saludo

    martes, 27 de octubre de 2009

    Utilizando plantillas en Visual Studio 2008

    La última versión de la SDK del CRM (Version 4.0.9 de Junio de 2009) nos ofrece una serie de plantillas quie considero muy útiles para el momento en que nos pongamos a desarrollar para CRM.
    Existen 3 plantillas:

  • Para actividades de workflow y de plugins ("CrmWorkflowActivity" y "MSCRM Plug-in")




  • Para hacer una página web con una ventana de diálogo ("AddOnWebPage")




  • La SDK la podéis descargar de aquí: http://www.microsoft.com/downloads/details.aspx?FamilyID=82E632A7-FAF9-41E0-8EC1-A2662AAE9DFB&displaylang=en
    Estas plantillas nos facilitarán la vida a la hora de comenzar con este tipo de proyectos.
    Se encuentran en la carpeta "visualstudiotemplates\cs\" dentro de la carpeta donde esta instalada la SDK de CRM.
    Para registrarlas simplemente debemos copiar los ficheros .ZIP de cada una de las carpetas en la carpeta %Documents and Settings%\[usuario]\Mis Documentos\Visual Studio 2008\Templates\ProjectTemplates\Visual C#\.

    Un abrazo!

    sábado, 24 de octubre de 2009

    Sugerencias con Microsoft Connect

    Microsoft dispone de un sitio donde podemos dejar comentarios acerca de Microsoft Dynamics CRM, como por ejemplo sugerencias para nuevas versiones, o votar por las sugerencias que han propuesto otras personas.
    Lo que sí estoy seguro, es que hay gente de microsoft leyendo estos mensajes, ya ya he puesto un par de sugerencias.


    Bueno, ha sugerir!:
    https://connect.microsoft.com/dynamicssuggestions

    viernes, 23 de octubre de 2009

    Añadiendo HttpModules al CRM

    Los "HttpModules" son clases que implementan la interfaz System.Web.IHttpModule que nos permiten capturar los métodos que se lanzan en las diferentes etapas del proceso de las peticiones web.
    Consiste básicamente en una clase (.dll) que es llamada directamente cuando ocurren determinados eventos.
    De esta forma, podríamos crear nuestras propias clases en .NET que sean llamadas cuando se navegue por el CRM.
    Para mas información acerca de los HttpModules recomiendo ir a http://msdn.microsoft.com/es-es/library/9b9dh535(VS.80).aspx
    Antes de continuar, responderé a la pregunta que se estarán haciendo, obviamente esta funcionalidad no esta soportada por CRM.
    La funcionalidad que nos puede aportar esto es practicamente infinita, ya que podremos "meter" código nuestro en la ejecución del propio CRM.
    Voy a poner un ejemplo sencillo. En este caso, lo que voy a hacer es que mientras se navegue por el CRM, vaya escribiendo en la barra de "status" en la parte inferior del navegador, escriba las URL de las páginas que esta cargando.
    Los pasos para esto son los siguientes:
    1) Lo primero que hay que hacer, es crearse la clase en cuestión y compilarla.
    El código es el siguiente:

    using System;
    using System.Web.UI;
    using System.Web;

    namespace Httpmodulo
    {
    public class ExtensionCRM : IHttpModule
    {
    public event EventHandler BeginRequest;
    public void Dispose()
    {}
    public void Init(HttpApplication app)
    {
    app.PreRequestHandlerExecute += new EventHandler(this.OnPreRequestHandlerExecute);
    }
    public void OnPreRequestHandlerExecute(object o, EventArgs args)
    {
    try
    {
    HttpContext context = ((HttpApplication)o).Context;
    IHttpHandler handler = context.Handler;
    ((Page)handler).PreRender += new EventHandler(this.OnPagePreRender);
    }
    catch (System.Exception ex){ }
    }
    public void OnPagePreRender(object sender, EventArgs eventArgs)
    {
    Page page = (Page)sender;

    if (page.Request.QueryString["redirect"] == null)
    {
    if (!page.Request.FilePath.Contains("css") &&
    !page.Request.FilePath.Contains("icon.aspx") &&
    !page.Request.FilePath.Contains("fonts.aspx"))
    {
    page.Response.Write(@"");
    }
    }
    }

    }

    Como se ve, simplemente en el evento "OnPagePreRender" meto el cñodigo que quiere añadir y de "page.Request.FilePath"recojo el nombre de la página. De todos modos como estamos en el "contexto" de ejecución del CRM podemos recoger mas información.

    2) Una vez realizada mi clase, debemos firmar la misma, compilarla y registrarla en la GAC (Global Assembly Cache).

    3) El último, que por cierto es lo que no esta soportado por el CRM, es simplemente añadir una línea en el Web.Config del sitio web del CRM, para que cargue nuestra dll:





    La "PublicKeyToken" la podemos ver al momento de registrar la dll en la GAC, seleccionandola y pinchando en "propiedades". De allí la copiamos y la ponemos.

    Listo, de esta forma bastantemente no soportada, podemos meter código propio en la ejecución del CRM. Por ejemplo puede servir para hacer Logs de paginas abiertas, poder cargar javascripts externos en los "onloads" de los formularios, etc, etc, y lo que se les pueda ocurrir!.

    un abrazo!

    jueves, 22 de octubre de 2009

    Ha salido el Rollup 7 de CRM

    Hoy han publicado el Rollup 7 para Microsoft Dynamics CRM.
    Puede ser descargado de aqui:
    http://www.microsoft.com/downloads/details.aspx?FamilyID=a4893988-7804-4e23-ab58-740441cc696e&displayLang=en
    Algunos comentarios acerca del mismo pueden leerse desde aquí:
    http://blogs.msdn.com/crm/archive/2009/10/22/update-rollup-7-for-microsoft-dynamics-crm-4-0.aspx
    Como siempre, primero instalarlo en un servidor de desarrollo, para probar todas las funcionalidades, desarrollos, etc.

    Un cordial saludo.

    lunes, 19 de octubre de 2009

    Contador de registros y páginas

    El funcionamiento de las vistas y las búsquedas avanzadas permite a los usuarios de forma muy sencilla acceder a la información almacenada en el CRM.
    A pesar de su sencillez, tiene una carencia importante y que casi siempre preguntan los usuarios finales: "¿Cómo sé cuantos registros totales me devuelve una vista?".
    Existe una forma de facilitar la vida a los usuarios, creando un Plugin que se dispare recoja cuando se ejecute una vista (FetchXml) y que añada un registro como resultado en la primera posición, que nos dé esa información. Realmente será un registro "simulado" que permitirá al usuario saber fácilmente cuantos registros hay en una consulta. La idea la he recogido del Blog de a33ik.
    El código siguiente muestra un contador de registros y páginas para las Cuentas y Contactos:

    public void Execute(IPluginExecutionContext context)
    {
    if (context.Depth != 1) return;

    if (context.MessageName == "Execute" && context.InputParameters.Contains("FetchXml"))
    {
    XmlDocument indoc = new XmlDocument();
    indoc.LoadXml((string)context.InputParameters["FetchXml"]);

    string entityName = indoc.SelectSingleNode("//fetch/entity").Attributes["name"].InnerText;

    if (entityName != EntityName.account.ToString() && entityName != EntityName.contact.ToString()) return; //solo para cuentas y contactos

    IMetadataService mservice = context.CreateMetadataService(false);

    RetrieveEntityRequest request = new RetrieveEntityRequest();
    request.RetrieveAsIfPublished = false;
    request.LogicalName = entityName;
    request.EntityItems = EntityItems.EntityOnly;
    string primaryFieldName = ((RetrieveEntityResponse)mservice.Execute(request)).EntityMetadata.PrimaryField;

    ICrmService crmService = context.CreateCrmService(true);

    int pagecount = int.Parse(indoc.DocumentElement.Attributes["count"].InnerText);

    indoc.DocumentElement.Attributes.Remove(indoc.DocumentElement.Attributes["count"]);
    indoc.DocumentElement.Attributes.Remove(indoc.DocumentElement.Attributes["page"]);

    foreach (XmlNode node in indoc.SelectNodes("//fetch/entity/attribute"))
    indoc.SelectSingleNode("//fetch/entity").RemoveChild(node);

    foreach (XmlNode node in indoc.SelectNodes("//fetch/entity/order"))
    indoc.SelectSingleNode("//fetch/entity").RemoveChild(node);

    foreach (XmlNode node in indoc.SelectNodes("//fetch/entity/link-entity"))
    foreach(XmlNode subnode in node.SelectNodes("./attribute"))
    node.RemoveChild(subnode);

    XmlAttribute aggrAttr = indoc.CreateAttribute("aggregate");
    aggrAttr.Value = "true";
    indoc.DocumentElement.Attributes.Append(aggrAttr);

    XmlNode field = indoc.CreateNode(XmlNodeType.Element, "attribute", null);

    XmlAttribute nameAttr = indoc.CreateAttribute("name");
    nameAttr.Value = string.Format("{0}id", (entityName == EntityName.activitypointer.ToString() ? "activity" : entityName));
    field.Attributes.Append(nameAttr);

    XmlAttribute aggregateAttr = indoc.CreateAttribute("aggregate");
    aggregateAttr.Value = "count";
    field.Attributes.Append(aggregateAttr);

    XmlAttribute aliasAttr = indoc.CreateAttribute("alias");
    aliasAttr.Value = "C";
    field.Attributes.Append(aliasAttr);

    indoc.SelectSingleNode("//fetch/entity").AppendChild(field);

    //Xml con el resultado (sin paginación)
    string fullResult = crmService.Fetch(indoc.OuterXml);

    XmlDocument fullResultDocument = new XmlDocument();
    fullResultDocument.LoadXml(fullResult);

    //registros por fetch
    int totalRecordCount = int.Parse(fullResultDocument.SelectSingleNode("//resultset/result/C").InnerText);
    int totalPageCount = (totalRecordCount / pagecount) + ((totalRecordCount % pagecount) == 0 ? 0 : 1);

    string result = string.Format("Total de registros = {0}, Total de Páginas = {1}", totalRecordCount, totalPageCount);

    //XML de Resultado que se muestra en el Grid
    XmlDocument outdoc = new XmlDocument();
    outdoc.LoadXml((string)context.OutputParameters["FetchXmlResult"]);

    //Creacion del registro que muestra los totales
    XmlNode ResultNodeText = outdoc.CreateNode(XmlNodeType.Element, primaryFieldName, null);
    ResultNodeText.InnerText = result;

    XmlNode ResultNodeId = outdoc.CreateNode(XmlNodeType.Element, string.Format("{0}id", (entityName == EntityName.activitypointer.ToString() ? "activity" : entityName)), null);
    ResultNodeId.InnerText = Guid.Empty.ToString();

    XmlNode res = outdoc.CreateNode(XmlNodeType.Element, "result", null);
    res.AppendChild(ResultNodeText);
    res.AppendChild(ResultNodeId);

    //Añadir en registro con la etiqueta del contador de páginas y registros como primer registro en el recordset
    outdoc.SelectSingleNode("//resultset").InsertBefore(res, outdoc.SelectSingleNode("//resultset").FirstChild);
    context.OutputParameters["FetchXmlResult"] = outdoc.OuterXml;
    }


    La dll se puede descargar de aquí: RecordCounter y se debe registrar de la siguiente forma:


    esto se verá de la siguiente forma:


    espero les sirva...

    un saludo

    jueves, 15 de octubre de 2009

    Herramientas útiles para trabajar con CRM (Stunnware Tools)

    En este artículo intentaré realizar comentarios acerca de una solución que nos ofrece muchas herramientas para trabajar con Microsoft Dynamics CRM 4.0 y que permite hacernos la vida mas "facil".
    Esta herramienta es la llamada "Stunnware Tools for Microsoft Dynamics CRM 4.0" que ha realizado Michael Höhne, un MVP de Microsoft CRM de Alemania, que por cierto tiene un Blog muy bueno al cual recomiendo: stunnware's CRM Corner.
    La herramienta la pueden descargar de esta direccion: http://www.stunnware.com/. El funcionamiento es muy sencillo, simplemente es una aplicación a la cual mediante el botón de "Options" se les puede ir añadiendo al menú de tipo "Ribbon" (el del Office 2007) los distintos módulos que se deseen (recomiendo añadir todos).
    Las distintas funcionalidades que contiene y que recomiendo su uso son:
  • Herramienta para modificar y actualizar el ISV y el SiteMap

  • Herramienta para comparar dos ficheros de Personalizaciones diferentes

  • Herramienta para creación de Consultas que genera automáticamente el XML para una consulta FetchXML y el código para el QueryExpression

  • Acceso y consulta de todos los MetaDatos de forma sencilla (entidades, atributos, etc)

  • etc.

  • Espero les sirva esta solución, la considero muy buena.

    un saludo

    jueves, 8 de octubre de 2009

    Como copiar una entidad con todos sus atributos

    Muchas veces necesitamos copiar toda la estructura de una entidad, incluyendo sus atributos, relaciones, etiquetas, etc.
    Esto suele ocurrir cuando se tiene una entidad a la que no le hemos definido Notas o Actividades, y luego se desea modificar dicha propiedad (que solo se puede definir en la creación de la entidad).
    He desarrollado una pequeña función que lo que hace es copiar toda la información de esquema (es decir atributos, relaciones y la definición general de la entidad) de una entidad original, a una nueva. Este desarrollo esta basado en la documentación que aparece en la SDK y es totalmente soportado.
    Simplemente la llamada a la misma podria ser:

    CopiarEntidad("localhost","Contoso","new_entidadoriginal", "new_nuevaentidadoriginal", true,true);
    Los parámetros serían los siguientes:
    1) Ip del servidor
    2) Empresa
    3) Nombre de esquema de la entidad original
    4) Nombre de esquema de la nueva entidad a crear
    5) true/false si la nueva entidad contendrá notas
    6) true/false si la nueva entidad contendrá actividades

    El código de la función es un poco largo así que no lo pego directamente aquí, sino que dejo este enlace para descargárselo.

    El funcionamiento del código es bastante sencillo. En primer lugar, se recoge dinámicamente los metadatos de la entidad original. Luego creo la nueva entidad y le creo todos los atributos relacionados recorriendo los atributos de la entidad original. Finalmente recorre las relaciones de N:1 de la entidad (campos de lookup de la entidad) y crea las relaciones.

    Si utilizan la función deben tener en cuenta:
    1) La nueva entidad que se crea, se crea con el nombre para mostrar "copia de ". Esto es así ya que el CRM no permite tener 2 entidades con los mismos nombres para mostrar.
    2) Utilizo la clase CrmServiceUtility para la creación de los "CrmLabel". Esta clase esta documentada en la SDK.
    3) Sólo esta preparado para copiar una entidad personalizada.
    4) Por ahora esta función no copia ni vistas ni personalizaciones de formulario.
    5) Se deben añadir las referencias a los web services de CRM ("CrmSdk" y "metadataService")

    Espero les sirva este código, intentaré ir mejorandolo de a poco en siguientes artículos.

    Un saludo

    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,

    sábado, 29 de agosto de 2009

    Validaciones en el evento OnSave() de los formularios

    Muchas veces necesitamos realizar validaciones en los formularios de CRM, que impidan que se guarden los datos introducidos en el mismo.
    MSCRM 4.0 nos da la opción de recoger el evento que provocó la grabación del formulario, para poder realizar la validación que se necesite.
    La documentación del evento OnSave() aparece en la SDK de CRM 4.0.
    Allí nos detalla que podemos recoger la acción en el OnSave() accediendo a los valores de event.Mode que son los siguientes:


    Hora intentaré hacer un par de ejemplos prácticos en relación con esto. Los ejemplos serán Javascripts que deberán ponerse en el evento OnSave() del formulario.

    1) En una entidad donde tenemos una "Fecha desde" y una "Fecha hasta", no dejar guardar el formulario si la "Fecha hasta" es menor que la "Fecha desde":

    if (event.Mode==1 event.Mode==2) // si es un evento de "Guardar" o "Guardar y cerrar"
    {
    var fechadesde=crmForm.all.new_fechadesde.DataValue;
    var fechahasta=crmForm.all.new_fechahasta.DataValue;
    if (fechadesde>fechahasta)
    {
    alert("La 'Fecha hasta' no puede ser menor que la 'fecha desde'.");
    event.returnValue = false;
    return false;
    }
    }

    2) Ejemplo para no dejar completar una Cita, si no se rellena algo en el campo de descripción, para así obligar a rellenar los comentarios acerca de la misma:

    if (event.Mode==58) // si es un evento de "Guardar como completado"
    {
    var comentarios=crmForm.all.description.DataValue;
    if (comentarios==null || comentarios=="")
    {
    alert("Para completar la cita, debe rellenar los comentarios de la misma.");
    event.returnValue = false;
    return false;
    }
    }

    viernes, 28 de agosto de 2009

    Ha salido el Update Rollup 6

    Esta semana ha salido un nuevo Rollup para el CRM 4.0.
    la URL para la descarga está aquí:
    Update Rollup 6 for Microsoft Dynamics CRM 4.0 (KB 970148)

    Para ver un resumen de las actualizaciones entrar aquí:
    El paquete acumulativo de actualizaciones 6 para Microsoft Dynamics CRM 4.0 supone disponible

    Como toda actualización, tener mucho cuidado antes de instalarlo, antes que nada instalarlo en un servidor de desarrollo, probar todos los desarrollos y personalizaciones.

    Un saludo!

    miércoles, 26 de agosto de 2009

    Importador de datos del CRM

    En este artículo, intentaré dar algunas recomendaciones acerca de la utilización del importador de datos del CRM.
    En primer lugar hay que tener en cuenta que el importador sólo permite la importación de datos desde ficheros en formato .CSV (separado por comas).
    De esta forma podremos insertar datos de forma masiva en CRM.
    Para explicarlo mejor, voy a realizar un ejemplo de importación de datos de una serie de Contactos, desde un fichero de Excel, hasta la inserción en CRM.

    En primer lugar vamos a crear un Excel, con las columnas de información que queremos importar. Como recomendación, introducir como nombre de las columnas, los mismos "Nombres para mostrar" de cada uno de los atributos del CRM que queremos importar (respetanto mayúsculas y acentos).
    El excel quedaría así (los nombres son todos ficticios):


    Ahora lo que vamos a hacer, es guardar el excel en formato .CSV pulsando en "Guardar como" y seleccionando de la siguiente manera:


    Para realizar la importación, debemos ir a Herramientas->Importar Datos...:


    Debemos seleccionar el fichero .CSV y seleccionar como delimitador de campo "Punto y coma (;)":


    Ahora se debe seleccionar el "Tipo de registro" a impotar (en este caso "Contacto"). Al haber introducido como cabecera los mismos nombres para mostrar que los atributos en CRM, la Asignación de datos es "Automático":


    Seleccionar a quién se le asignarán los registros y si se desea que se importen duplicados:


    Introducir un nombre para el trabajo de importación y confirmar la misma:


    La importación se lanza en un proceso asíncrono. Para realizar un seguimiento del estado del proceso de importación, se puede ir a Área de trabajo->Importaciones (la importación va pasando por los estados "Enviado","Analisis","Transformacion","Importando","Completado"):


    Una vez acabado se podrá abrir el registro del proceso asíncrono, y desde allí identificar los registros que se a creado, y los posibles errores y causas de los mismos. En este caso de los 7 registros, 2 han dado error, por no encontrar la referencia de la empresa "Sunny":


    Finalmente los registros están creados en el CRM:


    Cosas a tener en cuenta para la importación:

    • Las columnas de tipo referencia o "Lookup" deben ser rellenadas con los nombres de las entidades a los que se referencia. Los registros de la tabla referenciada, ya deben de estar creados.
    • La velocidad de las creaciones de los registros puede variar según el servidor de que se trate.
    • Las columnas de tipo desplegable o "Picklist" deben ser rellenadas con los valores en texto del desplegable.

    martes, 25 de agosto de 2009

    Ordenar registros por varias columnas

    Muchas veces me han preguntado si se podía ordenar los registros que aparecen en las vistas del CRM por varias columnas a la vez.
    La verdad que hasta ahora no lo sabía, pero sí que es posible.
    Me han recomendado buscar en el famoso "Centro de recursos" de CRM, un área al que casi nunca solía acceder, pero navegando por la misma se nota que es de mucha utilidad para usuarios de CRM.
    Allí se encuentra un articulo: Ordenar registros por varias columnas

    Explica que para ordenar por varias columnas simplemente hay que hacer:
    1) Ir a la vista que se desea ordenar (o busqueda avanzada).
    2) Seleccionar la primera columa a ordenar.
    3) Con las teclas CTRL+SHIFT presionados ir seleccionando las siguientes columnas.

    Hasta aquí, todo bien.
    Pero, me empezaron a surgir dudas de hasta donde se podía llegar con esta funcionalidad.
    1) ¿Hasta cuantas columnas de orden se pueden seleccionar a la vez?: Yo llegué hasta las 25 columnas, y lo único que noté es que cada vez tardaba un poco mas en recargar la vista.
    2) ¿Funciona para cualquier tipo de campo o con algun tipo de campo no te deja "Multi-ordenar"?: Funciona con cualquier tipo de campo.
    3) ¿Funciona con campos de otras entidades?: Como no se puede ordenar las vistas por columnas de otras entidades, tampoco se puede "Multi-ordenar".

    Me pareció una utilidad mas que intersante, en especial al exportar a Excel, ya que así podría ordenar rápidamente por lo que me interesa, y exportar a Excel sólo la página actual en la que me encuentro.

    domingo, 23 de agosto de 2009

    FilteredViews y autenticación de SQL Server.

    Como todos saben, la información de MSCRM está almacenada en una base de datos en SQL Server.
    La forma soportada y documentada en la SDK de Microsoft CRM 4.0 de acceder a la información directamente en el SQL Server, es a través de unas vistas que son llamadas "FilteredViews".
    Las FilteredViews, son vistas en SQL Server cuyo nombre contiene el mismo nombre de la entidad, con el texto "Filtered" por delante. Por ejemplo "FilteredAccount" para la entidad de "Cuentas".

    Al SQL Server uno se puede conectar de dos formas, y con cada una de las mismas, las FilteredViews se comportará de forma diferente:

    1) Autenticación integrada de Windows:
    Al conectarse de esta forma, y al realizar una consulta, automáticamente resolverá con el usuario conectado, los roles de seguridad, idioma, desplazamientos horarios, etc.
    Así, por ejemplo podríamos de esta forma hacer la siguiente consulta:

    SELECT * FROM FilteredAccount

    Las FilteredViews detectan el usuario del directorio activo conectado al SQL Server, y lo relacionan con el usuario correspondiente de CRM.
    Esto lo realiza con una función en SQL Server que podemos probar de la siguiente forma:

    SELECT dbo.fn_FindUserGuid

    2) Autenticación de SQL Server:
    Al acceder mediante autenticación de SQL Server, las FilteredViews no pueden reconocer al usuario conectado, y si ejecutamos la consulta anterior, simplemente no devuelve nada, como si no se tuviese permisos para ver ningún registro.
    Si nos conectamos de esta forma, y queremos hacer una consulta a las "FilteredViews" solo lo podemos haces "Impersonalizando" la consulta, es decir, ejecutando la misma como si fuesemos otro usuario.
    Esto se puede lograr con la cláusula "EXECUTE AS USER=..." de la siguiente forma:
    SELECT * FROM FilteredAccount EXECUTE AS USER='contoso\admin'

    De esta forma, la consulta se realizará con los roles de seguridad y perfil del usuario que se determine.

    jueves, 20 de agosto de 2009

    Búsqueda rápida de registros inactivos

    Al realizar una búsqueda rápida en MSCRM, solo aparecen aquellos registros que estén activos.
    Por ejemplo:


    Para habilitar que la "Búsqueda rápida" de una entidad busque también por registros inactivos, se debe hacer lo siguiente (el ejemplo en con las Cuentas):
    1) Exportar las personalizaciones de la entidad.
    2) Quitar el siguiente texto de la vista de "Búsqueda rápida de cuentas activas" (dentro de la "SavedQuery"):





    3) Volver a importar la personalización y publicar.

    Tener den cuenta que esta modificación es una modificación no soportada.

    martes, 18 de agosto de 2009

    Direcciones de envío de los Emails

    Muchas veces enviamos emails desde el propio MSCRM (no mediante el Outlook), que como saben son enviados a la dirección que tenga predefinida el "Customer" o Usuario, como "emailaddress1".
    Ahora bien, una vez que se pincha en "enviar" desde MSCRM, se marca como para enviar, pero es posible que quede en estado "pendiente", hasta que el proceso asíncrono de EmailRouter haga efectivamente el envío, o que se quede pendiente de sincronizar con un cliente de Outlook.
    Entre un momento y otro, puede pasar un tiempo, y es posible que por ejemplo el email de la Cuenta haya sido modificado.
    Entonces, ¿a que dirección se enviará el Email?, para esto habría dos opciones:
    1) Que se envie a la dirección que tenía al momento de crear el email.
    2) Que se envíe a la dirección que tiene al momento efectivo de realizar el envío.

    Bueno, la respuesta es que antes del Rollup 4 de MSCRM 4.0, ocurría lo primero, y a partir del Rollup 4, ocurre lo segundo.

    ¿Como funciona entonces?
    Al crear un Email, se crea un registro en la tabla "ActivityParty", con el campo "addressused" con el valor que tenga en ese momento.
    Esto se puede comprobar fácilmente realizando la siguiente consulta:

    SELECT addressused FROM FilteredActivityParty

    Una vez que se actualiza el Email del registro en cuestión (por ejemplo una Cuenta), se actualiza tambien en cascada en la tabla "ActivityParty" en campo "addressused".

    Los Rollups, muchas veces resulven temas, añaden funcionalidad y otras tantas cambian cosas, que pueden no estar documentadas, por esto mucho cuidado al instalar Rollups y probar todos los desarrollos previamente en un entorno que no sea de producción para así evitarse posibles dolores de cabeza.

    domingo, 16 de agosto de 2009

    Valores de los "Picklists" desde SQL Server

    A veces tenemos la intención de acceder a los metadatos de CRM, pero simplemente para identificar los valores posibles de un campo de tipo "Picklist" o desplegable.
    Esta información además de poder accederse con la propia SDK a través de los métodos disponibles de el MetadataService, también pueden ser accedidos de forma mas rápida y sencilla desde el propio SQL Server.
    La consulta es muy simple (sólo habría que modificar los campos del "WHERE"):


    SELECT AttributeValue, Value
    FROM StringMap
    WHERE AttributeName='accountcategorycode' AND ObjectTypeCode=1
    ORDER BY DisplayOrder

    viernes, 14 de agosto de 2009

    Control de Errores en llamadas a la SDK

    Muchas veces al hacer una llamada a un método de un web service de CRM, nos devuelve un error, que si lo recogemos como la simple "Exception", no nos dice exactamente el error que devuelve el CRM, sino un simple error genérico de web service.
    Se debe capturar en el "catch" el error System.Web.Services.Protocols.SoapException, y con eso, acceder a "soexp.Detail.InnerText".

    Para capturar correctamente los errores devueltos por el CRM, se debe de hacer de la siguiente forma:


    try
    {
    Guid guidAccount=service.Create(nuevacuenta);
    }
    catch (System.Web.Services.Protocols.SoapException soexp)
    {
    Console.WriteLine("La descripción del error soap es:" + soexp.Detail.InnerText);
    throw soexp;
    }
    catch (Exception ex)
    {
    Console.WriteLine("La descripción del error es:" + ex.Message);
    throw ex;
    }

    jueves, 13 de agosto de 2009

    "Soportado" vs "No Soportado"

    Muchas veces, nos surge la idea de hacer cosas que el CRM de forma natural no lo hace, o que lo hace de una manera que puede no servirnos, ya sea por la velocidad, o que por el propio funcionamiento del CRM no encaja con lo que quiere el cliente.
    Para esto se debe hacer algun desarrollo adicional para que cubra dicha necesidad.
    Estos desarrollos o extensiones puedes hacerse de una forma "soportada" o de una forma "no soportada" por Microsoft.
    Las personalizaciones "no suportadas" son las siguientes:

  • Modificar o añadir cualquier fichero del directorio virtual de CRM o del directorio de instalación del CRM.

  • Modificaciones en el sitio virtual del CRM.

  • Cualquier modificación en la base de datos SQL Server (tablas, vistas, procedimientos almacenados, etc), salvo la actualización o creación de índices.

  • Referenciar directamente a las DLLs del CRM.

  • Reutilización de los controles de usuario propios del CRM.

  • Utilización de javascripts propios del CRM.

  • Hacer personalizaciones fuera de las herramientas de personalización del CRM.

  • La utilización de "HttpModules" para inyectar HTML dinámico en los formularios de CRM.


  • Ahora bien, ¿qué consecuencias podría tener hacer cosas "no soportadas"?. Las consecuencias podrían ser las siguientes:

  • Que alguna actualización de CRM pueda cambiar el funcionamiento y hacer que nuestro desarrollo no funcione.

  • Se podría perder el soporte de Microsoft, ya que se han realizado cosas "no soportadas".

  • Podríamos tener problemas en futuras migraciones a próximas versiones de CRM.

  • Las personalizaciones no soportadas podrían causar funcionamientos no esperados.


  • A pesar de esto, muchas veces no nos queda otra opción que hacer cosas "no soportadas".
    las personalizaciones "no soportadas" yo las califico entre "totalmente no soportadas" y otras que son un poco mas "light". Las diferencias entre unas y otras estan claras, las primeras podrían causar problemas graves, y las segundas no tanto, o que podrian ser modificadas con relativa facilidad en futuras versiones.
    A continuación pongo algunos ejemplos:

    miércoles, 12 de agosto de 2009

    Eliminación masiva de registros en MSCRM

    Muchas veces necesitamos eliminar toda la información de una entidad específica. Si son pocos registros, no hay problema, los eliminamos de forma manual con el propio CRM.
    Ahora, si los registros son muchos, Microsoft CRM nos da la posibilidad de llamar a unos trabajos asíncronos de eliminación, de forma que podemos dispararlos, y dejarlos hasta que acaben de eliminar todos los registros.
    Recomiendo no eliminar nunca registros "a mano" en la base de datos SQL salvo que sea extrictamente necesario. Además tener en cuenta que si se borra directamente en SQL Server, toda la funcionalidad que se le haya definido a la entidad en cuando a relaciones en cascada, los workflows, y los plugins, no se dispararán. Además, como saben, no esta soportada la eliminación de registros directamente en la base de datos.

    La siguiente función hace exactamente eso, borra todos los registros de una entidad a traves de un trabajo del sistema asíncrono. Está hecho de una forma soportada y documentada en la SDK de MSCRM 4.0:


    public static void BorradoMasivo(string crmServerUrl, string orgName, string Entidad, string Usuario, string Password, string Dominio)
    {
    CrmAuthenticationToken token = new CrmAuthenticationToken();
    token.OrganizationName = orgName;
    CrmService service = new CrmService();
    service.Credentials = new System.Net.NetworkCredential(Usuario, Password,Dominio);
    service.Url = crmServerUrl;
    service.CrmAuthenticationTokenValue = token;

    QueryExpression bulkDeleteQuery = new QueryExpression();
    bulkDeleteQuery.EntityName = Entidad;

    BulkDeleteRequest bulkDeleteRequest = new BulkDeleteRequest();
    bulkDeleteRequest.JobName = "Eliminacion de: " + Entidad;
    bulkDeleteRequest.QuerySet = new QueryBase[] { bulkDeleteQuery };

    bulkDeleteRequest.StartDateTime = new CrmDateTime();
    bulkDeleteRequest.StartDateTime.Value = DateTime.Now.ToString();

    bulkDeleteRequest.RecurrencePattern = string.Empty;
    bulkDeleteRequest.SendEmailNotification = false;
    Guid[] emptyRecipients = new Guid[0];
    bulkDeleteRequest.ToRecipients = emptyRecipients;
    bulkDeleteRequest.CCRecipients = emptyRecipients;

    BulkDeleteResponse bulkDeleteResponse = (BulkDeleteResponse)service.Execute(bulkDeleteRequest);
    }

    Este proceso puede tardar mucho en caso de haber muchos miles (o millones) de registros.

    A medio camino del "delete" en la base de datos y de la utilización de la SDK, existe una posibilidad, no soportada también, de eliminar registros.
    Al eliminar un registro en MSCRM 4.0, realmente no se elimina de la base de datos, sino que simplemente se marca como para borrar poniendo el campo "deletionstatecode=2", para que luego el proceso asíncrono se encargue de eliminar todos los registros marcados. De esta forma podríamos "eliminar" registros del CRM a traves de un "UPDATE" en la base de datos. Esto no esta soportado, así que esto se debería probar que se eliminan correctamente.

    Así, por ejemplo, podríamos eliminar todas las Cuentas:

    UPDATE AccountBase SET DeletionStateCode=2

    Las eliminaciones masivas de registros pueden ser muy lentas si se hacen de forma soportada, pero para utilizar formas no soportadas, se debería estar muy seguro de lo que se hace y realizar exhaustivas pruebas de que eso funcion correctamente y no tiene consecuencias secundarias.

    Espero pueda servirles,

    Saludos