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