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