Del via


Eksempel: Opprett en egendefinert programtillegg for å bruke Google Maps API som leverandør av geospatiale data

Du kan bruke en egendefinert plugin-modul til å hente geospatiale data fra den foretrukne dataleverandøren i stedet for å bruke standard-API-en i Felttjeneste og Prosjekttjeneste.

Et eksempel er tilgjengelig på: Egendefinert plugin-modul for å bruke Google Maps API som geospatial dataleverandør (Dynamics 365). Dette eksemplet viser hvordan du oppretter en egendefinert plugin-modul for msdyn_GeocodeAddress og msdyn_RetrieveDistanceMatrix handlinger i Universal Resource Scheduling for å bruke Google Maps API for geospatiale data i stedet for å bruke standard-API-en.

Forutsetning

  • Det kreves en Internett-tilkobling for å laste ned eksempelprosjektet og gjenopprette NuGet-pakkene som brukes i eksempelprosjektet.

  • Dynamics 365 Field Service-løsningen må være installert på Dynamics 365 (online)-forekomsten. Finn ut mer i Installer og konfigurer Felttjeneste.

  • Du angir din egen Google API-nøkkel i GoogleDataContracts.cs-filen i eksemplet:

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

Kjør eksemplet

Eksemplet genererer en samlingsfil for plugin-modulen: CustomPlugin-FS-Geospatial.dll.

  1. Last ned eller klon eksempelrepositoriet.

  2. Naviger til plasseringen der du lastet ned eller klonet repositoriet på datamaskinen, gå til mappen field-service/CustomPlugin-FS-Geospatial , og dobbeltklikk på CustomPlugin-FS-Geospatial.sln filen for å åpne løsningen i Visual Studio.

  3. I Visual Studio velger du Bygg>build-løsning. NuGet-pakker i løsningen lastes ned automatisk hvis du aktiverer alternativet for å gjenopprette NuGet-pakker når du bygger et prosjekt i Visual Studio. Finn ut mer i Aktivering og deaktivering av pakkegjenoppretting.

Når du har kjørt (bygget) eksemplet, blir en egendefinert plugin-samlingsfil, CustomPlugin-FS-Geospatial.dll, tilgjengelig i <Project>\bin\debug mappen. Registrer den egendefinerte plugin-modulen i dynamics 365-forekomsten (på nettet) for å kunne bruke plugin-modulen til å bruke Google Maps-API-en i stedet for standard-API-en. Finn ut mer i Registrer og distribuer den egendefinerte plugin-modulen.

Eksempelkode for plugin-modul for msdyn_GeocodeAddress handling

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);
            }

        }
    }
}

Eksempelkode for plugin-modul for msdyn_RetrieveDistanceMatrix handling

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());
        }
    }
}

Ansvarsfraskrivelse for personvern

Du kan bruke eksempelkode til å samhandle med tredjepartstjenester som har en annen personvern- og sikkerhetspraksis enn Microsoft Dynamics 365. HVIS DU SENDER INN DATA TIL TREDJEPARTSTJENESTER, STYRES SLIKE DATA AV DERES RESPEKTIVE PERSONVERNERKLÆRINGER. For å unngå tvil dekkes ikke data som deles utenfor Microsoft Dynamics 365, av Microsoft Dynamics 365-avtalen(e) eller klareringssenteret for Microsoft Dynamics 365. Vi oppfordrer deg til å lese gjennom disse andre personvernerklæringene.

Flere ressurser