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
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
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
veremos un dialogo del cual no interesa la opción “Referencias”
Usaremos la opción de “Agregar” para buscar la dll que creamos en el paso anterior
Arrastramos el control imagen al diseñador del reporte
el control el diseñador del reporte nos despliega el dialogo donde podremos definir la formula que invocara al método de nuestra libreria
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”.
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>
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
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
Se localiza el rdl, guardar y lanzar el reporte
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
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