Mostrando entradas con la etiqueta Reporting Service. Mostrar todas las entradas
Mostrando entradas con la etiqueta Reporting Service. Mostrar todas las entradas

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


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

 

Introducción


La utilización de mapas para aportar valor en las aplicaciones es un aspecto cada vez mas requerido, las aplicaciones CRM están especializadas en trabar con cliente por lo que conocer su distribución aporta valor al usuario.

En este caso uniremos tres tecnologías Dynamic CRM el cual nos aportara los datos de las cuentas, Reporting Service para el listado de información, y Google Maps para obtener la imagen del mapas que será incrustado en el reporte

Por lo extenso del articulo se realizaran dos partes, esta primera donde se verán los pasos necesarios para la creación del reporte y su edición en el Visual Studio

Una segunda parte se encargara de presentar como integrar la librería dll con el reporte y su posterior publicación en CRM.

 

Creación del reporte desde Dynamic CRM


El primer paso será definir la estructura básica del reporte, para esta tarea nos ayudaremos con el el wizard que provee CRM.

1- Creamos el nuevo reporte

image

 

2- Se define como entidad primaria al cliente

image

 

3- Se definen los filtros si hace falta, en este caso solo listaremos los clientes de Buenos Aires

image

 

4- Se definen las columnas del reporte

image

 

5- Se aceptan los cambios validando que la entidad asociada sea la cuenta

image

 

Estos pasos nos permitirán contar con una estructura básica del reporte que mas tarde usaremos para vincular con la librería encargada de generar la imagen del mapa.

Ejecutamos el reporte para validar que este correcto

SNAGHTML20981b19

image

 

Edición Reporte desde Visual Studio 2008


El siguiente paso requiere de Visual Studio 2008 con las tools de Business Intelligence las cuales se instalan al agregar el Sql Server 2008 Express Advanced Services, este incluye el servicio de Reporting Service.

En el Visual Studio deberíamos poder crear un proyecto como el siguiente:

SNAGHTML2abc8dae

 

En CRM ubicamos el reporte y lo editamos

image

Usamos la opción para descargar el reporte como archivo rdl

image

El archivo descargado lo deberíamos ubicar en la carpeta del proyecto que creamos con el Visual Studio

image

Para luego agregarlo al proyecto

SNAGHTML2acbd8d2

 

Si editamos el reporte veremos que se conserva el diseño, así como la información de conexión y campos disponibles

SNAGHTML2acf227e

 

Es aquí donde vamos a definir la invocación a la librería que devolverá la imagen con el mapa de google representando la ubicación de los cuentas.

En el siguiente articulo veremos como crear el componente y vincularlo al reporte.