Bing Maps y Servicios Web de NAV

Vamos a hacer una aplicación de WPF que obtenga los datos de NAV vía servicios web.

Para ello utilizaremos:

- NAV 2009 R2

- Visual Studio 2010

- Control de Bing Maps para WPF (descargar en https://www.microsoft.com/download/en/details.aspx?displaylang=en&id=27165, y más información en https://msdn.microsoft.com/en-us/library/hh830431.aspx)

- Geocode Service de Bing Maps (más información en https://msdn.microsoft.com/en-us/library/cc966793.aspx)

La idea es que a partir de un número de factura obtengamos los datos de cabecera de factura de compra y mostremos la dirección en un mapa en nuestra aplicación de escritorio. Aquí los distintos pasos:

- NAV: CodeUnit

- VS: Añadir referencias a los servicios

- VS: Crear nuestro mapa

- VS: Intercambiar datos con NAV y Bing Maps

- VS: Unirlo todo

- VS: Personalizar el mapa

- Notas

Iremos explicando cada paso en base al ejemplo adjunto (objetos NAV y proyecto de Visual Studio).

NAV: CodeUnit

Lo primero es crearnos una CodeUnit que podamos utilizar para intercambiar datos con NAV. Para esta CodeUnit creamos la variable:

Name DataType Subtype
SalesInvoiceHeader Record Sales Invoice Header

y las funciones:

GetNombre(NumFactura : Code[10]) Nombre : Text[250]
GetDireccion(NumFactura : Code[10]) Direccion : Text[250]
GetCiudad(NumFactura : Code[10]) Ciudad : Text[100]
GetImporte(NumFactura : Code[10]) Importe : Decimal
SetDireccion(NumFactura : Code[10];Direccion : Text[250]) Res : Boolean
SetCiudad(NumFactura : Code[10];Ciudad : Text[100]) Res : Boolean

Como podéis ver, con unas obtenemos datos a partir de un número de factura dado, y con otras modificamos alguno de los datos. Podéis ver el código de cada una en la CodeUnit adjunta.

Publicamos nuestra CodeUnit desde el form o page de Web Services:

Object Type Object ID Service Name Published
Codeunit 50060 BdemoWS Yes

Si no estáis muy familiarizados con los servicios web podéis revisar este enlace y la nota al final de este artículo: https://blogs.msdn.com/b/nav/archive/2008/08/09/nav-2009-how-to-publish-a-web-service.aspx 

Visual Studio: Añadir referencias a los servicios

Ahora abrimos Visual Studio y creamos un proyecto de WPF:

1

Añadimos la referencia a los servicios web de NAV haciendo click en el proyecto –> Add Service Reference –> Advanced… –> Add Web Reference…

- URL: https://[MachineName]:7047/DynamicsNAV/WS/Codeunit/BdemoWS (salvo que hayamos cambiado la configuración)

- Go

- Web reference name: NAVCodeunit

- Add reference

En el MainWindows.xaml.cs añadimos:

 namespace BMDemo
 {
     using NAVCodeunit;
  
     public partial class MainWindow : Window
     {
          private BdemoWS ws;
  
          public MainWindow()
          {
              InitializeComponent();
  
              ws = new BdemoWS();
              ws.UseDefaultCredentials = true;
          }
      }
 }

Hasta aquí los servicios web de NAV, pero también queremos utilizar los servicios de Bing Maps. Por eso el siguiente paso es obtener una clave para acceder a estos servicios en https://www.bingmapsportal.com y tras registrarnos, My Account –> Create or View keys y crear una clave nueva. La copiamos y luego la utilizaremos en nuestro proyecto.

Ahora References –> Add reference –> Browse, y buscar la dll del Control para WPF que instalamos, el cual normalmente se encuentra en “%PROGRAMFILES%\Bing Maps WPF Control\V1\Libraries\Microsoft.Maps.MapControl.WPF.dll” o “%PROGRAMFILES(x86)%\Bing Maps WPF Control\V1\Libraries\Microsoft.Maps.MapControl.WPF.dll”.

Este control nos permitirá visualizar nuestro mapa, pero ¿cómo podemos a partir de una dirección sacar sus coordenadas y situar el mapa en esta dirección? Para esto usaremos el servicio de Geocode de Bing Maps. Añadir una referencia a este servicio es muy fácil, en nuestro proyecto vamos a References –> Add Service Reference, e introducimos lo siguiente:

- Address: https://dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc?wsdl

- Go

- Namespace: GeocodeService

y añadimos:

 ...
 using Microsoft.Maps.MapControl.WPF;
 using BMDemo.GeocodeService;
  
 namespace BMDemo
 {
  using NAVCodeunit;
  
  public partial class MainWindow : Window
  {
  
 ...

Tras esto nuestras referencias deberían quedar así:

5

¡Todo listo para empezar!

Visual Studio: Crear nuestro mapa

Ahora vamos a MainWindow.xaml. Si queremos crear una aplicación elaborada podríamos utilizar Microsoft Expression para diseñar la interfaz al detalle, y ya luego desde Visual Studio tendríamos el xaml listo y sólo tendríamos que ocuparnos de programar la parte en C#. Por simplicidad lo haremos todo desde Visual Studio.

Añadimos una fila (click sobre el Grid –> Grid Row –> Insert Before) y movemos para que nos queden dos filas, una para los controles y otra para el mapa.

En la parte superior añadimos nuestro formulario, para lo cual arrastramos los controles que necesitamos del Toolbox para que nos quede algo así en la parte superior (grid row = 0) con 4 labels, 5 textbox y 1 button:

4

Creamos el mapa en la parte inferior (grid row = 1):

 <Window x:Class="BMdemo.MainWindow" 
         xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
         xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
         xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF" 
         Title="Bing Maps NAV Demo" Height="700" Width="900" ResizeMode="NoResize">
  
     <Grid> 
         <Grid.RowDefinitions> 
             <RowDefinition Height="202" /> 
             <RowDefinition Height="559*" /> 
         </Grid.RowDefinitions> 
         <Grid.ColumnDefinitions> 
         </Grid.ColumnDefinitions> 
         <m:Map x:Name="MiMapa" CredentialsProvider="[CLAVE_DE_BING_MAPS]" ZoomLevel="2" Grid.Row="1" /> 
     </Grid> 
 </Window>
  

VS: Intercambiar datos con NAV y Bing Maps

Creamos dos funciones para obtener y enviar datos a NAV (y una serie de atributos en la clase):

 private string numfactura = "", dirEmpresa = "", ciudadEmpresa = "", nomEmpresa = "";
 private decimal impEmpresa = 0;
  
 ...
  
 private bool GetNavInfo()
 {
     try
     {
         dirEmpresa = ws.GetDireccion(numfactura);
         ciudadEmpresa = ws.GetCiudad(numfactura);
         nomEmpresa = ws.GetNombre(numfactura);
         impEmpresa = ws.GetImporte(numfactura);
     }
     catch (Exception ex)
     {
          MessageBox.Show(string.Format("Por favor comprueba que los servicios web estén activos. Mensaje de error: {0}", ex.Message));
          return false;
     }
  
     return (dirEmpresa.Length != 0) && (ciudadEmpresa.Length != 0) && (nomEmpresa.Length != 0);
 }
  
 private bool SetNavInfo()
 {
     return ws.SetDireccion(numfactura, direccionTB.Text) && ws.SetCiudad(numfactura, ciudadTB.Text);
 }

Y una función para traer datos de BingMaps (obtener las coordenadas de una dirección) creando también una serie de atributos:

 private Microsoft.Maps.MapControl.WPF.Location mapCenter;
 private MapLayer pushPinLayer;
 private Pushpin pin;
 private double zoom;
  
 ...
  
 private bool GeocodeAddress(string address)
 {
     string key = "[CLAVE_DE_BING_MAPS]";
     GeocodeRequest geocodeRequest = new GeocodeRequest();
     
     geocodeRequest.Credentials = new GeocodeService.Credentials();
     geocodeRequest.Credentials.ApplicationId = key;
     
     geocodeRequest.Query = address;
     
     ConfidenceFilter[] filters = new ConfidenceFilter[1];
     filters[0] = new ConfidenceFilter();
     filters[0].MinimumConfidence = GeocodeService.Confidence.High;
     
     GeocodeOptions geocodeOptions = new GeocodeOptions();
     geocodeOptions.Filters = filters;
     geocodeRequest.Options = geocodeOptions;
  
     GeocodeServiceClient geocodeService = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
     GeocodeResponse geocodeResponse = geocodeService.Geocode(geocodeRequest);
  
     if (geocodeResponse.Results.Length > 0)
     {
         mapCenter.Latitude = geocodeResponse.Results[0].Locations[0].Latitude;
         mapCenter.Longitude = geocodeResponse.Results[0].Locations[0].Longitude;
         
         return true;
     }
  
     return false;
 }

VS: Unirlo todo

Queremos que al introducir un número de factura en el primer textbox y hacer click en OK, se nos rellenen el resto de campos y se muestre la dirección en el mapa, así que vamos al MainWindow.xaml.cs.

Primero inicializamos los atributos de nuestro mapa y situamos un pushpin en un lugar cualquiera:

 public MainWindow()
 {
     InitializeComponent();
  
     ws = new BdemoWS();
     ws.UseDefaultCredentials = true;
     
     pushPinLayer = new MapLayer();
     MiMapa.Children.Add(pushPinLayer);
     
     pin = new Pushpin();
     zoom = 18;
     mapCenter = new Microsoft.Maps.MapControl.WPF.Location();
  
     GeocodeAddress("Puerta del Sol, Madrid");
     pushPinLayer.AddChild(pin, mapCenter);
     MiMapa.SetView(mapCenter, zoom);
 }

Creamos una función para actualizar el mapa con nuestra dirección:

 private void UpdateMap()
 {
     string completeDir = string.Format("{0}, {1}", dirEmpresa, ciudadEmpresa);
  
     if (GeocodeAddress(completeDir))
         MiMapa.SetView(mapCenter, zoom);
         
     else
         MessageBox.Show(string.Format("No se ha encontrado la dirección \"{0}\".", completeDir));
 }

Y llamamos a la función anterior cuando hagamos click en OK y haya un número de factura válido.

 private void okBT_Click(object sender, RoutedEventArgs e)
 {
     numfactura = numfacturaTB.Text;
  
     if (numfactura.Length != 0 && GetNavInfo())
         UpdateMap();
     
     else
         MessageBox.Show("Por favor, introduzca un número de factura válido.");
 }

Ahora si ejecutamos e introducimos un número de factura válido nos aparecerán los datos y el mapa mostrará la dirección:

6

VS: Personalizar el mapa

Por su puesto hay muchas personalizaciones que podemos hacer, tanto al mapa como a la aplicación o formulario en sí.

Podemos por ejemplo dejar que el usuario elija el tipo de vista que quiere del mapa (aerial o road), y para ello añadimos dos radio buttons:

7

y el código para cambiar a una vista o a otra:

 private void aerialRB_Checked(object sender, RoutedEventArgs e)
 {
     MiMapa.Mode = new AerialMode(true);
 }
  
 private void roadRB_Checked(object sender, RoutedEventArgs e)
 {
     MiMapa.Mode = new RoadMode();
 }

O podemos permitir que el usuario modifique un dato dentro de NAV, por ejemplo si se encuentra con una errata o cambio de dirección. Para ello añadimos un button Modificar y hacemos que ejecute lo siguiente al hacer click:

 private void modificarBT_Click(object sender, RoutedEventArgs e)
 {
     if (numfactura.Length != 0 && GetNavInfo())
     {
         if (SetNavInfo())
         {
             MessageBox.Show("Información actualizada correctamente.");
             GetNavInfo();
             direccionTB.Text = dirEmpresa;
             ciudadTB.Text = ciudadEmpresa;
             UpdateMap();
         }
         else
         {
             MessageBox.Show("No se ha podido actualizar la información.");
             GetNavInfo();
             nombreTB.Text = nomEmpresa;
             direccionTB.Text = dirEmpresa;
             ciudadTB.Text = ciudadEmpresa;
             importeTB.Text = impEmpresa.ToString();
             UpdateMap();
         }
     }
 }

Notas:

- Si descargáis el ejemplo, recordad que tenéis que sustituir la ruta y clave siguiente por las vuestras:

La ruta de los servicios web de NAV: https://[MachineName]:7047/DynamicsNAV/WS/Codeunit/BdemoWS

La clave de Bing Maps: "[CLAVE_DE_BING_MAPS]";

- Si vuestro proyecto no reconoce nuestra CodeUnit o los servicios web de NAV, debes probar a acceder a la ruta de los servicios desde Internet Explorer. Si no os aparece la CodeUnit en la lista de servicios publicados, revisad que el servicio de NAV esté apuntando a la base de datos correcta. Si no os aparece nada, revisad que los servicios web no estén parados. Si estáis trabajando en 3 capas y no podéis acceder a los servicios web desde la tercera capa entonces puede que no hayáis configurado bien el entorno (https://msdn.microsoft.com/en-us/library/dd301254.aspx).

- No podemos utilizar el control de BingMaps para WPF para nuestras páginas del RTC mediante el uso de variables DotNet, pues aunque copiemos las dlls del control en la carpeta de Add-Ins de NAV, al abrir el listado de assemblies nos aparecerá un error indicándonos que estas dlls fueron compiladas para .Net 4.0, y solo podemos utilizar hasta 3.5.

- Para más información:

Crear una aplicación usando el control de Bing Maps para WPF: https://msdn.microsoft.com/en-us/library/hh830433.aspx

Desarrollar una aplicación en .Net usando los servicios de Bing Maps: https://msdn.microsoft.com/en-us/library/dd221354.aspx

Juliet R. Moreiro Bockhop

Microsoft Dynamics NAV Support Engineer

 Diego García Álvarez

Microsoft Dynamics NAV Senior Support Engineer

 

“Los ejemplos aquí expuestos se proporcionan sólo como ejemplo ilustrativo y en ningún caso el autor se hace responsable del contenido ni efectos que pueda tener su uso.”