Eksempel: Opprett egendefinert plugin-modul for å bruke Google Maps API som leverandør av geospatiale data
Du kan bruke en egendefinert plugin-modul for å bruke geospatiale data fra en dataleverandør du velger, i stedet for standard Bing Maps-API i Field Service og Project Service.
Eksempel er tilgjengelig her: Egendefinert plugin-modul for å bruke Google Maps API som geospatial dataleverandør (Dynamics 365)
Forhåndskrav
En Internett-tilkobling kreves for å laste ned eksempelprosjektet og gjenopprette NuGet-pakkene som brukes i eksempelprosjektet.
Forutsetninger
Dynamics 365 Field Service-løsning må være installert på Dynamics 365 (online)-forekomsten. Mer informasjon: Installere og konfigurere Field Service
Angi din egen Google API-nøkkel i GoogleDataContracts.cs-filen i eksemplet:
public const string GoogleApiKey = "<PROVIDE YOUR GOOGLE API KEY";
Viser
Dette eksempelet viser hvordan du oppretter en egendefinert plugin-modul for msdyn_GeocodeAddress- og msdyn_RetrieveDistanceMatrix-handlinger i Universal Resource Scheduling for å bruke Google Maps API-et for geospatiale data i stedet for å bruke standard Bing Maps-API-et.
Kjøre eksemplet
Dette eksempelet genererer en plugin-modul-samlingsfil: CustomPlugin-FS Geospatial.dll.
- Last ned eller klon eksempelkopien.
- Gå til stedet der du lastet ned eller klonet repo på datamaskinen, gå til mappen field-service/CustomPlugin-FS-Geospatial, og dobbeltklikk på filen CustomPlugin-FS-Geospatial.sln for å åpne løsningen i Visual Studio.
- I Visual Studio velger du Bygg>Bygg løsning. NuGet-pakkene som brukes i løsningen, blir lastet ned automatisk hvis alternativet for å gjenopprette NuGet-pakker automatisk ved bygging av et prosjekt, aktiveres i Visual Studio. Mer informasjon: Aktivere og deaktivere pakkegjenoppretting
Når du har kjørt eksemplet
Når du har kjørt (bygget) eksemplet, vil en egendefinert plugin-modulsamlingsfil, CustomPlugin-FS Geospatial.dll, bli tilgjengelig i <Project>\bin\debug
-mappen. Du må registrere eksempelsamlingen med egendefinerte programtillegg på Dynamics 365 (online)-forekomsten for å kunne bruke programtillegget til å bruke API-en for Google Maps i stedet for standard API for Bing-kart. Hvis du vil ha mer informasjon: Registere og distribuere din egendefinerte plugin-modul.
Plugin-moduleksempelkode 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);
}
}
}
}
Plugin-moduleksempelkode 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());
}
}
}
Fraskrivelse av personvernerklæring
Du kan bruke eksempelkode for å samhandle med tredjepartstjenester der praksisen for personvern og sikkerhet kan være forskjellig fra praksisen for Microsoft Dynamics 365. HVIS DU SENDER INN DATA TIL TREDJEPARTSTJENESTER, ER SLIKE DATA UNDERLAGT DE RESPEKTIVE PERSONVERNERKLÆRINGENE. For å unngå misforståelser: Data som deles utenfor Microsoft Dynamics 365, dekkes ikke av Microsoft Dynamics 365-avtalen(e) eller Microsoft Dynamics 365 Trust Center. Vi oppfordrer deg til å lese gjennom disse andre personvernerklæringene.
Se også
Opprette egendefinert plugin-modul for å bruke din foretrukne geospatiale dataleverandør