Compartir a través de


Ejemplo: Crea un complemento personalizado para usar la API de Google Maps como proveedor de datos geoespaciales

Puede usar un complemento personalizado para usar datos geoespaciales de un proveedor de datos de su elección en lugar de usar la API de Bing Maps predeterminada en Field Service y Project Service.

Un ejemplo está disponible aquí: Complemento personalizado para usar la API de Google Maps como proveedor de datos geoespaciales (Dynamics 365)

Prerrequisitos

Se requiere una conexión a Internet para descargar el proyecto de ejemplo y para restablecer los paquetes NuGet que se usan en el proyecto de ejemplo.

Requisitos

  • La solución Dynamics 365 Field Service debe estar instalada en su instancia de Dynamics 365 (en línea). Más información: Instalar y configurar Field Service

  • Proporcione su propia clave de API de Google en el archivo GoogleDataContracts.cs del ejemplo:

    public const string GoogleApiKey = "<PROVIDE YOUR GOOGLE API KEY";

Demostraciones

En este ejemplo, se muestra cómo crear un complemento personalizado para las acciones msdyn_GeocodeAddress y msdyn_RetrieveDistanceMatrix en Universal Resource Scheduling para usar la API de Google Maps para datos geoespaciales en lugar de usar la API predeterminada de Bing Maps.

Ejecución del ejemplo

En este ejemplo se genera un archivo de ensamblado de complemento: CustomPlugin-FS-Geospatial.dll.

  1. Descargue o clone el repositorio de ejemplos.
  2. Vaya a la ubicación donde descargó o clonó el repositorio en el equipo, vaya a la carpeta field-service/CustomPlugin-FS-Geospatial y haga doble clic en el archivo CustomPlugin-FS-Geospatial.sln para abrir la solución en Visual Studio.
  3. En Visual Studio, seleccione Compilar>solución de compilación. Los paquetes NuGet usados en la solución se descargarán automáticamente si la opción para restaurar paquetes NuGet automáticamente al compilar un proyecto está habilitada en Visual Studio. Más información: Habilitación y deshabilitación de la restauración de paquetes

Después de ejecutar el ejemplo

Una vez que haya ejecutado (compilado) correctamente el ejemplo, un archivo de ensamblado de complemento personalizado, CustomPlugin-FS-Geospatial.dll, estará disponible en la <Project>\bin\debug carpeta. Registre el ensamblado de complemento personalizado de ejemplo en su instancia de Dynamics 365 (en línea) para poder usar el complemento para usar la API de Google Maps en lugar de la API de Bing Maps predeterminada. Más información: Registrar e implementar el complemento personalizado

Código de ejemplo del plug-in para la acción msdyn_GeocodeAddress

using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Text;
using Microsoft.Crm.Sdk.Samples.GoogleDataContracts;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace Microsoft.Crm.Sdk.Samples
{

    /// <summary>
    /// msdyn_GeocodeAddress Plugin.
    /// </summary>  
    public class msdyn_GeocodeAddress : IPlugin
    {
        const string PluginStatusCodeKey = "PluginStatus";
        const string Address1Key = "Line1";
        const string CityKey = "City";
        const string StateKey = "StateOrProvince";
        const string PostalCodeKey = "PostalCode";
        const string CountryKey = "Country";
        const string LatitudeKey = "Latitude";
        const string LongitudeKey = "Longitude";
        const string LcidKey = "Lcid";

        public void Execute(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
            {
                throw new InvalidPluginExecutionException("serviceProvider");
            }

            // Obtain the execution context service from the service provider.
            IPluginExecutionContext PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            // Obtain the organization factory service from the service provider.
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

            // Use the factory to generate the organization service.
            IOrganizationService OrganizationService = factory.CreateOrganizationService(PluginExecutionContext.UserId);

            // Obtain the tracing service from the service provider.
            ITracingService TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            ExecuteGeocodeAddress(PluginExecutionContext, OrganizationService, TracingService);

        }


        /// <summary>
        /// Retrieve geocode address using Google Api
        /// </summary>
        /// <param name="pluginExecutionContext">Execution context</param>
        /// <param name="organizationService">Organization service</param>
        /// <param name="tracingService">Tracing service</param>
        /// <param name="notificationService">Notification service</param>
        public void ExecuteGeocodeAddress(IPluginExecutionContext pluginExecutionContext, IOrganizationService organizationService,  ITracingService tracingService)
        {
            //Contains 5 fields (string) for individual parts of an address
            ParameterCollection InputParameters = pluginExecutionContext.InputParameters;
            // Contains 2 fields (double) for resultant geolocation
            ParameterCollection OutputParameters = pluginExecutionContext.OutputParameters;
            //Contains 1 field (int) for status of previous and this plugin
            ParameterCollection SharedVariables = pluginExecutionContext.SharedVariables;

            tracingService.Trace("ExecuteGeocodeAddress started. InputParameters = {0}, OutputParameters = {1}", InputParameters.Count().ToString(), OutputParameters.Count().ToString());
            

            try
            {
                // If a plugin earlier in the pipeline has already geocoded successfully, quit 
                if ((double)OutputParameters[LatitudeKey] != 0d || (double)OutputParameters[LongitudeKey] != 0d) return;

                // Get user Lcid if request did not include it
                int Lcid = (int)InputParameters[LcidKey];
                string _address = string.Empty;
                if (Lcid == 0)
                {
                    var userSettingsQuery = new QueryExpression("usersettings");
                    userSettingsQuery.ColumnSet.AddColumns("uilanguageid", "systemuserid");
                    userSettingsQuery.Criteria.AddCondition("systemuserid", ConditionOperator.Equal, pluginExecutionContext.InitiatingUserId);
                    var userSettings = organizationService.RetrieveMultiple(userSettingsQuery);
                    if (userSettings.Entities.Count > 0)
                        Lcid = (int)userSettings.Entities[0]["uilanguageid"];
                }

                // Arrange the address components in a single comma-separated string, according to LCID
                _address = GisUtility.FormatInternationalAddress(Lcid,
                    (string)InputParameters[Address1Key], 
                    (string)InputParameters[PostalCodeKey], 
                    (string)InputParameters[CityKey], 
                    (string)InputParameters[StateKey], 
                    (string)InputParameters[CountryKey]);

                // Make Geocoding call to Google API
                WebClient client = new WebClient();
                var url = $"https://{GoogleConstants.GoogleApiServer}{GoogleConstants.GoogleGeocodePath}/json?address={_address}&key={GoogleConstants.GoogleApiKey}";
                tracingService.Trace($"Calling {url}\n");
                string response = client.DownloadString(url);   // Post ...

                tracingService.Trace("Parsing response ...\n");
                DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(GeocodeResponse));    // Deserialize response json
                object objResponse = jsonSerializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(response)));     // Get response as an object
                GeocodeResponse geocodeResponse = objResponse as GeocodeResponse;       // Unbox into our data contracted class for response

                tracingService.Trace("Response Status = " + geocodeResponse.Status + "\n");
                if (geocodeResponse.Status != "OK")
                    throw new ApplicationException($"Server {GoogleConstants.GoogleApiServer} application error (Status {geocodeResponse.Status}).");

                tracingService.Trace("Checking geocodeResponse.Result...\n");
                if (geocodeResponse.Results != null)
                {
                    if (geocodeResponse.Results.Count() == 1)
                    {
                        tracingService.Trace("Checking geocodeResponse.Result.Geometry.Location...\n");
                        if (geocodeResponse.Results.First()?.Geometry?.Location != null)
                        {
                            tracingService.Trace("Setting Latitude, Longitude in OutputParameters...\n");

                            // update output parameters
                            OutputParameters[LatitudeKey] = geocodeResponse.Results.First().Geometry.Location.Lat;
                            OutputParameters[LongitudeKey] = geocodeResponse.Results.First().Geometry.Location.Lng;

                        }
                        else throw new ApplicationException($"Server {GoogleConstants.GoogleApiServer} application error (missing Results[0].Geometry.Location)");
                    }
                    else throw new ApplicationException($"Server {GoogleConstants.GoogleApiServer} application error (more than 1 result returned)");
                }
                else throw new ApplicationException($"Server {GoogleConstants.GoogleApiServer} application error (missing Results)");
            }
            catch (Exception ex)
            {
                // Signal to subsequent plugins in this message pipeline that geocoding failed here.
                OutputParameters[LatitudeKey] = 0d;
                OutputParameters[LongitudeKey] = 0d;

                //TODO: You may need to decide which caught exceptions will rethrow and which ones will simply signal geocoding did not complete.
                throw new InvalidPluginExecutionException(string.Format("Geocoding failed at {0} with exception -- {1}: {2}"
                    , GoogleConstants.GoogleApiServer, ex.GetType().ToString(), ex.Message), ex);
            }

        }
    }
}

Código de ejemplo de plugin para la acción "msdyn_RetrieveDistanceMatrix"

using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Text;
using Microsoft.Crm.Sdk.Samples.GoogleDataContracts;
using Microsoft.Xrm.Sdk;
using static Microsoft.Crm.Sdk.Samples.GoogleDataContracts.DistanceMatrixResponse.CResult.CElement;

namespace Microsoft.Crm.Sdk.Samples
{

    /// <summary>
    /// msdyn_RetrieveDistanceMatrix Plugin.
    /// </summary>
    public class msdyn_RetrieveDistance : IPlugin
    {
        const string PluginStatusCodeKey = "PluginStatus";
        const string SourcesKey = "Sources";
        const string TargetsKey = "Targets";
        const string MatrixKey = "Result";

        /// <summary>
        /// Initializes a new instance of the msdyn_RetrieveDistance class
        /// </summary>
        /// <param name="unsecure"></param>
        /// <param name="secure"></param>
        public msdyn_RetrieveDistance(string unsecure, string secure)
        {
            // TODO: Implement your custom configuration handling.
        }

        /// <summary>
        /// Execute the plugin
        /// </summary>
        /// <param name="serviceProvider"></param>
        public void Execute(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
            {
                throw new InvalidPluginExecutionException("serviceProvider");
            }

            // Obtain the execution context service from the service provider.
            IPluginExecutionContext PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            // Obtain the organization factory service from the service provider.
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

            // Use the factory to generate the organization service.
            IOrganizationService OrganizationService = factory.CreateOrganizationService(PluginExecutionContext.UserId);

            // Obtain the tracing service from the service provider.
            ITracingService TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            ExecuteDistanceMatrix(PluginExecutionContext, OrganizationService, TracingService);

        }

        public void ExecuteDistanceMatrix(IPluginExecutionContext pluginExecutionContext, IOrganizationService organizationService, ITracingService tracingService)
        {
            //Contains 2 fields (EntityCollection) for sources and targets
            ParameterCollection InputParameters = pluginExecutionContext.InputParameters;
            // Contains 1 field (EntityCollection) for results
            ParameterCollection OutputParameters = pluginExecutionContext.OutputParameters;
            //Contains 1 field (int) for status of previous and this plugin
            ParameterCollection SharedVariables = pluginExecutionContext.SharedVariables;

            tracingService.Trace("ExecuteDistanceMatrix started.  InputParameters = {0},OutputParameters = {1}", InputParameters.Count().ToString(), OutputParameters.Count().ToString());

            try
            {
                // If a plugin earlier in the pipeline has already retrieved a distance matrix successfully, quit 
                if (OutputParameters[MatrixKey] != null)
                    if (((EntityCollection)OutputParameters[MatrixKey]).Entities != null)
                        if (((EntityCollection)OutputParameters[MatrixKey]).Entities.Count > 0) return;

                // Make Distance Matrix call to Google API
                WebClient client = new WebClient();
                var url = String.Format($"https://{GoogleConstants.GoogleApiServer}{GoogleConstants.GoogleDistanceMatrixPath}/json"
                    + "?units=imperial"
                    + $"&origins={string.Join("|", ((EntityCollection)InputParameters[SourcesKey]).Entities.Select(e => e.GetAttributeValue<double?>("latitude") + "," + e.GetAttributeValue<double?>("longitude")))}"
                    + $"&destinations={string.Join("|", ((EntityCollection)InputParameters[TargetsKey]).Entities.Select(e => e.GetAttributeValue<double?>("latitude") + "," + e.GetAttributeValue<double?>("longitude")))}"
                    + $"&key={GoogleConstants.GoogleApiKey}");
                tracingService.Trace($"Calling {url}\n");
                string response = client.DownloadString(url);   // Post ...

                tracingService.Trace("Parsing response ...\n");
                DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(DistanceMatrixResponse));    // Deserialize response json
                object objResponse = jsonSerializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(response)));     // Get response as an object
                DistanceMatrixResponse distancematrixResponse = objResponse as DistanceMatrixResponse;       // Unbox as our data contracted class for response

                tracingService.Trace("Response Status = " + distancematrixResponse.Status + "\n");
                if (distancematrixResponse.Status != "OK")
                    throw new ApplicationException($"Server {GoogleConstants.GoogleApiServer} application error (Status={distancematrixResponse.Status}). {distancematrixResponse.ErrorMessage}");

                tracingService.Trace("Checking distancematrixResponse.Results...\n");
                if (distancematrixResponse.Rows != null)
                {
                    tracingService.Trace("Parsing distancematrixResponse.Results.Elements...\n");

                    // build and update output parameter
                    var result = new EntityCollection();
                    result.Entities.AddRange(distancematrixResponse.Rows.Select(r => ToEntity(r.Columns.Select(c => ToEntity(c.Status, c.Duration, c.Distance)).ToArray())));
                    OutputParameters[MatrixKey] = result;

                }
                else throw new ApplicationException($"Server {GoogleConstants.GoogleApiServer} application error (missing Rows)");
            }
            catch (Exception ex)
            {
                // Signal to subsequent plugins in this message pipeline that retrieval of distance matrix failed here.
                OutputParameters[MatrixKey] = null;

                //TODO: You may need to decide which caught exceptions will rethrow and which ones will simply signal geocoding did not complete.
                throw new InvalidPluginExecutionException(string.Format("Geocoding failed at {0} with exception -- {1}: {2}"
                    , GoogleConstants.GoogleApiServer, ex.GetType().ToString(), ex.Message), ex);
            }

            // For debugging purposes, throw an exception to see the details of the parameters
            CreateExceptionWithDetails("Debugging...", InputParameters, OutputParameters, SharedVariables);
        }

        private Entity ToEntity(string status, CProperty duration, CProperty meters)
        {
            var e = new Entity("organization");
            e["status"] = status;
            if (status.ToUpper() == "OK")
            {
                e["miles"] = meters.Value * 0.000621371d;      // Convert to miles
                e["duration"] = duration.Value;
            }
            else
            {                                        // either NOT_FOUND or ZERO_RESULTS
                e["miles"] = 0d;
                e["duration"] = 0d;
            }
            return e;
        }

        private Entity ToEntity(params Entity[] entities)
        {
            var c = new EntityCollection();
            c.Entities.AddRange(entities);
            var e = new Entity("organization");
            e[MatrixKey] = c;
            return e;
        }

        private void CreateExceptionWithDetails(string message, ParameterCollection inputs, ParameterCollection outputs, ParameterCollection shareds)
        {
            StringBuilder sb = new StringBuilder(message + "\n");
            sb.AppendLine("InputParameters -- ");
            foreach (var item in inputs)
            {
                sb.AppendLine("\t" + item.Key + " : '" + item.Value + "' ");
                if (((EntityCollection)item.Value).Entities != null)
                    ((EntityCollection)item.Value).Entities.ToList().ForEach(e => sb.AppendLine("\t\t" + e.GetAttributeValue<double>("latitude").ToString() + "," + e.GetAttributeValue<double>("longitude").ToString()));
            }
            if (outputs != null)
            {
                sb.AppendLine("OutputParameters -- ");
                foreach (var item in outputs)
                {
                    sb.AppendLine("\t" + item.Key + " : '" + item.Value + "' ");
                    if (item.Value != null)
                        if (((EntityCollection)item.Value).Entities != null)
                            ((EntityCollection)item.Value).Entities.ToList().ForEach(r => {
                                sb.AppendLine("\t\t" + r.GetAttributeValue<EntityCollection>(MatrixKey).ToString());
                                if (r.GetAttributeValue<EntityCollection>(MatrixKey).Entities != null)
                                    r.GetAttributeValue<EntityCollection>(MatrixKey).Entities.ToList().ForEach(e => sb.AppendLine("\t\t" + e.GetAttributeValue<double>("distance").ToString() + "," + e.GetAttributeValue<double>("duration").ToString()));

                            });
                }
            }
            sb.AppendLine("SharedVariables -- ");
            foreach (var item in shareds) sb.AppendLine("\t" + item.Key + " : '" + item.Value + "' ");
            throw new InvalidPluginExecutionException(sb.ToString());
        }
    }
}

Exención de responsabilidad del aviso de privacidad

Puede usar código de muestra para interactuar con servicios de terceros cuyas prácticas de privacidad y seguridad pueden diferir de las de Microsoft Dynamics 365. SI ENVÍA DATOS A SERVICIOS DE TERCEROS, DICHOS DATOS SE RIGEN POR SUS RESPECTIVAS DECLARACIONES DE PRIVACIDAD. Para evitar dudas, los datos compartidos fuera de Microsoft Dynamics 365 no están cubiertos por los contratos de Microsoft Dynamics 365 ni por el Centro de confianza de Microsoft Dynamics 365. Le recomendamos que revise estas otras declaraciones de privacidad.

Consulte también

Cree un complemento personalizado para usar su proveedor de datos geoespaciales preferido

Registrar e implementar el complemento personalizado para usar el proveedor de datos geoespaciales preferido