domingo, 19 de mayo de 2013

[Reporting Service] [Dynamic CRM] - Integrar con google maps (2/2)

 

Introducción


Continuamos con el articulo anterior

[Reporting Service] [Dynamic CRM] - Integrar con google maps (1/2)

 

Creación del componente


Antes de arrancar hay resaltar que no podremos usar .net 4 pues el rdl que estamos editando con VS2008 solo soporta hasta .net 3.5

Este punto no es menor, ya que esto nos imposibilita hacer uso de las librerías de CRM SDK, por lo tanto deberemos hacer uso del servicio de WCF para poder consultar al CRM y obtener la info de las cuentas

Entonces el primer paso será crear un proyecto del tipo Class Library, remarco la definición de .net 3.5

SNAGHTML2eb3ce99

En este punto se puede usar tanto VS 2008 o 2010, en este caso decidí usar 2010 pero remarcando la opción del framework utilizado.

En el proyecto veremos una clase que representa el proxy del servicio, esta se creo mediante el uso de la utilidad

Herramienta Lenguaje de descripción de servicios Web (Wsdl.exe)

utilizando la url: http://<sitio>:<puerto>/ContosoHQ/XRMServices/2011/Organization.svc

en este caso la organización de ejemplo que estamos usando es ContosoHQ, pero esto se debe reemplazar por el que estén utilizando. La clase resultante es la OrganizationService.cs

SNAGHTML3922419d 

Se creo un helper el cual no brindara información de CRM utilizando el servicio, se trata del CRMHelper.cs

Se utiliza el fetchxml proveniente del reporte como filtro para conocer que cuentas se están mostrando en el reporte.

 

public static List<AccountCRM> GetAccounts(string query)
{
    try
    {
        List<AccountCRM> accounts = new List<AccountCRM>();

        using (OrganizationServiceClient crmService = new OrganizationServiceClient())
        {
            
            EntityCollection myAccounts = crmService.RetrieveMultiple(new FetchExpression() { Query = query });

            foreach (Entity entity in myAccounts.Entities)
            {
               
                AccountCRM account = new AccountCRM()
                {
                    id = ((XmlText)((XmlNode[])entity.Attributes.First(x => x.key == "accountid").value)[2]).Value,
                    razonsocial = entity.Attributes.First(x => x.key == "name").value.ToString(),
                    address = entity.Attributes.First(x => x.key == "address1_line1").value.ToString(),
                    stateorprovince = entity.Attributes.First(x => x.key == "address1_stateorprovince").value.ToString(),
                    country = entity.Attributes.First(x => x.key == "address1_country").value.ToString(),
                    territory = ((EntityReference)entity.Attributes.First(x => x.key == "territoryid").value).Name,
                };
                
                //se valida si la key puede no retornarse 
                //si la entidad tiene un valor nulo o vacio el servicio no la retorna como respuesta 
                //a pesar que este incluida en el fetchxml
                var latitud = entity.Attributes.FirstOrDefault(x => x.key == "address1_latitude");
                var longitude = entity.Attributes.FirstOrDefault(x => x.key == "address1_longitude");

                if (!(latitud == null && longitude == null))
                {
                    account.Position = new GeoPosition()
                    {
                        Latitude = Convert.ToDouble(latitud.value),
                        Longitude = Convert.ToDouble(longitude.value),
                    };
                }

                accounts.Add(account);

            }
        }

        return accounts;

    }
    catch (Exception ex)
    {
        Trace.WriteLine(string.Format("[{0:dd-MM-yyyy HH:mm}] Message:{1}, StackTrace: {2}", DateTime.Now, ex.Message, ex.StackTrace));
        throw;
    }

}

El código que sigue hará uso de las cuenta para generar la url el cual permitirá a la api de google maps generar la imagen

public static byte[] GetMap(string fetchxml)
{
    //asignamos la cultura en en-US para que la puntuacion de la localizacion resuelva correctamente
    Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");

    try
    {
        //obtenemos las cuentas
        List<AccountCRM> accounts = CRMHelper.GetAccounts(fetchxml);

        if (accounts.Count > 0)
            return GoogleStaticMap(accounts);
        else
            return null;

    }
    catch (Exception ex)
    {
        Trace.WriteLine(string.Format("[{0:dd-MM-yyyy HH:mm}] Message:{1}, StackTrace: {2}", DateTime.Now, ex.Message, ex.StackTrace));
        return null;
    }

}

 

Una vez ejecutado el fetchexml y recuperada las cuenta se procede armar la url que generara la imagen del mapa

private static byte[] GoogleStaticMap(List<AccountCRM> accounts)
{
    List<string> colors = new List<string>() { "red", "blue", "yellow", "green", "orange" };
    try
    {
        //en la url se define el tamaño de la imagen, formato y tipo de mapa
        string url = "http://maps.googleapis.com/maps/api/staticmap?size=900x450&maptype=roadmap&format=jpg{0}&sensor=false";


        //solo se procesan las cuentas que tengan geo-posicionamiento
        accounts = accounts.Where(x => x.Position != null).ToList();

        if (accounts.Count() == 0)
            return null;

        //se agrupa las cuentas por su territorio
        //para definir los colores que se aplicara a cada marca en el mapa
        var groupAccounts = accounts.GroupBy(x=> x.territory);

        //se recorre cada grupo armando la marca
        List<string> markersList = new List<string>();
        int colorindex = 0;
        foreach (var item in groupAccounts)
        {
            markersList.Add(string.Format("&markers=color:{0}|{1}", colors[colorindex], string.Join("|", item
                                                                                            .Select(x => string.Format("{0},{1}", x.Position.Latitude, x.Position.Longitude))
                                                                                            .ToArray())
                         ));

            colorindex++;

            if (colorindex == colors.Count)
                colorindex = 0;
        }
        string markers = string.Join("", markersList.ToArray());


        url = string.Format(url, markers);

        //se invoca a la url para obtener la imagen del mapa
        Uri uri = new Uri(url);
        WebClient client = new WebClient();
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);

        Stream stream = request.GetResponse().GetResponseStream();

        //se convierte el stream en byte[]
        return ReadFullStream(stream, request.GetResponse().ContentLength);


    }
    catch (Exception ex)
    {
        Trace.WriteLine(string.Format("[{0:dd-MM-yyyy HH:mm}] Message:{1}, StackTrace: {2}", DateTime.Now, ex.Message, ex.StackTrace));
        throw;
    }

}

 

Como paso final copiaremos la dll generada en la carpeta del proyecto donde estamos editando el rdl

 

Vincular RDL con librería .net


Estando en al edición del rdl en el Visual Studio 2008, seleccionamos la opción

image

veremos un dialogo del cual no interesa la opción “Referencias”

image

Usaremos la opción de “Agregar” para buscar la dll que creamos en el paso anterior

image

 

Arrastramos el control imagen al diseñador del reporte

image

el control el diseñador del reporte nos despliega el dialogo donde podremos definir la formula que invocara al método de nuestra libreria

SNAGHTML2fdec868

La formula seria la siguiente:

=ContosoGoogleMap.ReportHelper.GetMap(Parameters!CRM_FilteredAccount.Value)

como se observa define el namespace + clase + método

al hacer uso del parámetro:  Parameters!CRM_FilteredAccount.Value obtendremos el fetchxml que le llega al reporte, por ejemplo, podría ser algo como:

<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
	<entity name="account">
		<all-attributes />
		<filter type="and">
			<condition attribute="address1_city" operator="eq" value="Buenos Aires" />
		</filter>
	</entity>
</fetch>

El propio CRM envía de forma automática el xml del fetchxml al reporte.

 

Configuración Reporting Service


1- Se copiara la dll a la carpeta

%ProgramFiles%\Microsoft SQL Server\MSRSXX.<Instance Name>\Reporting Services\ReportServer\bin

 

2- Modificar el archivo rssrvpolicy.config de la carpeta

%ProgramFiles%\Microsoft SQL Server\MSRSXX.<Instance Name>\Reporting Services\ReportServer

colocando justo debajo del <CodeGroup> que lleva el $CodeGen$ la definición:

<CodeGroup
	class="FirstMatchCodeGroup"
	version="1"
	PermissionSetName="FullTrust"
	Name="ContosoGoogleMapGroup"
	Description="">
	<IMembershipCondition
		class="UrlMembershipCondition"
		version="1"
		Url="C:\Program Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportServer\bin\ContosoGoogleMap.dll"/>
</CodeGroup>

3- Modificar en el rssrvpolicy.config la línea que lleva el Report_Expressions_Default_Permissions validando que el PermissionSetName este en “FullTrust”.

SNAGHTML2feed0c1

Esto será necesario para poder escribir a disco, además de poder realizar las invocaciones a las url de CRM y Google, sino se define se obtendrán errores como ser:

Error de solicitud de permiso de tipo 'System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

o

Error de solicitud de permiso de tipo 'System.Web.AspNetHostingPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'

 

4 – Modificar el web.config de la carpeta

%ProgramFiles%\Microsoft SQL Server\MSRSXX.<Instance Name>\Reporting Services\ReportServer

en este se debe definir la configuración del <system.serviceModel> que se encuentra en el app.config del archivo de test, este debería estar a nivel del <configuration>

SNAGHTML2ff3687e

también se puede definir el <system.diagnostics>

 

5 - Como paso final seria recomendable reiniciar el servicio de reporting para asegurar que todo lo modificado tome efecto

SNAGHTML2fa75ce8

 

Publicar del reporte en CRM


Una vez que se tenga el rdl modificado con la imagen cuya formula invoca a la librería y el servidor de reporte configurado, se procede a publicar el reporte. Para esta opción volvemos a Dynamic CRM donde editaremos el reporte y usaremos la opción desplegable que nos permite seleccionar un archivo para subirlo

SNAGHTML2ffd86d7

Se localiza el rdl, guardar y lanzar el reporte

SNAGHTML2ffee2db

El reporte resultante nos mostrara un listado de cliente y debajo el mapa con su localización, la cual pintara un color por el territorio al que pertenecen

image

 

Recursos


Guía para desarrolladores de la versión 2 del API de Google Static Maps

ver la sección de titulo “Marcadores”

Reporting Service - .net dll integration - problem security access with web service and file

 

Código


No hay comentarios:

Publicar un comentario