Esempio: creare un plug-in personalizzato per utilizzare l'API Google Maps come provider di dati geospaziali
Puoi utilizzare un plug-in personalizzato per utilizzare dati geospaziali di un provider di dati preferito anziché utilizzare l'API Bing Maps predefinita in Field Service e Project Service.
L'esempio: è disponibile in: Plug-in personalizzato per utilizzare l'API Google Maps come provider di dati geospaziali (Dynamics 365)
Prerequisiti
Per scaricare il progetto di esempio e per ripristinare i pacchetti NuGet utilizzati nel progetto di esempio è necessaria una connessione a Internet.
Requisiti
La soluzione Dynamics 365 Field Service deve essere installata nell'istanza di Dynamics 365 (online). Ulteriori informazioni: Installare e configurare Field Service
Fornisci la tua chiave API Google nel file GoogleDataContracts.cs nell'esempio:
public const string GoogleApiKey = "<PROVIDE YOUR GOOGLE API KEY";
Dimostra
In questo esempio viene mostrato come creare un plug-in personalizzato per le azioni msdyn_RetrieveDistanceMatrix e msdyn_GeocodeAddress in Universal Resource Scheduling allo scopo di utilizzare l'API Google Maps per dati geospaziali anziché l'API Bing Maps predefinita.
Eseguire l'esempio
In questo esempio viene generato un file di assembly del plug-in: CustomPlugin-FS-Geospatial.dll.
- Scaricare o clonare il repository degli esempi.
- Vai alla posizione in cui hai scaricato o clonato il repository sul tuo computer, accedi alla cartella field-service/CustomPlugin-FS-Geospatial e fai doppio clic sul file CustomPlugin-FS-Geospatial.sln per aprire la soluzione in Visual Studio.
- In Visual Studio, seleziona Compila>Compila soluzione. I pacchetti NuGet utilizzati nella soluzione saranno scaricati automaticamente se l'opzione per ripristinare i pacchetti NuGet automaticamente durante la generazione di un progetto è abilitata in Visual Studio. Ulteriori informazioni: Abilitazione e disabilitazione del ripristino di pacchetti
Dopo l'esecuzione dell'esempio
Dopo aver eseguito (compilato) correttamente l'esempio, un file di assembly del plug-in personalizzato, CustomPlugin-FS-Geospatial.dll, risulterà disponibile nella cartella <Project>\bin\debug
. Registra il file di assembly del plug-in personalizzato dell'esempio nell'istanza di Dynamics 365 (online) (online) per poter utilizzare il plug-in e quindi l'API Google Maps anziché l'API Bing Maps predefinita. Ulteriori informazioni: Registrare e distribuire il plug-in personalizzato
Codice dell'esempio di plug-in per l'azione 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);
}
}
}
}
Codice dell'esempio di plug-in per l'azione 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());
}
}
}
Informativa sulla privacy
Puoi utilizzare il codice di esempio per interagire con servizi di terze parti le cui procedure in termini di privacy e sicurezza possono differire da quelle di Microsoft Dynamics 365. SE SI INVIANO DATI A SERVIZI DI TERZI, TALI DATI SARANNO TRATTATI SULLA BASE DELLE RISPETTIVE INFORMATIVE SULLA PRIVACY. Per evitare qualsiasi dubbio, i dati condivisi all'esterno di Microsoft Dynamics 365 non sono coperti dai contratti Microsoft Dynamics 365 o dal Centro protezione Microsoft Dynamics 365. Ti consigliamo di leggere le Informative sulla privacy degli altri servizi.
Vedi anche
Creazione di un plug-in personalizzato per utilizzare il provider di dati geospaziali preferito