From e3d6df3bca9857759c8007816bf04daeee01aa7a Mon Sep 17 00:00:00 2001 From: Release-Agent <> Date: Thu, 25 Mar 2021 21:17:15 +0000 Subject: [PATCH] Sync up from Mainline branch for CommitId 9b34b0e7e1f4a76c4c3a7483fa6d572c3be34abc --- src/Build.Common.StandardAndLegacy.props | 4 + src/Build.Common.core.props | 4 + src/Build.Common.props | 1 + src/Build.Shared.props | 4 +- .../Client/ConnectionService.cs | 416 ++++---- .../DataverseClient/Client/ServiceClient.cs | 900 +++++++++--------- .../DataverseClient/Client/TraceLoggerBase.cs | 22 +- .../DataverseConnectionStringProcessor.cs | 35 +- .../DataverseClient/Client/Utils/Utils.cs | 148 +-- .../ServiceClientTests.cs | 98 +- .../CdsClient_Core_Tests/TestSupport.cs | 10 +- ...Platform.Dataverse.Client.ReleaseNotes.txt | 125 +-- 12 files changed, 913 insertions(+), 854 deletions(-) diff --git a/src/Build.Common.StandardAndLegacy.props b/src/Build.Common.StandardAndLegacy.props index 6c9cd4d..0ef8329 100644 --- a/src/Build.Common.StandardAndLegacy.props +++ b/src/Build.Common.StandardAndLegacy.props @@ -1,4 +1,8 @@ + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + netcoreapp3.1;net462 netstandard2.0;net462 diff --git a/src/Build.Common.core.props b/src/Build.Common.core.props index e8d9db0..ba3ec86 100644 --- a/src/Build.Common.core.props +++ b/src/Build.Common.core.props @@ -1,5 +1,9 @@ + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + net462;net472;net48;netcoreapp3.0;netcoreapp3.1 false diff --git a/src/Build.Common.props b/src/Build.Common.props index 74e5ddd..4676e46 100644 --- a/src/Build.Common.props +++ b/src/Build.Common.props @@ -1,6 +1,7 @@ + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) v4.6.2 diff --git a/src/Build.Shared.props b/src/Build.Shared.props index 3c7424b..db8849b 100644 --- a/src/Build.Shared.props +++ b/src/Build.Shared.props @@ -1,6 +1,7 @@ + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 2.9.1 3.19.8 4.25.0 @@ -17,7 +18,7 @@ - FORGOT-To-Set-ComponentAreaName + FORGOT-To-Set-ComponentAreaName @@ -44,7 +45,6 @@ 512 true true - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) prompt 4 diff --git a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs index 4a973d6..bf0d008 100644 --- a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs +++ b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs @@ -25,11 +25,12 @@ using System.Xml; using Microsoft.Identity.Client; using Microsoft.PowerPlatform.Dataverse.Client.Auth; +using Microsoft.Extensions.Logging; namespace Microsoft.PowerPlatform.Dataverse.Client { /// - /// Decision switch for the sort of Auth to login to Dataverse with + /// Decision switch for the sort of Auth to login to Dataverse with /// public enum AuthenticationType { @@ -60,64 +61,64 @@ public enum AuthenticationType /// internal sealed class ConnectionService : IConnectionService, IDisposable { - #region variables + #region variables [NonSerializedAttribute] private OrganizationWebProxyClient _svcWebClientProxy; private OrganizationWebProxyClient _externalWebClientProxy; // OAuth specific web service proxy [NonSerializedAttribute] - private WhoAmIResponse user; // Dataverse user entity that is the service. + private WhoAmIResponse user; // Dataverse user entity that is the service. private string _hostname; // Host name of the Dataverse server private string _port; // Port the WebService is on - private string _organization; // Org that is being inquired on.. + private string _organization; // Org that is being inquired on.. private AuthenticationType _eAuthType; // Default setting for Auth Cred; [NonSerializedAttribute] private NetworkCredential _AccessCred; // Network that is accessing used for AD based Auth [NonSerializedAttribute] - private ClientCredentials _UserClientCred; // Describes the user client credential when accessing claims or SPLA based services. + private ClientCredentials _UserClientCred; // Describes the user client credential when accessing claims or SPLA based services. [NonSerializedAttribute] - private string _InternetProtocalToUse = "http"; // Which Internet protocol to use to connect. + private string _InternetProtocalToUse = "http"; // Which Internet protocol to use to connect. - private OrganizationDetail _OrgDetail; // if provided by the calling system, bypasses all discovery server lookup processed. - //private OrganizationDetail _ActualOrgDetailUsed; // Org Detail that was used by the Auth system when it created the proxy. + private OrganizationDetail _OrgDetail; // if provided by the calling system, bypasses all discovery server lookup processed. + //private OrganizationDetail _ActualOrgDetailUsed; // Org Detail that was used by the Auth system when it created the proxy. /// - /// This is the actual Dataverse OrgURI used to connect, which could be influenced by the host name given during the connect process. + /// This is the actual Dataverse OrgURI used to connect, which could be influenced by the host name given during the connect process. /// private Uri _ActualDataverseOrgUri; - private string _LiveID; - private SecureString _LivePass; - private string _DataverseOnlineRegion; // Region of Dataverse Online to use. - private string _ServiceCACHEName = "Microsoft.PowerPlatform.Dataverse.Client.Service"; // this is the base cache key name that will be used to cache the service. + private string _LiveID; + private SecureString _LivePass; + private string _DataverseOnlineRegion; // Region of Dataverse Online to use. + private string _ServiceCACHEName = "Microsoft.PowerPlatform.Dataverse.Client.Service"; // this is the base cache key name that will be used to cache the service. //OAuth Params private PromptBehavior _promptBehavior; // prompt behavior - private string _tokenCachePath; // user specified token cache file path + private string _tokenCachePath; // user specified token cache file path private bool _isOnPremOAuth = false; // Identifies whether the connection is for OnPrem or Online Deployment for OAuth private static string _userId = null; //cached userid reading from config file private bool _isCalledbyExecuteRequest = false; //Flag indicating that the an request called by Execute_Command - private bool _isDefaultCredsLoginForOAuth = false; //Flag indicating that the user is trying to login with the current user id. + private bool _isDefaultCredsLoginForOAuth = false; //Flag indicating that the user is trying to login with the current user id. /// - /// If set to true, will relay any received cookie back to the server. + /// If set to true, will relay any received cookie back to the server. /// Defaulted to true. /// private bool _enableCookieRelay = Utils.AppSettingsHelper.GetAppSetting("PreferConnectionAffinity", true); /// - /// TimeSpan used to control the offset of the token reacquire behavior for none user Auth flows. + /// TimeSpan used to control the offset of the token reacquire behavior for none user Auth flows. /// private readonly TimeSpan _tokenOffSetTimeSpan = new TimeSpan(0, 2, 0); /// - /// if Set to true then the connection is for one use and should be cleand out of cache when completed. + /// if Set to true then the connection is for one use and should be cleand out of cache when completed. /// private bool unqueInstance = false; /// - /// Client or App Id to use. + /// Client or App Id to use. /// private string _clientId; @@ -137,20 +138,20 @@ internal sealed class ConnectionService : IConnectionService, IDisposable internal static string _authority; /// - /// when certificate Auth is used, this is the certificate that is used to execute the connection. + /// when certificate Auth is used, this is the certificate that is used to execute the connection. /// private X509Certificate2 _certificateOfConnection; /// - /// ThumbPrint of the Certificate to use. + /// ThumbPrint of the Certificate to use. /// private string _certificateThumbprint; /// - /// Location where the certificate identified by the Certificate thumb print can be found. + /// Location where the certificate identified by the Certificate thumb print can be found. /// private StoreName _certificateStoreLocation = StoreName.My; /// - /// Uri that will be used to connect to Dataverse for Cert Auth. + /// Uri that will be used to connect to Dataverse for Cert Auth. /// private Uri _targetInstanceUriToConnectTo = null; @@ -169,7 +170,7 @@ internal sealed class ConnectionService : IConnectionService, IDisposable private readonly string WebApiUriFormat = @"{0}://{1}/api/data/v{2}/"; /// - /// format string for Global discovery WebAPI + /// format string for Global discovery WebAPI /// private static readonly string _baseWebApiUriFormat = @"{0}/api/data/v{1}/"; @@ -183,7 +184,7 @@ internal sealed class ConnectionService : IConnectionService, IDisposable /// private static readonly string _commercialGlobalDiscoBaseWebAPIUriFormat = "https://globaldisco.crm.dynamics.com/api/discovery/v{0}/{1}"; /// - /// version of the global discovery service. + /// version of the global discovery service. /// private static readonly string _globlaDiscoVersion = "2.0"; @@ -193,10 +194,10 @@ internal sealed class ConnectionService : IConnectionService, IDisposable private Guid _OrganizationId; /// - /// Max connection timeout property + /// Max connection timeout property /// private static TimeSpan _MaxConnectionTimeout = Utils.AppSettingsHelper.GetAppSettingTimeSpan("MaxDataverseConnectionTimeOutMinutes", Utils.AppSettingsHelper.TimeSpanFromKey.Minutes, new TimeSpan(0, 0, 2, 0)); - + /// /// Tenant ID /// @@ -208,7 +209,7 @@ internal sealed class ConnectionService : IConnectionService, IDisposable private string _EnvironmentId; /// - /// TestHelper for Testing sim. + /// TestHelper for Testing sim. /// private IOrganizationService _testSupportIOrg; #endregion @@ -218,17 +219,17 @@ internal sealed class ConnectionService : IConnectionService, IDisposable /// ************** MSAL Properties **************** /// - /// MSAL Object, Can be either a PublicClient or a Confidential Client, depending on Context. + /// MSAL Object, Can be either a PublicClient or a Confidential Client, depending on Context. /// internal object _MsalAuthClient = null; /// - /// This is carries the result of the token authentication flow to optimize token retrieval. + /// This is carries the result of the token authentication flow to optimize token retrieval. /// internal AuthenticationResult _authenticationResultContainer = null; /// - /// Selected user located as a result, used to optimize token acquire on second round. + /// Selected user located as a result, used to optimize token acquire on second round. /// internal IAccount _userAccount = null; @@ -236,7 +237,7 @@ internal sealed class ConnectionService : IConnectionService, IDisposable /// - /// When true, indicates the construction is coming from a clone process. + /// When true, indicates the construction is coming from a clone process. /// internal bool IsAClone { get; set; } @@ -246,30 +247,30 @@ internal sealed class ConnectionService : IConnectionService, IDisposable public Guid? CallerAADObjectId { get; set; } /// - /// httpclient that is in use for this connection + /// httpclient that is in use for this connection /// internal HttpClient WebApiHttpClient { get; set; } /// /// This ID is used to support Dataverse Telemetry when trouble shooting SDK based errors. - /// When Set by the caller, all Dataverse API Actions executed by this client will be tracked under a single session id for later troubleshooting. - /// For example, you are able to group all actions in a given run of your client ( several creates / reads and such ) under a given tracking id that is shared on all requests. - /// providing this ID when reporting a problem will aid in trouble shooting your issue. + /// When Set by the caller, all Dataverse API Actions executed by this client will be tracked under a single session id for later troubleshooting. + /// For example, you are able to group all actions in a given run of your client ( several creates / reads and such ) under a given tracking id that is shared on all requests. + /// providing this ID when reporting a problem will aid in trouble shooting your issue. /// internal Guid? SessionTrackingId { get; set; } /// /// This will force the server to refresh the current metadata cache with current DB config. - /// Note, that this is a performance impacting event. Use of this flag will slow down operations server side as the server is required to check for consistency on each API call executed. + /// Note, that this is a performance impacting event. Use of this flag will slow down operations server side as the server is required to check for consistency on each API call executed. /// internal bool ForceServerCacheConsistency { get; set; } /// - /// returns the URL to global discovery for querying all instances. + /// returns the URL to global discovery for querying all instances. /// internal static string GlobalDiscoveryAllInstancesUri { get { return string.Format(_commercialGlobalDiscoBaseWebAPIUriFormat, _globlaDiscoVersion, "Instances"); } } /// - /// Format string for calling global disco for a specific instance. + /// Format string for calling global disco for a specific instance. /// private static string GlobalDiscoveryInstanceUriFormat { get { return string.Format(_commercialGlobalDiscoBaseWebAPIUriFormat, _globlaDiscoVersion, "Instances({0})"); } } @@ -303,12 +304,12 @@ internal bool CalledbyExecuteRequest } /// - /// Logging provider for DataverseConnectionServiceobject. + /// Logging provider for DataverseConnectionServiceobject. /// private DataverseTraceLogger logEntry { get; set; } /// - /// Returns Logs from this process. + /// Returns Logs from this process. /// /// internal IEnumerable> GetAllLogs() @@ -323,7 +324,7 @@ internal IEnumerable> GetAllLogs() public bool isLogEntryCreatedLocaly { get; set; } /// - /// Get and Set of network credentials... + /// Get and Set of network credentials... /// internal System.Net.NetworkCredential DataverseServiceAccessCredential { @@ -337,12 +338,12 @@ internal System.Net.NetworkCredential DataverseServiceAccessCredential internal string InternetProtocalToUse { get { return _InternetProtocalToUse; } set { _InternetProtocalToUse = value; } } /// - /// returns the connected organization detail object. + /// returns the connected organization detail object. /// internal OrganizationDetail ConnectedOrganizationDetail { get { return _OrgDetail; } } /// - /// + /// /// internal AuthenticationType AuthenticationTypeInUse { @@ -394,7 +395,7 @@ internal string HostPort } /// - /// Gets / Set the Dataverse Hostname that the web service is listening on. + /// Gets / Set the Dataverse Hostname that the web service is listening on. /// internal string HostName { @@ -404,7 +405,7 @@ internal string HostName /// - /// Returns the Current Dataverse User. + /// Returns the Current Dataverse User. /// internal WhoAmIResponse CurrentUser { @@ -413,8 +414,8 @@ internal WhoAmIResponse CurrentUser } /// - /// Returns the Actual URI used to connect to Dataverse. - /// this URI could be influenced by user defined variables. + /// Returns the Actual URI used to connect to Dataverse. + /// this URI could be influenced by user defined variables. /// internal Uri ConnectOrgUriActual { get { return _ActualDataverseOrgUri; } } @@ -424,27 +425,27 @@ internal WhoAmIResponse CurrentUser internal Uri ConnectODataBaseUriActual { get; set; } /// - /// Flag indicating that the an External connection to Dataverse is used to connect. + /// Flag indicating that the an External connection to Dataverse is used to connect. /// internal bool UseExternalConnection = false; /// - /// Returns the friendly name of the connected org. + /// Returns the friendly name of the connected org. /// internal string ConnectedOrgFriendlyName { get; private set; } /// - /// Returns the endpoint collection for the connected org. + /// Returns the endpoint collection for the connected org. /// internal EndpointCollection ConnectedOrgPublishedEndpoints { get; set; } /// - /// Version Number of the organization, if null Discovery service process was not run or the value returned was unreadable. + /// Version Number of the organization, if null Discovery service process was not run or the value returned was unreadable. /// internal Version OrganizationVersion { get; set; } /// - /// Organization ID of connected org. + /// Organization ID of connected org. /// internal Guid OrganizationId { @@ -482,7 +483,7 @@ internal Guid TenantId } /// - /// Gets or sets the Environment Id. + /// Gets or sets the Environment Id. /// internal string EnvironmentId { @@ -507,13 +508,13 @@ internal string EnvironmentId internal Func> GetAccessTokenAsync { get; set; } /// - /// returns the format string for the baseWebAPI + /// returns the format string for the baseWebAPI /// internal string BaseWebAPIDataFormat { get { return _baseWebApiUriFormat; } } /// /// Gets or Sets the Max Connection timeout for the connection to Dataverse/XRM - /// default is 2min. + /// default is 2min. /// internal static TimeSpan MaxConnectionTimeout { @@ -522,7 +523,7 @@ internal static TimeSpan MaxConnectionTimeout } /// - /// Gets or sets the value to enabled cookie relay on this connection. + /// Gets or sets the value to enabled cookie relay on this connection. /// internal bool EnableCookieRelay { @@ -541,7 +542,7 @@ internal ConnectionService( IOrganizationService testIOrganziationSvc) _testSupportIOrg = testIOrganziationSvc; logEntry = new DataverseTraceLogger(); isLogEntryCreatedLocaly = true; - RefreshInstanceDetails(testIOrganziationSvc, null); + RefreshInstanceDetails(testIOrganziationSvc, null); } /// @@ -568,7 +569,7 @@ internal ConnectionService(OrganizationWebProxyClient externalOrgWebProxyClient, { AttachWebProxyHander(_externalWebClientProxy); - // Set timeouts. + // Set timeouts. _externalWebClientProxy.InnerChannel.OperationTimeout = _MaxConnectionTimeout; _externalWebClientProxy.Endpoint.Binding.SendTimeout = _MaxConnectionTimeout; _externalWebClientProxy.Endpoint.Binding.ReceiveTimeout = _MaxConnectionTimeout; @@ -600,20 +601,21 @@ internal ConnectionService(OrganizationWebProxyClient externalOrgWebProxyClient, internal ConnectionService( AuthenticationType authType, // Only OAuth is supported in this constructor. string orgName, // CRM Organization Name your connecting too - string liveUserId, // Live ID - Live only + string liveUserId, // Live ID - Live only SecureString livePass, // Live Pw - Live Only string crmOnlineRegion, - bool useUniqueCacheName, // tells the system to create a unique cache name for this instance. + bool useUniqueCacheName, // tells the system to create a unique cache name for this instance. OrganizationDetail orgDetail, string clientId, // The client Id of the client registered with Azure - Uri redirectUri, // The redirectUri telling the redirect login window + Uri redirectUri, // The redirectUri telling the redirect login window PromptBehavior promptBehavior, // The prompt behavior for ADAL library string hostName, // Host name to connect to string port, // Port used to connect to bool onPrem, DataverseTraceLogger logSink = null, Uri instanceToConnectToo = null, - bool useDefaultCreds = false) + bool useDefaultCreds = false + ) { if (authType != AuthenticationType.OAuth && authType != AuthenticationType.ClientSecret) throw new ArgumentOutOfRangeException("authType", "This constructor only supports the OAuth or Client Secret Auth types"); @@ -666,14 +668,14 @@ internal ConnectionService( /// Incoming Log Sink data internal ConnectionService( AuthenticationType authType, // Only Certificate is supported in this constructor. - Uri instanceToConnectToo, // set the connection instance to use. - bool useUniqueCacheName, // tells the system to create a unique cache name for this instance. + Uri instanceToConnectToo, // set the connection instance to use. + bool useUniqueCacheName, // tells the system to create a unique cache name for this instance. OrganizationDetail orgDetail, string clientId, // The client Id of the client registered with Azure - Uri redirectUri, // The redirectUri telling the redirect login window + Uri redirectUri, // The redirectUri telling the redirect login window string certThumbprint, // thumb print of the certificate to use - StoreName certStoreName, // Where to find the Certificate identified by the thumb print. - X509Certificate2 certifcate, // loaded and configured certificate to use. + StoreName certStoreName, // Where to find the Certificate identified by the thumb print. + X509Certificate2 certifcate, // loaded and configured certificate to use. string hostName, // Host name to connect to string port, // Port used to connect to bool onPrem, @@ -710,12 +712,12 @@ internal ConnectionService( } /// - /// Loges into Dataverse using the supplied parameters. + /// Loges into Dataverse using the supplied parameters. /// /// public bool DoLogin(out ConnectionService ConnectionObject) { - // Initializes the Dataverse Service. + // Initializes the Dataverse Service. bool IsConnected = IntilizeService(out ConnectionObject); return IsConnected; } @@ -726,14 +728,14 @@ public bool DoLogin(out ConnectionService ConnectionObject) /// private void GenerateCacheKeys(bool useUniqueCacheName) { - // This is to deal with 2 instances of the ConnectionService being created in the Same Running Instance that would need to connect to different Dataverse servers. + // This is to deal with 2 instances of the ConnectionService being created in the Same Running Instance that would need to connect to different Dataverse servers. if (useUniqueCacheName) { - unqueInstance = true; // this instance is unique. + unqueInstance = true; // this instance is unique. _authority = string.Empty; _userId = null; Guid guID = Guid.NewGuid(); - _ServiceCACHEName = _ServiceCACHEName + guID.ToString(); // Creating a unique instance name for the cache object. + _ServiceCACHEName = _ServiceCACHEName + guID.ToString(); // Creating a unique instance name for the cache object. } } @@ -743,7 +745,7 @@ private void GenerateCacheKeys(bool useUniqueCacheName) /// Return true on Success, false on failure private bool IntilizeService(out ConnectionService ConnectionObject) { - // Get the Dataverse Service. + // Get the Dataverse Service. IOrganizationService dvService = this.GetCachedService(out ConnectionObject); if (dvService != null) @@ -757,12 +759,12 @@ private bool IntilizeService(out ConnectionService ConnectionObject) /// /// Try's to gets the Cached Dataverse Service from memory. - /// on Failure, Initialize a New instance. + /// on Failure, Initialize a New instance. /// /// private IOrganizationService GetCachedService(out ConnectionService ConnectionObject) { - // try to get the object from Memory . + // try to get the object from Memory . if (!string.IsNullOrEmpty(_ServiceCACHEName)) { try @@ -783,7 +785,7 @@ private IOrganizationService GetCachedService(out ConnectionService ConnectionOb ConnectionObject = null; if (ConnectionObject == null) { - // No Service found.. Init the Service and try to bring it online. + // No Service found.. Init the Service and try to bring it online. IOrganizationService localSvc = InitServiceAsync().Result; if (localSvc == null) return null; @@ -792,18 +794,18 @@ private IOrganizationService GetCachedService(out ConnectionService ConnectionOb { if (System.Runtime.Caching.MemoryCache.Default.Contains(_ServiceCACHEName)) System.Runtime.Caching.MemoryCache.Default.Remove(_ServiceCACHEName); - // Cache the Service for 5 min. + // Cache the Service for 5 min. System.Runtime.Caching.MemoryCache.Default.Add(_ServiceCACHEName, this, DateTime.Now.AddMinutes(5)); } return localSvc; } else { - //service from Cache .. get user associated with the connection + //service from Cache .. get user associated with the connection try { - // Removed call to WHoAMI as it is amused when picking up cache that the reauth logic will be exercised by the first call to the server. - ConnectionObject.ResetDisposedState(); // resetting disposed state as this object was pulled from cache. + // Removed call to WHoAMI as it is amused when picking up cache that the reauth logic will be exercised by the first call to the server. + ConnectionObject.ResetDisposedState(); // resetting disposed state as this object was pulled from cache. if (ConnectionObject._svcWebClientProxy != null) return (IOrganizationService)ConnectionObject._svcWebClientProxy; else @@ -811,19 +813,19 @@ private IOrganizationService GetCachedService(out ConnectionService ConnectionOb } catch (Exception ex) { - logEntry.Log("Failed to Create a connection to Dataverse", TraceEventType.Error , ex); + logEntry.Log("Failed to Create a connection to Dataverse", TraceEventType.Error , ex); return null; } } } /// - /// Initialize a Connection to Dataverse + /// Initialize a Connection to Dataverse /// /// private async Task InitServiceAsync() { - // Dataverse Service Endpoint to work with + // Dataverse Service Endpoint to work with IOrganizationService dvService = null; Stopwatch dtQueryTimer = new Stopwatch(); try @@ -843,7 +845,7 @@ private async Task InitServiceAsync() if (!IsAClone) { - // Get Version of organization: + // Get Version of organization: Guid guRequestId = Guid.NewGuid(); RetrieveVersionRequest verRequest = new RetrieveVersionRequest() { RequestId = guRequestId }; logEntry.Log(string.Format("Externally provided connection to Dataverse Service - Retrieving Version Info. RequestId:{0}", guRequestId.ToString()), TraceEventType.Verbose); @@ -861,8 +863,8 @@ private async Task InitServiceAsync() } catch (Exception ex) { - // Failed to get version info : - // Log it.. + // Failed to get version info : + // Log it.. logEntry.Log("Failed to retrieve version info from connected Dataverse organization", TraceEventType.Error, ex); } } @@ -901,7 +903,7 @@ private async Task InitServiceAsync() logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Discovery URI is = {0}", CrmUrl), TraceEventType.Information); if (!Uri.IsWellFormedUriString(CrmUrl, UriKind.Absolute)) { - // Throw error here. + // Throw error here. logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Discovery URI is malformed = {0}", CrmUrl), TraceEventType.Error); return null; @@ -917,14 +919,14 @@ private async Task InitServiceAsync() if (_eAuthType == AuthenticationType.Certificate) { - // Certificate based .. get the Cert. + // Certificate based .. get the Cert. if (_certificateOfConnection == null && !string.IsNullOrEmpty(_certificateThumbprint)) { - // Certificate is not passed in. Thumbprint found... try to acquire the cert. + // Certificate is not passed in. Thumbprint found... try to acquire the cert. _certificateOfConnection = FindCertificate(_certificateThumbprint, _certificateStoreLocation, logEntry); if (_certificateOfConnection == null) { - // Fail.. no Cert. + // Fail.. no Cert. throw new Exception("Failed to locate or read certificate from passed thumbprint.", logEntry.LastException); } } @@ -933,7 +935,7 @@ private async Task InitServiceAsync() { if (_eAuthType == AuthenticationType.OAuth) { - // oAuthBased. + // oAuthBased. _UserClientCred.UserName.Password = string.Empty; _UserClientCred.UserName.UserName = string.Empty; } @@ -942,10 +944,10 @@ private async Task InitServiceAsync() OrganizationDetail orgDetail = null; if (_OrgDetail == null) { - // Discover Orgs Url. + // Discover Orgs Url. Uri uCrmUrl = new Uri(CrmUrl); - // This will try to discover any organizations that the user has access too, one way supports AD / IFD and the other supports Claims + // This will try to discover any organizations that the user has access too, one way supports AD / IFD and the other supports Claims OrganizationDetailCollection orgs = null; if (_eAuthType == AuthenticationType.OAuth) @@ -961,7 +963,7 @@ private async Task InitServiceAsync() } - // Check the Result to see if we have Orgs back + // Check the Result to see if we have Orgs back if (orgs != null && orgs.Count > 0) { logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Found {0} Org(s)", orgs.Count), TraceEventType.Information); @@ -977,15 +979,15 @@ private async Task InitServiceAsync() } else { - // error here. + // error here. logEntry.Log("No Organizations found.", TraceEventType.Error); return null; } } else - orgDetail = _OrgDetail; // Assign to passed in value. + orgDetail = _OrgDetail; // Assign to passed in value. - // Try to connect to CRM here. + // Try to connect to CRM here. dvService = await ConnectAndInitServiceAsync(orgDetail, true, uUserHomeRealm); if (dvService == null) @@ -993,10 +995,10 @@ private async Task InitServiceAsync() logEntry.Log("Failed to connect to Dataverse", TraceEventType.Error); return null; } - + if (_eAuthType == AuthenticationType.OAuth || _eAuthType == AuthenticationType.Certificate || _eAuthType == AuthenticationType.ClientSecret) dvService = (OrganizationWebProxyClient)dvService; - + #endregion } @@ -1025,11 +1027,11 @@ private async Task InitServiceAsync() { if (_certificateOfConnection == null && !string.IsNullOrEmpty(_certificateThumbprint)) { - // Certificate is not passed in. Thumbprint found... try to acquire the cert. + // Certificate is not passed in. Thumbprint found... try to acquire the cert. _certificateOfConnection = FindCertificate(_certificateThumbprint, _certificateStoreLocation, logEntry); if (_certificateOfConnection == null) { - // Fail.. no Cert. + // Fail.. no Cert. throw new Exception("Failed to locate or read certificate from passed thumbprint.", logEntry.LastException); } } @@ -1097,7 +1099,7 @@ private async Task InitServiceAsync() if (orgDetail != null && !string.IsNullOrEmpty(orgDetail.OrgDetail.UniqueName)) { - // Found it .. + // Found it .. logEntry.Log(string.Format(CultureInfo.InvariantCulture, "found User Org = {0} in results", _organization), TraceEventType.Information); dvService = await ConnectAndInitServiceAsync(orgDetail.OrgDetail, false, null); if (dvService != null) @@ -1119,7 +1121,7 @@ private async Task InitServiceAsync() } else { - // Error here. + // Error here. logEntry.Log("No Orgs Found", TraceEventType.Information); logEntry.Log(string.Format(CultureInfo.InvariantCulture, "No Organizations Found, Searched online. Region Setting = {0}", _DataverseOnlineRegion) @@ -1129,7 +1131,7 @@ private async Task InitServiceAsync() } finally { - onlineServerList.Dispose(); // Clean up array. + onlineServerList.Dispose(); // Clean up array. } } } @@ -1140,7 +1142,7 @@ private async Task InitServiceAsync() return null; } - // Do a WHO AM I request to make sure the connection is good. + // Do a WHO AM I request to make sure the connection is good. if (!UseExternalConnection) { Guid guIntialTrackingID = Guid.NewGuid(); @@ -1156,13 +1158,13 @@ private async Task InitServiceAsync() } return (IOrganizationService)dvService; - + } #region Login / Discovery Server Exception handlers catch (MessageSecurityException ex) { - // Login to Live Failed. + // Login to Live Failed. logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Invalid Login Information : {0}", ex.Message), TraceEventType.Error, ex); throw ex; @@ -1173,7 +1175,7 @@ private async Task InitServiceAsync() // Check the result for Errors. if (!string.IsNullOrEmpty(ex.Message) && ex.Message.Contains("HTTP status 401")) { - // Login Error. + // Login Error. logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Unable to Login to Dataverse: {0}", ex.Message),TraceEventType.Error, ex); } @@ -1210,7 +1212,7 @@ private async Task InitServiceAsync() } /// - /// Executes a direct login using the current configuration. + /// Executes a direct login using the current configuration. /// /// private async Task DoDirectLoginAsync() @@ -1243,10 +1245,10 @@ private async Task DoDirectLoginAsync() logEntry.Log("Organization Details Unavailable due to SkipOrgDetails flag set to True, to populate organization details on login, do not set SkipOrgDetails or set it to false."); } - // Format the URL for WebAPI service. + // Format the URL for WebAPI service. if (OrganizationVersion != null && OrganizationVersion.Major >= 8) { - // Need to come back to this later to allow it to connect to the correct API endpoint. + // Need to come back to this later to allow it to connect to the correct API endpoint. ConnectODataBaseUriActual = new Uri(string.Format(WebApiUriFormat, _targetInstanceUriToConnectTo.Scheme, _targetInstanceUriToConnectTo.DnsSafeHost, OrganizationVersion.ToString(2))); } } @@ -1257,16 +1259,16 @@ private async Task DoDirectLoginAsync() /// - /// Refresh the organization instance details. + /// Refresh the organization instance details. /// /// ConnectionSvc /// Instance URL private void RefreshInstanceDetails(IOrganizationService dvService, Uri uriOfInstance) { - // Load the organization instance details + // Load the organization instance details if (dvService != null) { - //TODO:// Add Logic here to improve perf by connecting to global disco. + //TODO:// Add Logic here to improve perf by connecting to global disco. Guid guRequestId = Guid.NewGuid(); logEntry.Log(string.Format("Querying Organization Instance Details. Request ID: {0}", guRequestId)); Stopwatch dtQueryTimer = new Stopwatch(); @@ -1277,7 +1279,7 @@ private void RefreshInstanceDetails(IOrganizationService dvService, Uri uriOfIns if (resp.Detail != null) { _OrgDetail = new OrganizationDetail(); - //Add Endpoints. + //Add Endpoints. foreach (var ep in resp.Detail.Endpoints) { string endPointName = ep.Key.ToString(); @@ -1309,7 +1311,7 @@ private void RefreshInstanceDetails(IOrganizationService dvService, Uri uriOfIns ConnectedOrgFriendlyName = _OrgDetail.FriendlyName; ConnectedOrgPublishedEndpoints = _OrgDetail.Endpoints; - // try to create a version number from the org. + // try to create a version number from the org. OrganizationVersion = new Version("0.0.0.0"); try { @@ -1325,7 +1327,7 @@ private void RefreshInstanceDetails(IOrganizationService dvService, Uri uriOfIns } /// - /// Get current user info. + /// Get current user info. /// /// /// @@ -1341,12 +1343,12 @@ private void RefreshInstanceDetails(IOrganizationService dvService, Uri uriOfIns trackingID = Guid.NewGuid(); WhoAmIRequest req = new WhoAmIRequest(); - if (trackingID != Guid.Empty) // Add Tracking number of present. + if (trackingID != Guid.Empty) // Add Tracking number of present. req.RequestId = trackingID; var resp = (WhoAmIResponse)dvService.Execute(req); - // Left in information mode intentionaly. + // Left in information mode intentionaly. logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Executed Command - WhoAmIRequest : RequestId={1} : total duration: {0}", dtQueryTimer.Elapsed.ToString(), trackingID.ToString())); return resp; } @@ -1369,12 +1371,12 @@ private void RefreshInstanceDetails(IOrganizationService dvService, Uri uriOfIns } /// - /// Sets Properties on the cloned instance. + /// Sets Properties on the cloned instance. /// /// Source instance to clone from internal void SetClonedProperties(ServiceClient sourceClient) { - // Sets the cloned properties from the caller. + // Sets the cloned properties from the caller. user = sourceClient.SystemUser; OrganizationVersion = sourceClient._connectionSvc.OrganizationVersion; ConnectedOrgPublishedEndpoints = sourceClient.ConnectedOrgPublishedEndpoints; @@ -1392,13 +1394,13 @@ internal void SetClonedProperties(ServiceClient sourceClient) _certificateThumbprint = sourceClient._connectionSvc._certificateThumbprint; _certificateOfConnection = sourceClient._connectionSvc._certificateOfConnection; _redirectUri = sourceClient._connectionSvc._redirectUri; - _resource = sourceClient._connectionSvc._resource; + _resource = sourceClient._connectionSvc._resource; } #region WebAPI Interface Utilities /// - /// Makes a call to a web API to support request to XRM. + /// Makes a call to a web API to support request to XRM. /// /// URI of request target /// method being used @@ -1447,7 +1449,7 @@ internal void SetClonedProperties(ServiceClient sourceClient) _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); } - // Add User Agent and request id to send. + // Add User Agent and request id to send. string Agent = "Unknown"; if (AppDomain.CurrentDomain != null) { @@ -1476,7 +1478,7 @@ internal void SetClonedProperties(ServiceClient sourceClient) if (!string.IsNullOrEmpty(contentType)) { contentPost = new StringContent(body); - if (contentPost.Headers.Contains(Utilities.RequestHeaders.CONTENT_TYPE)) // Remove the default content type if its there. + if (contentPost.Headers.Contains(Utilities.RequestHeaders.CONTENT_TYPE)) // Remove the default content type if its there. contentPost.Headers.Remove(Utilities.RequestHeaders.CONTENT_TYPE); contentPost.Headers.TryAddWithoutValidation(Utilities.RequestHeaders.CONTENT_TYPE, contentType); // Replace with added content type } @@ -1500,7 +1502,7 @@ internal void SetClonedProperties(ServiceClient sourceClient) } else { - // Fall though logic to deal with an Http client not being passed in. + // Fall though logic to deal with an Http client not being passed in. using (HttpClient httpCli = new HttpClient()) { logDt.Restart(); @@ -1556,7 +1558,7 @@ internal void SetClonedProperties(ServiceClient sourceClient) #region Service utilities. // /// - // /// Find authority and resources + // /// Find authority and resources // /// // /// Service Uri endpoint // /// Resource to connect to @@ -1569,7 +1571,7 @@ internal void SetClonedProperties(ServiceClient sourceClient) // UriBuilder versionTaggedUriBuilder = GetUriBuilderWithVersion(discoveryServiceUri); - // //discoveryServiceProxy + // //discoveryServiceProxy // svcDiscoveryProxy = new DiscoveryWebProxyClient(versionTaggedUriBuilder.Uri); // svcWebClientProxy = new OrganizationWebProxyClient(versionTaggedUriBuilder.Uri, true); @@ -1614,18 +1616,18 @@ private static UriBuilder GetUriBuilderWithVersion(Uri discoveryServiceUri) return versionTaggedUriBuilder; } - + /// /// Obtaining authentication context /// private static AuthenticationContext ObtainAuthenticationContext(string Authority, bool requireValidation, string tokenCachePath) { - // Do not need to dispose this here as its added ot the authentication context, its cleaned up with the authentication context later. + // Do not need to dispose this here as its added ot the authentication context, its cleaned up with the authentication context later. CdsServiceClientTokenCache tokenCache = new CdsServiceClientTokenCache(tokenCachePath); #if DEBUG - // When in debug mode.. Always disable Authority validation to support NOVA builds. + // When in debug mode.. Always disable Authority validation to support NOVA builds. requireValidation = false; #endif @@ -1642,7 +1644,7 @@ private static AuthenticationContext ObtainAuthenticationContext(string Authorit return authenticationContext; } -#if (NET462 || NET472 || NET48) +#if (NET462 || NET472 || NET48) /// /// Obtain access token for regular popup based authentication /// @@ -1665,7 +1667,7 @@ private static AuthenticationResult ObtainAccessToken(AuthenticationContext auth } #endif -#if (NET462 || NET472 || NET48) +#if (NET462 || NET472 || NET48) /// /// Obtain access token for silent login /// @@ -1698,9 +1700,9 @@ private static AuthenticationResult ObtainAccessToken(AuthenticationContext auth return _authenticationResult; } -#if (NET462 || NET472 || NET48) +#if (NET462 || NET472 || NET48) /// - /// Obtain access token for ClientSecret Based Login. + /// Obtain access token for ClientSecret Based Login. /// /// Authentication Context to be used for connection /// Resource endpoint to connect @@ -1716,7 +1718,7 @@ private static AuthenticationResult ObtainAccessToken(AuthenticationContext auth } #else /// - /// Obtain access token for ClientSecret Based Login. + /// Obtain access token for ClientSecret Based Login. /// /// Authentication Context to be used for connection /// Resource endpoint to connect @@ -1733,7 +1735,7 @@ private static AuthenticationResult ObtainAccessToken(AuthenticationContext auth #endif /// - /// Trues to get the current users login token for the target resource. + /// Trues to get the current users login token for the target resource. /// /// Authentication Context to be used for connection /// Resource endpoint to connect @@ -1766,13 +1768,14 @@ private static AuthenticationResult ObtainAccessTokenCurrentUser(AuthenticationC /// (optional) Initialized CdsTraceLogger Object /// Use the global disco path. /// (optional) If true attempts login using current user + /// Logging provider /// The list of organizations discovered. - internal static async Task DiscoverOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, string clientId, Uri redirectUri, PromptBehavior promptBehavior, bool isOnPrem, string authority, DataverseTraceLogger logSink = null, bool useGlobalDisco = false, bool useDefaultCreds = false) + internal static async Task DiscoverOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, string clientId, Uri redirectUri, PromptBehavior promptBehavior, bool isOnPrem, string authority, DataverseTraceLogger logSink = null, bool useGlobalDisco = false, bool useDefaultCreds = false , ILogger externalLogger = null) { bool isLogEntryCreatedLocaly = false; if (logSink == null) { - logSink = new DataverseTraceLogger(); + logSink = new DataverseTraceLogger(externalLogger); isLogEntryCreatedLocaly = true; } @@ -1832,21 +1835,22 @@ internal static async Task DiscoverOrganizationsAs /// GD URI /// Pointer to the token provider handler /// Logging endpoint (optional) + /// Logging provider /// Populated OrganizationDetailCollection or Null. - internal static async Task DiscoverGlobalOrganizationsAsync(Uri discoveryServiceUri, Func> tokenProviderFunction , DataverseTraceLogger logSink = null) + internal static async Task DiscoverGlobalOrganizationsAsync(Uri discoveryServiceUri, Func> tokenProviderFunction , DataverseTraceLogger logSink = null, ILogger externalLogger = null) { bool isLogEntryCreatedLocaly = false; if (logSink == null) { - logSink = new DataverseTraceLogger(); + logSink = new DataverseTraceLogger(externalLogger); isLogEntryCreatedLocaly = true; } - // if the discovery URL does not contain api/discovery , base it and use it in the commercial format base. - // Check must be here as well to deal with remote auth. + // if the discovery URL does not contain api/discovery , base it and use it in the commercial format base. + // Check must be here as well to deal with remote auth. if (!(discoveryServiceUri.Segments.Contains("api") && discoveryServiceUri.Segments.Contains("discovery"))) { - // do not have the full API URL here. + // do not have the full API URL here. discoveryServiceUri = new Uri(string.Format(_baselineGlobalDiscoveryFormater, discoveryServiceUri.DnsSafeHost, _globlaDiscoVersion, "Instances")); } @@ -1854,7 +1858,7 @@ internal static async Task DiscoverGlobalOrganizat { logSink.Log("DiscoverOrganizations - : " + discoveryServiceUri.ToString()); string AuthToken = await tokenProviderFunction(discoveryServiceUri.ToString()); - return await QueryGlobalDiscoveryAsync(AuthToken, discoveryServiceUri, logSink); + return await QueryGlobalDiscoveryAsync(AuthToken, discoveryServiceUri, logSink); } finally { @@ -1885,7 +1889,7 @@ private static async Task DiscoverOrganizations_In { if (logSink == null) { - // when set, the log source is locally created. + // when set, the log source is locally created. createdLogSource = true; logSink = new DataverseTraceLogger(); } @@ -1945,7 +1949,7 @@ private static async Task DiscoverOrganizations_In //if (authContext != null && authContext.TokenCache is CdsServiceClientTokenCache) // ((CdsServiceClientTokenCache)authContext.TokenCache).Dispose(); - if (createdLogSource) // Only dispose it if it was created locally. + if (createdLogSource) // Only dispose it if it was created locally. logSink.Dispose(); } } @@ -1972,7 +1976,7 @@ private static async Task DiscoverGlobalOrganizati { if (logSink == null) { - // when set, the log source is locally created. + // when set, the log source is locally created. createdLogSource = true; logSink = new DataverseTraceLogger(); } @@ -1980,11 +1984,11 @@ private static async Task DiscoverGlobalOrganizati if (discoveryServiceUri == null) throw new ArgumentNullException("discoveryServiceUri", "Discovery service uri cannot be null."); - // if the discovery URL does not contain api/discovery , base it and use it in the commercial format base. - // Check needs to be in 2 places as there are 2 different ways Auth can occur. + // if the discovery URL does not contain api/discovery , base it and use it in the commercial format base. + // Check needs to be in 2 places as there are 2 different ways Auth can occur. if(!(discoveryServiceUri.Segments.Contains("api") && discoveryServiceUri.Segments.Contains("discovery"))) { - // do not have the full API URL here. + // do not have the full API URL here. discoveryServiceUri = new Uri(string.Format(_baselineGlobalDiscoveryFormater, discoveryServiceUri.DnsSafeHost, _globlaDiscoVersion, "Instances")); } @@ -1997,12 +2001,12 @@ private static async Task DiscoverGlobalOrganizati string authToken = string.Empty; string resource = string.Empty; // not used here.. - // Develop authority here. + // Develop authority here. // Form challenge for global disco Uri authChallengeUri = new Uri($"{discoveryServiceUri.Scheme}://{discoveryServiceUri.DnsSafeHost}/api/aad/challenge"); // Execute Authentication Request and return token And ServiceURI - //Uri targetResourceRequest = new Uri(string.Format("{0}://{1}/api/discovery/", discoveryServiceUri.Scheme , discoveryServiceUri.DnsSafeHost)); + //Uri targetResourceRequest = new Uri(string.Format("{0}://{1}/api/discovery/", discoveryServiceUri.Scheme , discoveryServiceUri.DnsSafeHost)); IAccount user = null; object msalAuthClientOut = null; AuthenticationResult authenticationResult = null; @@ -2010,7 +2014,7 @@ private static async Task DiscoverGlobalOrganizati authToken = authRequestResult.GetAuthTokenAndProperties(out authenticationResult, out targetServiceUrl, out msalAuthClientOut, out authority, out resource, out user); - // Get the GD Info and return. + // Get the GD Info and return. return await QueryGlobalDiscoveryAsync(authToken, discoveryServiceUri, logSink); } @@ -2020,13 +2024,13 @@ private static async Task DiscoverGlobalOrganizati //if (authContext != null && authContext.TokenCache is CdsServiceClientTokenCache) // ((CdsServiceClientTokenCache)authContext.TokenCache).Dispose(); - if (createdLogSource) // Only dispose it if it was created localy. + if (createdLogSource) // Only dispose it if it was created localy. logSink.Dispose(); } } /// - /// Queries the global discovery service + /// Queries the global discovery service /// /// /// @@ -2038,7 +2042,7 @@ private static async Task QueryGlobalDiscoveryAsyn if (logSink == null) { - // when set, the log source is locally created. + // when set, the log source is locally created. createdLogSource = true; logSink = new DataverseTraceLogger(); } @@ -2059,14 +2063,14 @@ private static async Task QueryGlobalDiscoveryAsyn var a = await ExecuteHttpRequestAsync(discoveryServiceUri.ToString(), HttpMethod.Get, customHeaders: headers, logSink: logSink).ConfigureAwait(false); string body = await a.Content.ReadAsStringAsync(); - // Parse the out put into a discovery request. + // Parse the out put into a discovery request. var b = JsonConvert.DeserializeObject(body); OrganizationDetailCollection orgList = new OrganizationDetailCollection(); foreach (var inst in b.Instances) { Version orgVersion = new Version("8.0"); - Version.TryParse(inst.Version, out orgVersion); // try parsing the version out. + Version.TryParse(inst.Version, out orgVersion); // try parsing the version out. EndpointCollection ep = new EndpointCollection(); ep.Add(EndpointType.WebApplication, inst.Url); @@ -2080,7 +2084,7 @@ private static async Task QueryGlobalDiscoveryAsyn d.State = (OrganizationState)Enum.Parse(typeof(OrganizationState), inst.State.ToString()); d.UniqueName = inst.UniqueName; d.UrlName = inst.UrlName; - d.EnvironmentId = !string.IsNullOrEmpty(inst.EnvironmentId) ? inst.EnvironmentId : string.Empty; + d.EnvironmentId = !string.IsNullOrEmpty(inst.EnvironmentId) ? inst.EnvironmentId : string.Empty; d.Geo = !string.IsNullOrEmpty(inst.Region) ? inst.Region : string.Empty; d.TenantId = !string.IsNullOrEmpty(inst.TenantId) ? inst.TenantId : string.Empty; System.Reflection.PropertyInfo proInfo = d.GetType().GetProperty("Endpoints"); @@ -2107,7 +2111,7 @@ private static async Task QueryGlobalDiscoveryAsyn { if (dtStartQuery.IsRunning) dtStartQuery.Stop(); - if (createdLogSource) // Only dispose it if it was created locally. + if (createdLogSource) // Only dispose it if it was created locally. logSink.Dispose(); } } @@ -2159,7 +2163,7 @@ private static ClientCredentials GetClientCredentials(NetworkCredential networkC private static X509Certificate2 FindCertificate(string certificateThumbprint, StoreName storeName, DataverseTraceLogger logSink) { logSink.Log(string.Format("Looking for certificate with thumbprint: {0}..", certificateThumbprint)); - // Look in both current user and local machine. + // Look in both current user and local machine. var storeLocations = new[] { StoreLocation.CurrentUser, StoreLocation.LocalMachine }; try { @@ -2180,7 +2184,7 @@ private static X509Certificate2 FindCertificate(string certificateThumbprint, St } /// - /// Used to locate the certificate in the store and return a collection of certificates that match the thumbprint. + /// Used to locate the certificate in the store and return a collection of certificates that match the thumbprint. /// /// Thumbprint to search for /// Where to search for on the machine @@ -2200,7 +2204,7 @@ private static bool TryFindCertificatesInStore(string certificateThumbprint, Sto /// - /// Connects too and initializes the Dataverse org Data service. + /// Connects too and initializes the Dataverse org Data service. /// /// Organization Data /// True if called from the OnPrem Branch @@ -2208,7 +2212,7 @@ private static bool TryFindCertificatesInStore(string certificateThumbprint, Sto [SuppressMessage("Microsoft.Usage", "CA9888:DisposeObjectsCorrectly", MessageId = "proxy")] private async Task ConnectAndInitServiceAsync(OrganizationDetail orgdata, bool IsOnPrem, Uri homeRealmUri) { - //_ActualOrgDetailUsed = orgdata; + //_ActualOrgDetailUsed = orgdata; _ActualDataverseOrgUri = BuildOrgConnectUri(orgdata); logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Organization Service URI is = {0}", _ActualDataverseOrgUri.ToString()), TraceEventType.Information); @@ -2221,35 +2225,35 @@ private async Task ConnectAndInitServiceAsync(Organization logDt.Start(); // Build User Credential logEntry.Log("ConnectAndInitService - Initializing Organization Service Object", TraceEventType.Verbose); - // this to provide trouble shooting information when determining org connect failures. + // this to provide trouble shooting information when determining org connect failures. logEntry.Log(string.Format(CultureInfo.InvariantCulture, "ConnectAndInitService - Requesting connection to Organization with Dataverse Version: {0}", orgdata.OrganizationVersion == null ? "No organization data available" : orgdata.OrganizationVersion), TraceEventType.Information); - // try to create a version number from the org. + // try to create a version number from the org. OrganizationVersion = null; try { - Version tempVer = null; - if ( Version.TryParse(orgdata.OrganizationVersion, out tempVer)) + Version tempVer = null; + if ( Version.TryParse(orgdata.OrganizationVersion, out tempVer)) OrganizationVersion = tempVer; } catch { }; OrganizationWebProxyClient svcWebClientProxy = null; - if (_eAuthType == AuthenticationType.OAuth - || _eAuthType == AuthenticationType.Certificate - || _eAuthType == AuthenticationType.ExternalTokenManagement + if (_eAuthType == AuthenticationType.OAuth + || _eAuthType == AuthenticationType.Certificate + || _eAuthType == AuthenticationType.ExternalTokenManagement || _eAuthType == AuthenticationType.ClientSecret) { string resource = string.Empty; string Authority = string.Empty; Uri targetServiceUrl = null; - + string authToken = string.Empty; if (_eAuthType == AuthenticationType.ExternalTokenManagement) { - // Call External hook here. + // Call External hook here. try { targetServiceUrl = targetServiceUrl = AuthProcessor.GetUriBuilderWithVersion(_ActualDataverseOrgUri).Uri; @@ -2305,14 +2309,14 @@ internal void AttachWebProxyHander (OrganizationWebProxyClient proxy ) /// - /// Grab the Channel factory Open event and add the CrmHook Service behaviors. + /// Grab the Channel factory Open event and add the CrmHook Service behaviors. /// /// incoming ChannelFactory /// ignored private void WebProxyChannelFactory_Opening(object sender, EventArgs e) { - // Add Connection header support for Organization Web client. + // Add Connection header support for Organization Web client. ChannelFactory fact = sender as ChannelFactory; if (fact != null) { @@ -2349,7 +2353,7 @@ byte[] encodedDataAsBytes /// /// Builds the Organization Service Connect URI - /// - This is done, potentially replacing the original string, to deal with the discovery service returning an unusable string, for example, a DNS name that does not resolve. + /// - This is done, potentially replacing the original string, to deal with the discovery service returning an unusable string, for example, a DNS name that does not resolve. /// /// Org Data found from the Discovery Service. /// CRM Connection URI @@ -2358,7 +2362,7 @@ private Uri BuildOrgConnectUri(OrganizationDetail orgdata) logEntry.Log("BuildOrgConnectUri CoreClass ()", TraceEventType.Start); - // Build connection URL + // Build connection URL string CrmUrl = string.Empty; Uri OrgEndPoint = new Uri(orgdata.Endpoints[EndpointType.OrganizationService]); @@ -2371,19 +2375,19 @@ private Uri BuildOrgConnectUri(OrganizationDetail orgdata) #endif if (Utilities.IsValidOnlineHost(OrgEndPoint)) { - // CRM Online ..> USE PROVIDED URI. + // CRM Online ..> USE PROVIDED URI. logEntry.Log("BuildOrgConnectUri CoreClass ()", TraceEventType.Stop); return OrgEndPoint; } else { // A workaround added in this case to Check for _hostname to be null or empty if it's empty by constructor definitions they are online deployment type - //And OAuth supports both online and onprem deployment so incase of online Oauth hostname will be empty and orgEndpoint has to be retrun ideally case + //And OAuth supports both online and onprem deployment so incase of online Oauth hostname will be empty and orgEndpoint has to be retrun ideally case // is to test both AuthType and Deployment type current code doesn't support that hence the workaround. if (String.IsNullOrEmpty(_hostname)) { logEntry.Log("BuildOrgConnectUri CoreClass ()", TraceEventType.Stop); - return OrgEndPoint; // O365 returns direct org end point. + return OrgEndPoint; // O365 returns direct org end point. } else { @@ -2415,7 +2419,7 @@ private Uri BuildOrgConnectUri(OrganizationDetail orgdata) } /// - /// Iterates through the list of Dataverse Discovery Servers to find one that knows the user. + /// Iterates through the list of Dataverse Discovery Servers to find one that knows the user. /// /// private async Task FindDiscoveryServerAsync(DiscoveryServers onlineServerList) @@ -2425,11 +2429,11 @@ private async Task FindDiscoveryServerAsync(DiscoveryServers onlineServ if (_OrgDetail == null) { - // If the user as Specified a server to use, try to get the org from that server. + // If the user as Specified a server to use, try to get the org from that server. if (!string.IsNullOrWhiteSpace(_DataverseOnlineRegion)) { logEntry.Log("Using User Specified Server ", TraceEventType.Information); - // Server Specified... + // Server Specified... DiscoveryServer svr = onlineServerList.GetServerByShortName(_DataverseOnlineRegion); if (svr != null) { @@ -2438,13 +2442,13 @@ private async Task FindDiscoveryServerAsync(DiscoveryServers onlineServ if (svr.RegionalGlobalDiscoveryServer == null) { logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Trying Discovery Server, ({1}) URI is = {0}", svr.DiscoveryServerUri.ToString(), svr.DisplayName), TraceEventType.Information); - col = await QueryLiveDiscoveryServerAsync(svr.DiscoveryServerUri); // Defaults to not using GD. + col = await QueryLiveDiscoveryServerAsync(svr.DiscoveryServerUri); // Defaults to not using GD. } else { logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Trying Regional Global Discovery Server, ({1}) URI is = {0}", svr.RegionalGlobalDiscoveryServer.ToString(), svr.DisplayName), TraceEventType.Information); await QueryOnlineServersListAsync(onlineServerList.OSDPServers, col, orgsList, svr.DiscoveryServerUri, svr.RegionalGlobalDiscoveryServer); - //col = QueryLiveDiscoveryServer(svr.DiscoveryServer); // Defaults to not using GD. + //col = QueryLiveDiscoveryServer(svr.DiscoveryServer); // Defaults to not using GD. return orgsList; } } @@ -2452,7 +2456,7 @@ private async Task FindDiscoveryServerAsync(DiscoveryServers onlineServ { if (_eAuthType == AuthenticationType.OAuth) { - // OAuth, and GD is allowed. + // OAuth, and GD is allowed. logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Trying Global Discovery Server ({0}) and filtering to region {1}", GlobalDiscoveryAllInstancesUri, _DataverseOnlineRegion), TraceEventType.Information); await QueryOnlineServersListAsync(onlineServerList.OSDPServers, col, orgsList, svr.DiscoveryServerUri); return orgsList; @@ -2473,7 +2477,7 @@ private async Task FindDiscoveryServerAsync(DiscoveryServers onlineServ // Server is unspecified or the user chose ‘don’t know’ if (_eAuthType == AuthenticationType.OAuth) { - // use GD. + // use GD. col = await QueryLiveDiscoveryServerAsync(new Uri(GlobalDiscoveryAllInstancesUri), true); if (col != null) { @@ -2502,7 +2506,7 @@ private async Task FindDiscoveryServerAsync(DiscoveryServers onlineServ } /// - /// Iterate over the discovery servers available. + /// Iterate over the discovery servers available. /// /// /// @@ -2534,7 +2538,7 @@ private async Task QueryOnlineServersListAsync(ObservableCollection QueryOnlineServersListAsync(ObservableCollection QueryLiveDiscoveryServerAsync(U return await DiscoverOrganizationsAsync(discoServer, _certificateOfConnection, _clientId, false, _authority, logEntry); } - return null; - + return null; + } } catch (SecurityAccessDeniedException) { - // User Does not have any orgs on this server. + // User Does not have any orgs on this server. return null; } } @@ -2634,7 +2638,7 @@ private void AddOrgToOrgList(OrganizationDetailCollection organizationDetailList /// Adds an Org to the List of Orgs /// /// - /// + /// /// private void AddOrgToOrgList(OrganizationDetail organizationDetail, string discoveryServer, ref OrgList orgList) { @@ -2658,7 +2662,7 @@ internal async Task RefreshWebProxyClientTokenAsync() { if (_MsalAuthClient is IPublicClientApplication pClient) { - // this is a user based application. + // this is a user based application. if (_isCalledbyExecuteRequest && _promptBehavior != PromptBehavior.Never) { _isCalledbyExecuteRequest = false; @@ -2681,14 +2685,14 @@ internal async Task RefreshWebProxyClientTokenAsync() if (_eAuthType == AuthenticationType.ExternalTokenManagement) { - // Call External hook here. + // Call External hook here. try { if (GetAccessTokenAsync != null) _svcWebClientProxy.HeaderToken = await GetAccessTokenAsync(_ActualDataverseOrgUri.ToString()); else throw new Exception("External Authentication Requested but not configured correctly. Faulted In Request Access Token 004"); - + } catch (Exception ex) { @@ -2699,17 +2703,17 @@ internal async Task RefreshWebProxyClientTokenAsync() if (_svcWebClientProxy != null) return _svcWebClientProxy.HeaderToken; else - return string.Empty; // this can happen when running via tests + return string.Empty; // this can happen when running via tests } #region IDisposable Support /// - /// Reset disposed state to handle this object being pulled from cache. + /// Reset disposed state to handle this object being pulled from cache. /// private void ResetDisposedState() { - // reset the disposed state to deal with the object being pulled from cache. + // reset the disposed state to deal with the object being pulled from cache. disposedValue = false; } private bool disposedValue = false; // To detect redundant calls @@ -2725,8 +2729,8 @@ void Dispose(bool disposing) if (logEntry != null) logEntry.Dispose(); } - - //TODO: REMOVE ONCE MEM TEST COMPELTES CLEAN. + + //TODO: REMOVE ONCE MEM TEST COMPELTES CLEAN. //if (_authenticationContext != null && _authenticationContext.TokenCache != null) //{ // if (_authenticationContext.TokenCache is CdsServiceClientTokenCache) @@ -2737,7 +2741,7 @@ void Dispose(bool disposing) if (unqueInstance) { - // Clean the connect out of memory. + // Clean the connect out of memory. System.Runtime.Caching.MemoryCache.Default.Remove(_ServiceCACHEName); } @@ -2753,7 +2757,7 @@ void Dispose(bool disposing) } } } - catch { }; // Failed to dispose.. no way to notifiy this right now.. let it go . + catch { }; // Failed to dispose.. no way to notifiy this right now.. let it go . } disposedValue = true; diff --git a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs index 992e9a0..9010e4a 100644 --- a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs +++ b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs @@ -29,11 +29,12 @@ using Microsoft.PowerPlatform.Dataverse.Client; using System.Threading; using System.Dynamic; +using Microsoft.Extensions.Logging; namespace Microsoft.PowerPlatform.Dataverse.Client { /// - /// Primary implementation of the API interface for Dataverse. + /// Primary implementation of the API interface for Dataverse. /// public sealed class ServiceClient : IOrganizationService, IOrganizationServiceAsync, IDisposable { @@ -41,22 +42,22 @@ public sealed class ServiceClient : IOrganizationService, IOrganizationServiceAs /// - /// Cached Object collection, used for pick lists and such. + /// Cached Object collection, used for pick lists and such. /// - private Dictionary> _CachObject; //Cache object. + private Dictionary> _CachObject; //Cache object. /// - /// List of Dataverse Language ID's + /// List of Dataverse Language ID's /// private List _loadedLCIDList; /// - /// Name of the cache object. + /// Name of the cache object. /// private string _cachObjecName = ".LookupCache"; /// - /// Logging object for the Dataverse Interface. + /// Logging object for the Dataverse Interface. /// internal DataverseTraceLogger _logEntry; @@ -76,12 +77,12 @@ public sealed class ServiceClient : IOrganizationService, IOrganizationServiceAs private MetadataUtility _metadataUtlity = null; /// - /// This is an internal Lock object, used to sync communication with Dataverse. + /// This is an internal Lock object, used to sync communication with Dataverse. /// internal object _lockObject = new object(); /// - /// BatchManager for Execute Multiple. + /// BatchManager for Execute Multiple. /// private BatchManager _batchManager = null; @@ -93,7 +94,7 @@ public sealed class ServiceClient : IOrganizationService, IOrganizationServiceAs private bool _disableConnectionLocking = false; /// - /// SDK Version property backer. + /// SDK Version property backer. /// public string _sdkVersionProperty = null; @@ -103,13 +104,13 @@ public sealed class ServiceClient : IOrganizationService, IOrganizationServiceAs private int _maxRetryCount = Utils.AppSettingsHelper.GetAppSetting("ApiOperationRetryCountOverride", 10); /// - /// Amount of time to wait between retries + /// Amount of time to wait between retries /// private TimeSpan _retryPauseTime = Utils.AppSettingsHelper.GetAppSetting("ApiOperationRetryDelayOverride", new TimeSpan(0, 0, 0, 5)); /// - /// Value used by the retry system while the code is running, - /// this value can scale up and down based on throttling limits. + /// Value used by the retry system while the code is running, + /// this value can scale up and down based on throttling limits. /// private TimeSpan _retryPauseTimeRunning; @@ -171,7 +172,7 @@ internal OrganizationWebProxyClient OrganizationWebProxyClient /// /// This is the number of minuets that logs will be retained before being purged from memory. Default is 5 min. - /// This capability controls how long the log cache is kept in memory. + /// This capability controls how long the log cache is kept in memory. /// public static TimeSpan InMemoryLogCollectionTimeOutMinutes { get; set; } = Utils.AppSettingsHelper.GetAppSettingTimeSpan("InMemoryLogCollectionTimeOutMinutes", Utils.AppSettingsHelper.TimeSpanFromKey.Minutes, TimeSpan.FromMinutes(5)); @@ -194,12 +195,12 @@ public TimeSpan RetryPauseTime } /// - /// if true the service is ready to accept requests. + /// if true the service is ready to accept requests. /// public bool IsReady { get; private set; } /// - /// if true then Batch Operations are available. + /// if true then Batch Operations are available. /// public bool IsBatchOperationsAvailable { @@ -245,8 +246,8 @@ public string OAuthUserId } /// - /// Gets or Sets the Max Connection Timeout for the connection. - /// Default setting is 2 min, + /// Gets or Sets the Max Connection Timeout for the connection. + /// Default setting is 2 min, /// this property can also be set via app.config/app.settings with the property MaxConnectionTimeOutMinutes /// public static TimeSpan MaxConnectionTimeout @@ -262,7 +263,7 @@ public static TimeSpan MaxConnectionTimeout } /// - /// Authentication Type to use + /// Authentication Type to use /// public AuthenticationType ActiveAuthenticationType { @@ -276,8 +277,8 @@ public AuthenticationType ActiveAuthenticationType } /// - /// Returns the current access token in Use to connect to Dataverse. - /// Note: this is only available when a token based authentication process is in use. + /// Returns the current access token in Use to connect to Dataverse. + /// Note: this is only available when a token based authentication process is in use. /// public string CurrentAccessToken { @@ -297,13 +298,13 @@ public string CurrentAccessToken } /// - /// Pointer to Dataverse Service. + /// Pointer to Dataverse Service. /// internal IOrganizationService DataverseService { get { - // Added to support testing of ServiceClient direct code. + // Added to support testing of ServiceClient direct code. if (_testOrgSvcInterface != null) return _testOrgSvcInterface; @@ -348,27 +349,27 @@ internal WhoAmIResponse SystemUser public string LastError { get { if (_logEntry != null) return _logEntry.LastError; else return string.Empty; } } /// - /// Returns the Last Exception from Dataverse. + /// Returns the Last Exception from Dataverse. /// public Exception LastException { get { if (_logEntry != null) return _logEntry.LastException; else return null; } } /// - /// Returns the Actual URI used to connect to Dataverse. - /// this URI could be influenced by user defined variables. + /// Returns the Actual URI used to connect to Dataverse. + /// this URI could be influenced by user defined variables. /// public Uri ConnectedOrgUriActual { get { if (_connectionSvc != null) return _connectionSvc.ConnectOrgUriActual; else return null; } } /// - /// Returns the friendly name of the connected Dataverse instance. + /// Returns the friendly name of the connected Dataverse instance. /// public string ConnectedOrgFriendlyName { get { if (_connectionSvc != null) return _connectionSvc.ConnectedOrgFriendlyName; else return null; } } /// - /// - /// Returns the unique name for the org that has been connected. + /// + /// Returns the unique name for the org that has been connected. /// public string ConnectedOrgUniqueName { get { if (_connectionSvc != null) return _connectionSvc.CustomerOrganization; else return null; } } /// - /// Returns the endpoint collection for the connected org. + /// Returns the endpoint collection for the connected org. /// public EndpointCollection ConnectedOrgPublishedEndpoints { get { if (_connectionSvc != null) return _connectionSvc.ConnectedOrgPublishedEndpoints; else return null; } } @@ -378,29 +379,29 @@ internal WhoAmIResponse SystemUser public OrganizationDetail OrganizationDetail { get { if (_connectionSvc != null) return _connectionSvc.ConnectedOrganizationDetail; else return null; } } /// - /// This is the connection lock object that is used to control connection access for various threads. This should be used if you are using the Datavers queries via Linq to lock the connection + /// This is the connection lock object that is used to control connection access for various threads. This should be used if you are using the Datavers queries via Linq to lock the connection /// internal object ConnectionLockObject { get { return _lockObject; } } /// - /// Returns the Version Number of the connected Dataverse organization. - /// If access before the Organization is connected, value returned will be null or 0.0 + /// Returns the Version Number of the connected Dataverse organization. + /// If access before the Organization is connected, value returned will be null or 0.0 /// public Version ConnectedOrgVersion { get { if (_connectionSvc != null) return _connectionSvc?.OrganizationVersion; else return new Version(0, 0); } } /// - /// ID of the connected organization. + /// ID of the connected organization. /// public Guid ConnectedOrgId { get { if (_connectionSvc != null) return _connectionSvc.OrganizationId; else return Guid.Empty; } } /// - /// Disabled internal cross thread safeties, this will gain much higher performance, however it places the requirements of thread safety on you, the developer. + /// Disabled internal cross thread safeties, this will gain much higher performance, however it places the requirements of thread safety on you, the developer. /// public bool DisableCrossThreadSafeties { get { return _disableConnectionLocking; } set { _disableConnectionLocking = value; } } /// /// Returns the access token from the attached function. - /// This is set via the ServiceContructor that accepts a target url and a function to return an access token. + /// This is set via the ServiceContructor that accepts a target url and a function to return an access token. /// internal Func> GetAccessToken { get; set; } @@ -423,7 +424,7 @@ public Guid CallerId } /// - /// Gets or Sets the AAD Object ID of the caller. + /// Gets or Sets the AAD Object ID of the caller. /// This is supported for Xrm 8.1 + only /// public Guid? CallerAADObjectId @@ -444,7 +445,7 @@ public Guid? CallerAADObjectId { if (_connectionSvc?.OrganizationVersion != null) { - _connectionSvc.CallerAADObjectId = null; // Null value as this is not supported for this version. + _connectionSvc.CallerAADObjectId = null; // Null value as this is not supported for this version. _logEntry.Log($"Setting CallerAADObject ID not supported in version {_connectionSvc?.OrganizationVersion}"); } } @@ -453,9 +454,9 @@ public Guid? CallerAADObjectId /// /// This ID is used to support Dataverse Telemetry when trouble shooting SDK based errors. - /// When Set by the caller, all Dataverse API Actions executed by this client will be tracked under a single session id for later troubleshooting. - /// For example, you are able to group all actions in a given run of your client ( several creates / reads and such ) under a given tracking id that is shared on all requests. - /// providing this ID when reporting a problem will aid in trouble shooting your issue. + /// When Set by the caller, all Dataverse API Actions executed by this client will be tracked under a single session id for later troubleshooting. + /// For example, you are able to group all actions in a given run of your client ( several creates / reads and such ) under a given tracking id that is shared on all requests. + /// providing this ID when reporting a problem will aid in trouble shooting your issue. /// public Guid? SessionTrackingId { @@ -476,7 +477,7 @@ public Guid? SessionTrackingId { if (_connectionSvc?.OrganizationVersion != null) { - _connectionSvc.SessionTrackingId = null; // Null value as this is not supported for this version. + _connectionSvc.SessionTrackingId = null; // Null value as this is not supported for this version. _logEntry.Log($"Setting SessionTrackingId ID not supported in version {_connectionSvc?.OrganizationVersion}"); } } @@ -486,9 +487,9 @@ public Guid? SessionTrackingId /// /// This will force the Dataverse server to refresh the current metadata cache with current DB config. - /// Note, that this is a performance impacting property. - /// Use of this flag will slow down operations server side as the server is required to check for consistency of the platform metadata against disk on each API call executed. - /// It is recommended to use this ONLY in conjunction with solution import or delete operations. + /// Note, that this is a performance impacting property. + /// Use of this flag will slow down operations server side as the server is required to check for consistency of the platform metadata against disk on each API call executed. + /// It is recommended to use this ONLY in conjunction with solution import or delete operations. /// public bool ForceServerMetadataCacheConsistency { @@ -508,7 +509,7 @@ public bool ForceServerMetadataCacheConsistency { if (_connectionSvc?.OrganizationVersion != null) { - _connectionSvc.ForceServerCacheConsistency = false; // Null value as this is not supported for this version. + _connectionSvc.ForceServerCacheConsistency = false; // Null value as this is not supported for this version. _logEntry.Log($"Setting ForceServerMetadataCacheConsistency not supported in version {_connectionSvc?.OrganizationVersion}"); } } @@ -517,7 +518,7 @@ public bool ForceServerMetadataCacheConsistency } /// - /// Get the Client SDK version property + /// Get the Client SDK version property /// public string SdkVersionProperty { @@ -532,7 +533,7 @@ public string SdkVersionProperty } /// - /// Gets the Tenant Id of the current connection. + /// Gets the Tenant Id of the current connection. /// public Guid TenantId { @@ -568,28 +569,29 @@ public string EnvironmentId #region Constructor and Setup methods /// - /// Default / Non accessible constructor + /// Default / Non accessible constructor /// private ServiceClient() { } /// - /// Internal constructor used for testing. + /// Internal constructor used for testing. /// /// /// /// - internal ServiceClient(IOrganizationService orgSvc , HttpClient httpClient, Version targetVersion = null) + /// Logging provider + internal ServiceClient(IOrganizationService orgSvc , HttpClient httpClient, Version targetVersion = null , ILogger logger = null) { _testOrgSvcInterface = orgSvc; - _logEntry = new DataverseTraceLogger() + _logEntry = new DataverseTraceLogger(logger) { LogRetentionDuration = new TimeSpan(0,10,0), EnabledInMemoryLogCapture = true }; _connectionSvc = new ConnectionService(orgSvc); _connectionSvc.WebApiHttpClient = httpClient; - + if ( targetVersion != null) _connectionSvc.OrganizationVersion = targetVersion; @@ -602,48 +604,51 @@ internal ServiceClient(IOrganizationService orgSvc , HttpClient httpClient, Vers /// ServiceClient to accept the connectionstring as a parameter /// /// - public ServiceClient(string dataverseConnectionString) + /// Logging provider + public ServiceClient(string dataverseConnectionString , ILogger logger = null) { if (string.IsNullOrEmpty(dataverseConnectionString)) throw new ArgumentNullException("Dataverse ConnectionString", "Dataverse ConnectionString cannot be null or empty."); - ConnectToService(dataverseConnectionString); + ConnectToService(dataverseConnectionString , logger); } /// /// Uses the Organization Web proxy Client provided by the user /// /// User Provided Organization Web Proxy Client - public ServiceClient(OrganizationWebProxyClient externalOrgWebProxyClient) + /// Logging provider + public ServiceClient(OrganizationWebProxyClient externalOrgWebProxyClient , ILogger logger = null) { CreateServiceConnection(null, AuthenticationType.OAuth, string.Empty, string.Empty, string.Empty, null, string.Empty, MakeSecureString(string.Empty), string.Empty, string.Empty, string.Empty, false, false, null, string.Empty, null, - PromptBehavior.Auto, externalOrgWebProxyClient); + PromptBehavior.Auto, externalOrgWebProxyClient, externalLogger:logger); } /// - /// Creates an instance of ServiceClient who's authentication is managed by the caller. - /// This requires the caller to implement a function that will accept the InstanceURI as a string will return the access token as a string on demand when the ServiceClient requires it. - /// This approach is recommended when working with WebApplications or applications that are required to implement an on Behalf of flow for user authentication. + /// Creates an instance of ServiceClient who's authentication is managed by the caller. + /// This requires the caller to implement a function that will accept the InstanceURI as a string will return the access token as a string on demand when the ServiceClient requires it. + /// This approach is recommended when working with WebApplications or applications that are required to implement an on Behalf of flow for user authentication. /// /// URL of the Dataverse instance to connect too. /// Function that will be called when the access token is require for interaction with Dataverse. This function must accept a string (InstanceURI) and return a string (accesstoken) /// A value of "true" Forces the ServiceClient to create a new connection to the Dataverse instance vs reusing an existing connection, Defaults to true. - public ServiceClient(Uri instanceUrl, Func> tokenProviderFunction, bool useUniqueInstance = true) + /// Logging provider + public ServiceClient(Uri instanceUrl, Func> tokenProviderFunction, bool useUniqueInstance = true , ILogger logger = null) { GetAccessToken = tokenProviderFunction ?? - throw new DataverseConnectionException("tokenProviderFunction required for this constructor", new ArgumentNullException("tokenProviderFunction")); // Set the function pointer or access. + throw new DataverseConnectionException("tokenProviderFunction required for this constructor", new ArgumentNullException("tokenProviderFunction")); // Set the function pointer or access. CreateServiceConnection( null, AuthenticationType.ExternalTokenManagement, string.Empty, string.Empty, string.Empty, null, string.Empty, null, string.Empty, string.Empty, string.Empty, true, useUniqueInstance, null, - string.Empty, null, PromptBehavior.Never, null, string.Empty, StoreName.My, null, instanceUrl); + string.Empty, null, PromptBehavior.Never, null, string.Empty, StoreName.My, null, instanceUrl, externalLogger:logger); } /// /// Log in with OAuth for online connections, /// - /// Utilizes the discovery system to resolve the correct endpoint to use given the provided server orgName, user name and password. + /// Utilizes the discovery system to resolve the correct endpoint to use given the provided server orgName, user name and password. /// /// /// User Id supplied @@ -656,19 +661,20 @@ public ServiceClient(Uri instanceUrl, Func> tokenProviderFu /// The redirect URI application will be redirected post OAuth authentication. /// The prompt Behavior. /// (optional) If true attempts login using current user ( Online ) + /// Logging provider public ServiceClient(string userId, SecureString password, string regionGeo, string orgName, bool useUniqueInstance, OrganizationDetail orgDetail, - string clientId, Uri redirectUri, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false) + string clientId, Uri redirectUri, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false , ILogger logger = null) { CreateServiceConnection( null, AuthenticationType.OAuth, string.Empty, string.Empty, orgName, null, userId, password, string.Empty, regionGeo, string.Empty, true, useUniqueInstance, orgDetail, - clientId, redirectUri, promptBehavior, null, useDefaultCreds: useDefaultCreds); + clientId, redirectUri, promptBehavior, null, useDefaultCreds: useDefaultCreds, externalLogger:logger); } /// /// Log in with OAuth for online connections, /// - /// Will attempt to connect directly to the URL provided for the API endpoint. + /// Will attempt to connect directly to the URL provided for the API endpoint. /// /// /// User Id supplied @@ -679,13 +685,14 @@ public ServiceClient(string userId, SecureString password, string regionGeo, str /// The prompt Behavior. /// (optional) If true attempts login using current user ( Online ) /// API or Instance URI to access the Dataverse environment. - public ServiceClient(string userId, SecureString password, Uri hostUri, bool useUniqueInstance, - string clientId, Uri redirectUri, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false) + /// Logging provider + public ServiceClient(string userId, SecureString password, Uri hostUri, bool useUniqueInstance, + string clientId, Uri redirectUri, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false , ILogger logger = null) { CreateServiceConnection( null, AuthenticationType.OAuth, string.Empty, string.Empty, null, null, userId, password, string.Empty, null, string.Empty, true, useUniqueInstance, null, - clientId, redirectUri, promptBehavior, null, useDefaultCreds: useDefaultCreds, instanceUrl: hostUri); + clientId, redirectUri, promptBehavior, null, useDefaultCreds: useDefaultCreds, instanceUrl: hostUri , externalLogger:logger); } /// @@ -703,13 +710,14 @@ public ServiceClient(string userId, SecureString password, Uri hostUri, bool us /// The registered client Id on Azure portal. /// The redirect URI application will be redirected post OAuth authentication. /// The prompt Behavior. + /// Logging provider public ServiceClient(string userId, SecureString password, string domain, string hostName, string port, string orgName, bool useSsl, bool useUniqueInstance, - OrganizationDetail orgDetail, string clientId, Uri redirectUri, PromptBehavior promptBehavior = PromptBehavior.Auto) + OrganizationDetail orgDetail, string clientId, Uri redirectUri, PromptBehavior promptBehavior = PromptBehavior.Auto, ILogger logger = null) { CreateServiceConnection( null, AuthenticationType.OAuth, hostName, port, orgName, null, userId, password, domain, string.Empty, string.Empty, useSsl, useUniqueInstance, orgDetail, - clientId, redirectUri, promptBehavior, null); + clientId, redirectUri, promptBehavior, null, externalLogger:logger); } /// @@ -726,8 +734,9 @@ public ServiceClient(string userId, SecureString password, string domain, string /// The registered client Id on Azure portal. /// The redirect URI application will be redirected post OAuth authentication. /// The token cache path where token cache file is placed. + /// Logging provider public ServiceClient(X509Certificate2 certificate, StoreName certificateStoreName, string certificateThumbPrint, Uri instanceUrl, string orgName, bool useSsl, bool useUniqueInstance, - OrganizationDetail orgDetail, string clientId, Uri redirectUri, string tokenCachePath) + OrganizationDetail orgDetail, string clientId, Uri redirectUri, string tokenCachePath , ILogger logger = null) { if ((string.IsNullOrEmpty(clientId) || redirectUri == null)) { @@ -744,13 +753,13 @@ public ServiceClient(X509Certificate2 certificate, StoreName certificateStoreNam CreateServiceConnection( null, AuthenticationType.Certificate, string.Empty, string.Empty, orgName, null, string.Empty, null, string.Empty, string.Empty, string.Empty, useSsl, useUniqueInstance, orgDetail, - clientId, redirectUri, PromptBehavior.Never, null, certificateThumbPrint, certificateStoreName, certificate, instanceUrl); + clientId, redirectUri, PromptBehavior.Never, null, certificateThumbPrint, certificateStoreName, certificate, instanceUrl, externalLogger:logger); } /// /// Log in with Certificate Auth OnLine connections. - /// This requires the org API URI. + /// This requires the org API URI. /// /// Certificate to use during login /// StoreName to look in for certificate identified by certificateThumbPrint @@ -760,8 +769,9 @@ public ServiceClient(X509Certificate2 certificate, StoreName certificateStoreNam /// Dataverse Org Detail object, this is is returned from a query to the Dataverse Discovery Server service. not required. /// The registered client Id on Azure portal. /// The redirect URI application will be redirected post OAuth authentication. + /// Logging provider public ServiceClient(X509Certificate2 certificate, StoreName certificateStoreName, string certificateThumbPrint, Uri instanceUrl, bool useUniqueInstance, OrganizationDetail orgDetail, - string clientId, Uri redirectUri) + string clientId, Uri redirectUri , ILogger logger = null) { if ((string.IsNullOrEmpty(clientId))) { @@ -778,50 +788,53 @@ public ServiceClient(X509Certificate2 certificate, StoreName certificateStoreNam CreateServiceConnection( null, AuthenticationType.Certificate, string.Empty, string.Empty, string.Empty, null, string.Empty, null, string.Empty, string.Empty, string.Empty, true, useUniqueInstance, orgDetail, - clientId, redirectUri, PromptBehavior.Never, null, certificateThumbPrint, certificateStoreName, certificate, instanceUrl); + clientId, redirectUri, PromptBehavior.Never, null, certificateThumbPrint, certificateStoreName, certificate, instanceUrl , externalLogger:logger); } /// - /// ClientID \ ClientSecret Based Authentication flow. + /// ClientID \ ClientSecret Based Authentication flow. /// /// Direct URL of Dataverse instance to connect too. /// The registered client Id on Azure portal. /// Client Secret for Client Id. /// Use unique instance or reuse current connection. - public ServiceClient(Uri instanceUrl, string clientId, string clientSecret, bool useUniqueInstance) + /// Logging provider + public ServiceClient(Uri instanceUrl, string clientId, string clientSecret, bool useUniqueInstance , ILogger logger = null) { CreateServiceConnection(null, AuthenticationType.ClientSecret, string.Empty, string.Empty, string.Empty, null, string.Empty, MakeSecureString(clientSecret), string.Empty, string.Empty, string.Empty, true, useUniqueInstance, - null, clientId, null, PromptBehavior.Never, null, null, instanceUrl: instanceUrl); + null, clientId, null, PromptBehavior.Never, null, null, instanceUrl: instanceUrl, externalLogger:logger); } /// - /// ClientID \ ClientSecret Based Authentication flow, allowing for Secure Client ID passing. + /// ClientID \ ClientSecret Based Authentication flow, allowing for Secure Client ID passing. /// /// Direct URL of Dataverse instance to connect too. /// The registered client Id on Azure portal. /// Client Secret for Client Id. /// Use unique instance or reuse current connection. - public ServiceClient(Uri instanceUrl, string clientId, SecureString clientSecret, bool useUniqueInstance) + /// Logging provider + public ServiceClient(Uri instanceUrl, string clientId, SecureString clientSecret, bool useUniqueInstance , ILogger logger = null) { CreateServiceConnection(null, AuthenticationType.ClientSecret, string.Empty, string.Empty, string.Empty, null, string.Empty, clientSecret, string.Empty, string.Empty, string.Empty, true, useUniqueInstance, - null, clientId, null, PromptBehavior.Never, null, null, instanceUrl: instanceUrl); + null, clientId, null, PromptBehavior.Never, null, null, instanceUrl: instanceUrl, externalLogger: logger); } /// - /// Parse the given connection string + /// Parse the given connection string /// Connects to Dataverse using CreateWebServiceConnection /// /// - internal void ConnectToService(string connectionString) + /// Logging provider + internal void ConnectToService(string connectionString , ILogger logger = null) { - var parsedConnStr = DataverseConnectionStringProcessor.Parse(connectionString); + var parsedConnStr = DataverseConnectionStringProcessor.Parse(connectionString , logger); if ( parsedConnStr.AuthenticationType == AuthenticationType.InvalidConnection ) throw new ArgumentException("AuthType is invalid. Please see Microsoft.PowerPlatform.Dataverse.Client.AuthenticationType for supported authentication types." , "AuthType") @@ -834,10 +847,10 @@ internal void ConnectToService(string connectionString) string orgName = parsedConnStr.Organization; - if ((parsedConnStr.SkipDiscovery && parsedConnStr.ServiceUri != null) && string.IsNullOrEmpty(orgName)) + if ((parsedConnStr.SkipDiscovery && parsedConnStr.ServiceUri != null) && string.IsNullOrEmpty(orgName)) // Orgname is mandatory if skip discovery is not passed - throw new ArgumentNullException("Dataverse Instance Name or URL name Required", - parsedConnStr.IsOnPremOauth ? + throw new ArgumentNullException("Dataverse Instance Name or URL name Required", + parsedConnStr.IsOnPremOauth ? $"Unable to determine instance name to connect to from passed instance Uri, Uri does not match known online deployments." : $"Unable to determine instance name to connect to from passed instance Uri. Uri does not match specification for OnPrem instances."); @@ -858,7 +871,7 @@ internal void ConnectToService(string connectionString) switch (parsedConnStr.AuthenticationType) { case AuthenticationType.OAuth: - hostname = parsedConnStr.IsOnPremOauth ? hostname : string.Empty; // + hostname = parsedConnStr.IsOnPremOauth ? hostname : string.Empty; // port = parsedConnStr.IsOnPremOauth ? port : string.Empty; if (string.IsNullOrEmpty(clientId) && redirectUri == null) @@ -872,7 +885,7 @@ internal void ConnectToService(string connectionString) null, clientId, redirectUri, parsedConnStr.PromptBehavior, instanceUrl: parsedConnStr.SkipDiscovery ? parsedConnStr.ServiceUri : null, useDefaultCreds: parsedConnStr.UseCurrentUser); break; case AuthenticationType.Certificate: - hostname = parsedConnStr.IsOnPremOauth ? hostname : string.Empty; // + hostname = parsedConnStr.IsOnPremOauth ? hostname : string.Empty; // port = parsedConnStr.IsOnPremOauth ? port : string.Empty; if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(parsedConnStr.CertThumbprint)) @@ -916,11 +929,12 @@ internal void ConnectToService(string connectionString) /// when true, skips init /// Auth type of source connection /// source organization version - internal ServiceClient(OrganizationWebProxyClient externalOrgWebProxyClient, bool isCloned = true , AuthenticationType orginalAuthType = AuthenticationType.OAuth , Version sourceOrgVersion = null) + /// Logging provider + internal ServiceClient(OrganizationWebProxyClient externalOrgWebProxyClient, bool isCloned = true , AuthenticationType orginalAuthType = AuthenticationType.OAuth , Version sourceOrgVersion = null, ILogger logger = null) { CreateServiceConnection(null, orginalAuthType, string.Empty, string.Empty, string.Empty, null, string.Empty, MakeSecureString(string.Empty), string.Empty, string.Empty, string.Empty, false, false, null, string.Empty, null, - PromptBehavior.Auto, externalOrgWebProxyClient, isCloned: isCloned , incomingOrgVersion: sourceOrgVersion); + PromptBehavior.Auto, externalOrgWebProxyClient, isCloned: isCloned , incomingOrgVersion: sourceOrgVersion, externalLogger:logger); } @@ -953,6 +967,7 @@ internal ServiceClient(OrganizationWebProxyClient externalOrgWebProxyClient, boo /// When True, Indicates that the construction request is coming from a clone operation. /// (optional) If true attempts login using current user ( Online ) /// Incoming Org Version, used as part of clone. + /// Logging provider internal void CreateServiceConnection( object externalOrgServiceProxy, AuthenticationType requestedAuthType, @@ -978,20 +993,21 @@ internal void CreateServiceConnection( Uri instanceUrl = null, bool isCloned = false, bool useDefaultCreds = false, - Version incomingOrgVersion = null + Version incomingOrgVersion = null, + ILogger externalLogger = null ) { - _logEntry = new DataverseTraceLogger + _logEntry = new DataverseTraceLogger(externalLogger) { // Set initial properties EnabledInMemoryLogCapture = InMemoryLogCollectionEnabled, LogRetentionDuration = InMemoryLogCollectionTimeOutMinutes - }; + }; _connectionSvc = null; - // Handel Direct Set from Login control. + // Handel Direct Set from Login control. if (instanceUrl == null && orgDetail != null) { if (orgDetail.FriendlyName.Equals("DIRECTSET", StringComparison.OrdinalIgnoreCase) @@ -1010,11 +1026,11 @@ internal void CreateServiceConnection( try { - // Support for things like Excel that do not run from a local directory. + // Support for things like Excel that do not run from a local directory. if (File.Exists("microsoft.cds.sdk.dll")) { - // Do CDS Assembly version Check... - // Must be assemblies of version 5.0.9688.1533 or newer. + // Do CDS Assembly version Check... + // Must be assemblies of version 5.0.9688.1533 or newer. FileVersionInfo fv = FileVersionInfo.GetVersionInfo("microsoft.cds.sdk.dll"); if (fv != null) @@ -1035,15 +1051,15 @@ internal void CreateServiceConnection( _metadataUtlity = new MetadataUtility(this); _dynamicAppUtility = new DynamicEntityUtility(this, _metadataUtlity); - // doing a direct Connect, use Connection Manager to do the connect. - // if using an user provided connection,. + // doing a direct Connect, use Connection Manager to do the connect. + // if using an user provided connection,. if (externalOrgWebProxyClient != null) { _connectionSvc = new ConnectionService(externalOrgWebProxyClient, _logEntry); _connectionSvc.IsAClone = isCloned; if ( isCloned && incomingOrgVersion != null) { - _connectionSvc.OrganizationVersion = incomingOrgVersion; + _connectionSvc.OrganizationVersion = incomingOrgVersion; } } else @@ -1051,11 +1067,11 @@ internal void CreateServiceConnection( if (requestedAuthType == AuthenticationType.ExternalTokenManagement) { _connectionSvc = new ConnectionService( - requestedAuthType, - instanceUrl, - useUniqueInstance, - orgDetail, clientId, - redirectUri, certificateThumbPrint, + requestedAuthType, + instanceUrl, + useUniqueInstance, + orgDetail, clientId, + redirectUri, certificateThumbPrint, certificateStoreName, certificate, hostName, port, false,logSink:_logEntry); if (GetAccessToken != null) @@ -1068,7 +1084,7 @@ internal void CreateServiceConnection( } else { - // check to see what sort of login this is. + // check to see what sort of login this is. if (requestedAuthType == AuthenticationType.OAuth) { if (!String.IsNullOrEmpty(hostName)) @@ -1094,7 +1110,7 @@ internal void CreateServiceConnection( { try { - // Assign the log entry host to the ConnectionService engine + // Assign the log entry host to the ConnectionService engine ConnectionService tempConnectService = null; _connectionSvc.InternetProtocalToUse = useSsl ? "https" : "http"; if (!_connectionSvc.DoLogin(out tempConnectService)) @@ -1107,12 +1123,12 @@ internal void CreateServiceConnection( { if (tempConnectService != null) { - _connectionSvc.Dispose(); // Clean up temp version and unassign assets. + _connectionSvc.Dispose(); // Clean up temp version and unassign assets. _connectionSvc = tempConnectService; } _cachObjecName = _connectionSvc.ServiceCACHEName + ".LookupCache"; - // Min supported version for batch operations. + // Min supported version for batch operations. if (_connectionSvc?.OrganizationVersion != null && Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(_connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.BatchOperations)) _batchManager = new BatchManager(_logEntry); @@ -1135,7 +1151,7 @@ internal void CreateServiceConnection( #region Public General Interfaces /// - /// Enabled only if InMemoryLogCollectionEnabled is true. + /// Enabled only if InMemoryLogCollectionEnabled is true. /// Return all logs currently stored for the ServiceClient in queue. /// public IEnumerable> GetAllLogs() @@ -1146,7 +1162,7 @@ public IEnumerable> GetAllLogs() } /// - /// Enabled only if InMemoryLogCollectionEnabled is true. + /// Enabled only if InMemoryLogCollectionEnabled is true. /// Return all logs currently stored for the ServiceClient in queue in string list format with [UTCDateTime][LogEntry]. /// public string[] GetAllLogsAsStringList() @@ -1155,22 +1171,24 @@ public string[] GetAllLogsAsStringList() } /// - /// Clone, 'Clones" the current Dataverse ServiceClient with a new connection to Dataverse. - /// Clone only works for connections creating using OAuth Protocol. + /// Clone, 'Clones" the current Dataverse ServiceClient with a new connection to Dataverse. + /// Clone only works for connections creating using OAuth Protocol. /// + /// Logging provider /// returns an active ServiceClient or null - public ServiceClient Clone() + public ServiceClient Clone(ILogger logger = null) { - return Clone(null); + return Clone(null, logger:logger); } /// - /// Clone, 'Clones" the current Dataverse Service client with a new connection to Dataverse. - /// Clone only works for connections creating using OAuth Protocol. + /// Clone, 'Clones" the current Dataverse Service client with a new connection to Dataverse. + /// Clone only works for connections creating using OAuth Protocol. /// /// Strong Type Assembly to reference as part of the create of the clone. + /// Logging provider /// - public ServiceClient Clone(System.Reflection.Assembly strongTypeAsm) + public ServiceClient Clone(System.Reflection.Assembly strongTypeAsm, ILogger logger = null) { if (_connectionSvc == null || IsReady == false) { @@ -1205,13 +1223,13 @@ public ServiceClient Clone(System.Reflection.Assembly strongTypeAsm) if (proxy != null) { proxy.HeaderToken = _connectionSvc.WebClient.HeaderToken; - var SvcClient = new ServiceClient(proxy, true , _connectionSvc.AuthenticationTypeInUse ,_connectionSvc?.OrganizationVersion); + var SvcClient = new ServiceClient(proxy, true , _connectionSvc.AuthenticationTypeInUse ,_connectionSvc?.OrganizationVersion, logger:logger); SvcClient._connectionSvc.SetClonedProperties(this); SvcClient.CallerAADObjectId = CallerAADObjectId; SvcClient.CallerId = CallerId; SvcClient.MaxRetryCount = _maxRetryCount; SvcClient.RetryPauseTime = _retryPauseTime; - SvcClient.GetAccessToken = GetAccessToken; + SvcClient.GetAccessToken = GetAccessToken; return SvcClient; } @@ -1234,10 +1252,11 @@ public ServiceClient Clone(System.Reflection.Assembly strongTypeAsm) /// The prompt behavior. /// The authority provider for OAuth tokens. Unique if any already known. /// (Optional) if specified, tries to use the current user + /// Logging provider /// A collection of organizations - public static async Task DiscoverOnPremiseOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, string clientId, Uri redirectUri, string authority, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false) + public static async Task DiscoverOnPremiseOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, string clientId, Uri redirectUri, string authority, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false, ILogger logger = null) { - return await ConnectionService.DiscoverOrganizationsAsync(discoveryServiceUri, clientCredentials, clientId, redirectUri, promptBehavior, isOnPrem:true , authority, useDefaultCreds: useDefaultCreds); + return await ConnectionService.DiscoverOrganizationsAsync(discoveryServiceUri, clientCredentials, clientId, redirectUri, promptBehavior, isOnPrem:true , authority, useDefaultCreds: useDefaultCreds , externalLogger: logger); } /// @@ -1251,10 +1270,11 @@ public static async Task DiscoverOnPremiseOrganiza /// The deployment type: OnPrem or Online. /// The authority provider for OAuth tokens. Unique if any already known. /// (Optional) if specified, tries to use the current user + /// Logging provider /// A collection of organizations - public static async Task DiscoverOnlineOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, string clientId, Uri redirectUri, bool isOnPrem, string authority, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false) + public static async Task DiscoverOnlineOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, string clientId, Uri redirectUri, bool isOnPrem, string authority, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false, ILogger logger= null) { - return await ConnectionService.DiscoverOrganizationsAsync(discoveryServiceUri, clientCredentials, clientId, redirectUri, promptBehavior, isOnPrem, authority, useGlobalDisco: true, useDefaultCreds: useDefaultCreds); + return await ConnectionService.DiscoverOrganizationsAsync(discoveryServiceUri, clientCredentials, clientId, redirectUri, promptBehavior, isOnPrem, authority, useGlobalDisco: true, useDefaultCreds: useDefaultCreds, externalLogger: logger); } /// @@ -1271,13 +1291,14 @@ public static async Task DiscoverOnlineOrganizatio /// The deployment type: OnPrem or Online. /// The authority provider for OAuth tokens. Unique if any already known. /// (Optional) if specified, tries to use the current user + /// Logging provider /// A collection of organizations - public static async Task DiscoverOnlineOrganizationsAsync(string userId, string password, string clientId, Uri redirectUri, bool isOnPrem, string authority, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false, Model.DiscoveryServer discoServer = null) + public static async Task DiscoverOnlineOrganizationsAsync(string userId, string password, string clientId, Uri redirectUri, bool isOnPrem, string authority, PromptBehavior promptBehavior = PromptBehavior.Auto, bool useDefaultCreds = false, Model.DiscoveryServer discoServer = null, ILogger logger = null) { Uri discoveryUriToUse = null; if (discoServer != null && discoServer.RequiresRegionalDiscovery) { - // use the specified regional discovery server. + // use the specified regional discovery server. discoveryUriToUse = discoServer.RegionalGlobalDiscoveryServer; } else @@ -1286,12 +1307,12 @@ public static async Task DiscoverOnlineOrganizatio discoveryUriToUse = new Uri(ConnectionService.GlobalDiscoveryAllInstancesUri); } - // create credentials. + // create credentials. ClientCredentials clientCredentials = new ClientCredentials(); clientCredentials.UserName.UserName = userId; clientCredentials.UserName.Password = password; - return await ConnectionService.DiscoverOrganizationsAsync(discoveryUriToUse, clientCredentials, clientId, redirectUri, promptBehavior, isOnPrem, authority, useGlobalDisco: true, useDefaultCreds: useDefaultCreds); + return await ConnectionService.DiscoverOrganizationsAsync(discoveryUriToUse, clientCredentials, clientId, redirectUri, promptBehavior, isOnPrem, authority, useGlobalDisco: true, useDefaultCreds: useDefaultCreds , externalLogger: logger); } /// @@ -1299,13 +1320,14 @@ public static async Task DiscoverOnlineOrganizatio /// /// Global discovery base URI to use to connect too, if null will utilize the commercial Global Discovery Server. /// Function that will provide access token to the discovery call. + /// Logging provider /// - public static async Task DiscoverOnlineOrganizationsAsync(Func> tokenProviderFunction , Uri discoveryServiceUri = null) + public static async Task DiscoverOnlineOrganizationsAsync(Func> tokenProviderFunction , Uri discoveryServiceUri = null, ILogger logger = null) { if (discoveryServiceUri == null) discoveryServiceUri = new Uri(ConnectionService.GlobalDiscoveryAllInstancesUri); // use commercial GD - return await ConnectionService.DiscoverGlobalOrganizationsAsync(discoveryServiceUri, tokenProviderFunction); + return await ConnectionService.DiscoverGlobalOrganizationsAsync(discoveryServiceUri, tokenProviderFunction , externalLogger:logger); } #endregion @@ -1314,7 +1336,7 @@ public static async Task DiscoverOnlineOrganizatio #region Batch Interface methods. /// - /// Create a Batch Request for executing batch operations. This returns an ID that will be used to identify a request as a batch request vs a "normal" request. + /// Create a Batch Request for executing batch operations. This returns an ID that will be used to identify a request as a batch request vs a "normal" request. /// /// Name of the Batch /// Should Results be returned @@ -1340,14 +1362,14 @@ public Guid CreateBatchOperationRequest(string batchName, bool returnResults = t Guid guBatchId = Guid.Empty; if (_batchManager != null) { - // Try to create a new Batch here. + // Try to create a new Batch here. guBatchId = _batchManager.CreateNewBatch(batchName, returnResults, continueOnError); } return guBatchId; } /// - /// Returns the batch id for a given batch name. + /// Returns the batch id for a given batch name. /// /// Name of Batch /// @@ -1379,7 +1401,7 @@ public Guid GetBatchOperationIdRequestByName(string batchName) /// - /// Returns the organization request at a give position + /// Returns the organization request at a give position /// /// ID of the batch /// Position @@ -1467,7 +1489,7 @@ public RequestBatch GetBatchById(Guid batchId) } /// - /// Executes the batch command and then parses the retrieved items into a list. + /// Executes the batch command and then parses the retrieved items into a list. /// If there exists a exception then the LastException would be filled with the first item that has the exception. /// /// ID of the batch to run @@ -1506,7 +1528,7 @@ public List>> RetrieveBatchRespons /// - /// Begins running the Batch command. + /// Begins running the Batch command. /// /// ID of the batch to run /// true if the batch begins, false if not. @@ -1542,19 +1564,19 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) return null; } - // Ready to run the batch. + // Ready to run the batch. _logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Executing Batch {0}|{1}, Sending {2} events.", b.BatchId, b.BatchName, b.BatchItems.Count), TraceEventType.Verbose); ExecuteMultipleRequest req = new ExecuteMultipleRequest(); req.Settings = b.BatchRequestSettings; OrganizationRequestCollection reqstList = new OrganizationRequestCollection(); - // Make sure the batch is ordered. + // Make sure the batch is ordered. reqstList.AddRange(b.BatchItems.Select(s => s.Request)); req.Requests = reqstList; b.Status = BatchStatus.Running; ExecuteMultipleResponse resp = (ExecuteMultipleResponse)Command_Execute(req, "Execute Batch Command"); - // Need to add retry logic here to deal with a "server busy" status. + // Need to add retry logic here to deal with a "server busy" status. b.Status = BatchStatus.Complete; if (resp != null) { @@ -1568,15 +1590,15 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) return null; } - // Need methods here to work with the batch now, - // get items out by id, - // get batch request. + // Need methods here to work with the batch now, + // get items out by id, + // get batch request. #endregion /// - /// Uses the dynamic entity patter to create a new entity + /// Uses the dynamic entity patter to create a new entity /// /// Name of Entity To create /// Initial Values @@ -1587,7 +1609,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) /// Guid on Success, Guid.Empty on fail public Guid CreateNewRecord(string entityName, Dictionary valueArray, string applyToSolution = "", bool enabledDuplicateDetection = false, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { @@ -1602,7 +1624,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) return Guid.Empty; - // Create the New Entity Type. + // Create the New Entity Type. Entity NewEnt = new Entity(); NewEnt.LogicalName = entityName; @@ -1624,7 +1646,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) if (AddRequestToBatch(batchId, createReq, entityName, string.Format(CultureInfo.InvariantCulture, "Request for Create on {0} queued", entityName), bypassPluginExecution)) return Guid.Empty; - + createResp = (CreateResponse)ExecuteOrganizationRequestImpl(createReq, entityName, useWebAPI: true , bypassPluginExecution: bypassPluginExecution); if (createResp != null) { @@ -1636,7 +1658,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) } /// - /// Generic update entity + /// Generic update entity /// /// String version of the entity name /// Key fieldname of the entity @@ -1649,7 +1671,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) /// true on success, false on fail public bool UpdateEntity(string entityName, string keyFieldName, Guid id, Dictionary fieldList, string applyToSolution = "", bool enabledDuplicateDetection = false, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null || id == Guid.Empty) { return false; @@ -1670,15 +1692,15 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) AddValueToPropertyList(field, PropertyList); } - // Add the key... - // check to see if the key is in the import set already + // Add the key... + // check to see if the key is in the import set already if (!fieldList.ContainsKey(keyFieldName)) PropertyList.Add(new KeyValuePair(keyFieldName, id)); #endregion uEnt.Attributes.AddRange(PropertyList.ToArray()); - uEnt.Id = id; + uEnt.Id = id; UpdateRequest req = new UpdateRequest(); req.Target = uEnt; @@ -1700,7 +1722,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) /// - /// Updates the State and Status of the Entity passed in. + /// Updates the State and Status of the Entity passed in. /// /// Name of the entity /// Guid ID of the entity you are updating @@ -1715,7 +1737,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) } /// - /// Updates the State and Status of the Entity passed in. + /// Updates the State and Status of the Entity passed in. /// /// Name of the entity /// Guid ID of the entity you are updating @@ -1739,7 +1761,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) /// true on success, false on failure public bool DeleteEntity(string entityType, Guid entityId, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -1763,7 +1785,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) } else { - // Error and fall though. + // Error and fall though. _logEntry.Log("Unable to add request to batch, Batching is not currently available, Executing normally", TraceEventType.Warning); } } @@ -1782,7 +1804,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) } else { - // Error and fall though. + // Error and fall though. _logEntry.Log("Unable to add request to batch, Batching is not currently available, Executing normally", TraceEventType.Warning); } } @@ -1793,7 +1815,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) DeleteResponse resp = (DeleteResponse)ExecuteOrganizationRequestImpl(req, string.Format(CultureInfo.InvariantCulture, "Trying to Delete. Entity = {0}, ID = {1}", entityType, entityId), useWebAPI: true , bypassPluginExecution: bypassPluginExecution); if (resp != null) { - // Clean out the cache if the account happens to be stored in there. + // Clean out the cache if the account happens to be stored in there. if ((_CachObject != null) && (_CachObject.ContainsKey(entityType))) { while (_CachObject[entityType].ContainsValue(entityId)) @@ -1814,7 +1836,7 @@ public ExecuteMultipleResponse ExecuteBatch(Guid batchId) } /// - /// Gets a list of accounts based on the search parameters. + /// Gets a list of accounts based on the search parameters. /// /// Dataverse Entity Type Name to search /// Array of Search Parameters @@ -1842,7 +1864,7 @@ public Dictionary> GetEntityDataBySearchParam /// - /// Gets a list of accounts based on the search parameters. + /// Gets a list of accounts based on the search parameters. /// /// Dataverse Entity Type Name to search /// Array of Search Parameters @@ -1855,7 +1877,7 @@ public Dictionary> GetEntityDataBySearchParam public Dictionary> GetEntityDataBySearchParams(string entityName, List searchParameters, LogicalSearchOperator searchOperator, - List fieldList, Guid batchId = default(Guid), + List fieldList, Guid batchId = default(Guid), bool bypassPluginExecution = false) { string pgCookie = string.Empty; @@ -1864,7 +1886,7 @@ public Dictionary> GetEntityDataBySearchParam } /// - /// Searches for data from an entity based on the search parameters. + /// Searches for data from an entity based on the search parameters. /// /// Name of the entity to search /// Array of Search Parameters @@ -1894,7 +1916,7 @@ public Dictionary> GetEntityDataBySearchParam bool bypassPluginExecution = false ) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error outPageCookie = string.Empty; isMoreRecords = false; @@ -1908,7 +1930,7 @@ public Dictionary> GetEntityDataBySearchParam if (searchParameters == null) searchParameters = new List(); - // Build the query here. + // Build the query here. QueryExpression query = BuildQueryFilter(entityName, searchParameters, fieldList, searchOperator); if (pageCount != -1) @@ -1989,7 +2011,7 @@ public Dictionary> GetEntityDataBySearchParam /// results as an entity collection or null public EntityCollection GetEntityDataByFetchSearchEC(string fetchXml, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { @@ -2000,7 +2022,7 @@ public Dictionary> GetEntityDataBySearchParam if (string.IsNullOrWhiteSpace(fetchXml)) return null; - // This model directly requests the via FetchXML + // This model directly requests the via FetchXML RetrieveMultipleRequest req = new RetrieveMultipleRequest() { Query = new FetchExpression(fetchXml) }; RetrieveMultipleResponse retrieved; @@ -2068,7 +2090,7 @@ public EntityCollection GetEntityDataByFetchSearchEC( bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error outPageCookie = string.Empty; isMoreRecords = false; @@ -2107,7 +2129,7 @@ public EntityCollection GetEntityDataByFetchSearchEC( /// - /// Queries an Object via a M to M Link + /// Queries an Object via a M to M Link /// /// Name of the entity you want return data from /// Search Prams for the Return Entity @@ -2146,7 +2168,7 @@ public Dictionary> GetEntityDataByLinkedSearc } /// - /// Queries an Object via a M to M Link + /// Queries an Object via a M to M Link /// /// Name of the entity you want return data from /// Search Prams for the Return Entity @@ -2175,7 +2197,7 @@ public Dictionary> GetEntityDataByLinkedSearc bool isReflexiveRelationship = false, bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -2208,16 +2230,16 @@ public Dictionary> GetEntityDataByLinkedSearc #endregion // Create Link Object for LinkedEnitty Name and add the filter info - LinkEntity nestedLinkEntity = new LinkEntity(); // this is the Secondary - nestedLinkEntity.LinkToEntityName = linkedEntityName; // what Entity are we linking too... + LinkEntity nestedLinkEntity = new LinkEntity(); // this is the Secondary + nestedLinkEntity.LinkToEntityName = linkedEntityName; // what Entity are we linking too... nestedLinkEntity.LinkToAttributeName = linkedEntityLinkAttribName; // what Attrib are we linking To on that Entity - nestedLinkEntity.LinkFromAttributeName = isReflexiveRelationship ? string.Format("{0}two", linkedEntityLinkAttribName) : linkedEntityLinkAttribName; // what Attrib on the primary object are we linking too. - nestedLinkEntity.LinkCriteria = linkedEntityFilter; // Filtered query + nestedLinkEntity.LinkFromAttributeName = isReflexiveRelationship ? string.Format("{0}two", linkedEntityLinkAttribName) : linkedEntityLinkAttribName; // what Attrib on the primary object are we linking too. + nestedLinkEntity.LinkCriteria = linkedEntityFilter; // Filtered query - //Create Link Object for Primary + //Create Link Object for Primary LinkEntity m2mLinkEntity = new LinkEntity(); m2mLinkEntity.LinkToEntityName = m2MEntityName; // this is the M2M table - m2mLinkEntity.LinkToAttributeName = isReflexiveRelationship ? string.Format("{0}one", returnEntityPrimaryId) : returnEntityPrimaryId; // this is the name of the other side. + m2mLinkEntity.LinkToAttributeName = isReflexiveRelationship ? string.Format("{0}one", returnEntityPrimaryId) : returnEntityPrimaryId; // this is the name of the other side. m2mLinkEntity.LinkFromAttributeName = returnEntityPrimaryId; m2mLinkEntity.LinkEntities.AddRange(new LinkEntity[] { nestedLinkEntity }); @@ -2231,11 +2253,11 @@ public Dictionary> GetEntityDataByLinkedSearc cols.Columns.AddRange(fieldList.ToArray()); } - // Build Query + // Build Query QueryExpression query = new QueryExpression(); - query.NoLock = false; // Added to remove the Locks. + query.NoLock = false; // Added to remove the Locks. - query.EntityName = returnEntityName; // Set to the requested entity Type + query.EntityName = returnEntityName; // Set to the requested entity Type if (cols != null) query.ColumnSet = cols; else @@ -2278,7 +2300,7 @@ public Dictionary> GetEntityDataByLinkedSearc /// public Dictionary GetEntityDataById(string searchEntity, Guid entityId, List fieldList, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null || entityId == Guid.Empty) { return null; @@ -2303,7 +2325,7 @@ public Dictionary> GetEntityDataByLinkedSearc else req.ColumnSet = new ColumnSet(true);// new AllColumns(); - req.Target = re; //getEnt; + req.Target = re; //getEnt; if (AddRequestToBatch(batchId, req, string.Format(CultureInfo.InvariantCulture, "Trying to Read a Record. Entity = {0} , ID = {1}", searchEntity, entityId.ToString()), string.Format(CultureInfo.InvariantCulture, "Request to Read a Record. Entity = {0} , ID = {1} queued", searchEntity, entityId.ToString()), bypassPluginExecution)) @@ -2318,7 +2340,7 @@ public Dictionary> GetEntityDataByLinkedSearc try { - // Not really doing an update here... just turning it into something I can walk. + // Not really doing an update here... just turning it into something I can walk. Dictionary resultSet = new Dictionary(); AddDataToResultSet(ref resultSet, resp.Entity); return resultSet; @@ -2343,7 +2365,7 @@ public Dictionary> GetEntityDataByLinkedSearc /// public Guid CreateAnnotation(string targetEntityTypeName, Guid targetEntityId, Dictionary fieldList, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (string.IsNullOrEmpty(targetEntityTypeName)) return Guid.Empty; @@ -2388,7 +2410,7 @@ public Guid CreateNewActivityEntry(string activityEntityTypeName, { #region PreChecks - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -2409,7 +2431,7 @@ public Guid CreateNewActivityEntry(string activityEntityTypeName, Guid activityId = Guid.Empty; try { - // reuse the passed in field list if its available, else punt and create a new one. + // reuse the passed in field list if its available, else punt and create a new one. if (fieldList == null) fieldList = new Dictionary(); @@ -2419,10 +2441,10 @@ public Guid CreateNewActivityEntry(string activityEntityTypeName, if (!string.IsNullOrWhiteSpace(description)) fieldList.Add("description", new DataverseDataTypeWrapper(description, DataverseFieldType.String)); - // Create the base record. + // Create the base record. activityId = CreateNewRecord(activityEntityTypeName, fieldList, bypassPluginExecution: bypassPluginExecution); - // if I have a user ID, try to assign it to that user. + // if I have a user ID, try to assign it to that user. if (!string.IsNullOrWhiteSpace(creatingUserId)) { Guid userId = GetLookupValueForEntity("systemuser", creatingUserId); @@ -2450,8 +2472,8 @@ public Guid CreateNewActivityEntry(string activityEntityTypeName, } /// - /// Closes the Activity type specified. - /// The Activity Entity type supports fax , letter , and phonecall + /// Closes the Activity type specified. + /// The Activity Entity type supports fax , letter , and phonecall /// *Note: This will default to using English names for Status. if you need to use Non-English, you should populate the names for completed for the status and state. /// /// Type of Activity you would like to close.. Supports fax, letter, phonecall @@ -2480,14 +2502,14 @@ public Guid CreateNewActivityEntry(string activityEntityTypeName, /// private bool UpdateStateStatusForEntity(string entName, Guid entId, string newState, string newStatus, int newStateid = -1, int newStatusid = -1, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error SetStateRequest req = new SetStateRequest(); req.EntityMoniker = new EntityReference(entName, entId); int istatuscode = -1; int istatecode = -1; - // Modified to prefer IntID's first... this is in support of multi languages. + // Modified to prefer IntID's first... this is in support of multi languages. if (newStatusid != -1) istatuscode = newStatusid; @@ -2541,7 +2563,7 @@ public Guid CreateNewActivityEntry(string activityEntityTypeName, } /// - /// Returns all Activities Related to a given Entity ID. + /// Returns all Activities Related to a given Entity ID. /// Only Account, Contact and Opportunity entities are supported. /// /// Type of Entity to search against @@ -2579,7 +2601,7 @@ public Dictionary> GetActivitiesBy( } /// - /// Returns all Activities Related to a given Entity ID. + /// Returns all Activities Related to a given Entity ID. /// Only Account, Contact and Opportunity entities are supported. /// /// Type of Entity to search against @@ -2614,7 +2636,7 @@ public Dictionary> GetActivitiesBy( } /// - /// Returns all Activities Related to a given Entity ID. + /// Returns all Activities Related to a given Entity ID. /// Only Account, Contact and Opportunity entities are supported. /// /// Type of Entity to search against @@ -2652,7 +2674,7 @@ public Dictionary> GetEntityDataByRollup( /// - /// Returns all Activities Related to a given Entity ID. + /// Returns all Activities Related to a given Entity ID. /// Only Account, Contact and Opportunity entities are supported. /// /// Type of Entity to search against @@ -2687,7 +2709,7 @@ public Dictionary> GetEntityDataByRollup( bool bypassPluginExecution = false ) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error outPageCookie = string.Empty; isMoreRecords = false; @@ -2802,7 +2824,7 @@ public T GetDataByKeyFromResultsSet(Dictionary results, strin } catch { - // try to do the basic return + // try to do the basic return return (T)results[key]; } } @@ -2810,18 +2832,18 @@ public T GetDataByKeyFromResultsSet(Dictionary results, strin catch { if (results[key] is T) - // try to do the basic return + // try to do the basic return return (T)results[key]; } } - // MSB :: Added this method in light of new features in CDS 2011.. + // MSB :: Added this method in light of new features in CDS 2011.. if (results[key] is T) - // try to do the basic return + // try to do the basic return return (T)results[key]; else { - if (results != null && results.ContainsKey(key)) // Specific To CDS 2011.. + if (results != null && results.ContainsKey(key)) // Specific To CDS 2011.. { if (results.ContainsKey(key + "_Property")) { @@ -2845,7 +2867,7 @@ public T GetDataByKeyFromResultsSet(Dictionary results, strin } /// - /// Executes a named workflow on an object. + /// Executes a named workflow on an object. /// /// name of the workflow to run /// ID to exec against @@ -2854,7 +2876,7 @@ public T GetDataByKeyFromResultsSet(Dictionary results, strin /// Async Op ID of the WF or Guid.Empty public Guid ExecuteWorkflowOnEntity(string workflowName, Guid id, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -2930,14 +2952,14 @@ public T GetDataByKeyFromResultsSet(Dictionary results, strin /// Guid of the Import Request, or Guid.Empty. If Guid.Empty then request failed. public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); return Guid.Empty; } - // Error checking + // Error checking if (importRequest == null) { this._logEntry.Log("************ Exception on SubmitImportRequest, importRequest is required", TraceEventType.Error); @@ -2960,7 +2982,7 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil Guid ImportFile = Guid.Empty; List ImportFileIds = new List(); - // Create Import Object + // Create Import Object // The Import Object is the anchor for the Import job in Dataverse. Dictionary importFields = new Dictionary(); importFields.Add("name", new DataverseDataTypeWrapper(importRequest.ImportName, DataverseFieldType.String)); @@ -2968,18 +2990,18 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil ImportId = CreateNewRecord("import", importFields); if (ImportId == Guid.Empty) - // Error here; + // Error here; return Guid.Empty; #region Determin Map to Use //Guid guDataMapId = Guid.Empty; if (string.IsNullOrWhiteSpace(importRequest.DataMapFileName) && importRequest.DataMapFileId == Guid.Empty) - // User Requesting to use System Mapping here. - importRequest.UseSystemMap = true; // Override whatever setting they had here. + // User Requesting to use System Mapping here. + importRequest.UseSystemMap = true; // Override whatever setting they had here. else { - // User providing information on a map to use. - // Query to get the map from the system + // User providing information on a map to use. + // Query to get the map from the system List fldList = new List(); fldList.Add("name"); fldList.Add("source"); @@ -2987,12 +3009,12 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil Dictionary MapData = null; if (importRequest.DataMapFileId != Guid.Empty) { - // Have the id here... get the map based on the ID. + // Have the id here... get the map based on the ID. MapData = GetEntityDataById("importmap", importRequest.DataMapFileId, fldList); } else { - // Search by name... exact match required. + // Search by name... exact match required. List filters = new List(); DataverseSearchFilter filter = new DataverseSearchFilter(); filter.FilterOperator = Microsoft.Xrm.Sdk.Query.LogicalOperator.And; @@ -3003,7 +3025,7 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil Dictionary> rslts = GetEntityDataBySearchParams("importmap", filters, LogicalSearchOperator.None, fldList); if (rslts != null && rslts.Count > 0) { - // if there is more then one record returned.. throw an error ( should not happen ) + // if there is more then one record returned.. throw an error ( should not happen ) if (rslts.Count > 1) { // log error here. @@ -3022,10 +3044,10 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil ImportMap = importRequest.DataMapFileId; - // Now get the entity import mapping info, We need this to get the source entity name from the map XML file. + // Now get the entity import mapping info, We need this to get the source entity name from the map XML file. if (ImportMap != Guid.Empty) { - // Iterate over the import files and update the entity names. + // Iterate over the import files and update the entity names. fldList.Clear(); fldList.Add("sourceentityname"); @@ -3074,18 +3096,18 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil Dictionary importFileFields = new Dictionary(); foreach (var FileItem in importRequest.Files) { - // Create the Import File Object - Loop though file objects and create as many as necessary. + // Create the Import File Object - Loop though file objects and create as many as necessary. // This is the row that has the data being imported as well as the status of the import file. importFileFields.Add("name", new DataverseDataTypeWrapper(FileItem.FileName, DataverseFieldType.String)); importFileFields.Add("source", new DataverseDataTypeWrapper(FileItem.FileName, DataverseFieldType.String)); - importFileFields.Add("filetypecode", new DataverseDataTypeWrapper(FileItem.FileType, DataverseFieldType.Picklist)); // File Type is either : 0 = CSV , 1 = XML , 2 = Attachment + importFileFields.Add("filetypecode", new DataverseDataTypeWrapper(FileItem.FileType, DataverseFieldType.Picklist)); // File Type is either : 0 = CSV , 1 = XML , 2 = Attachment importFileFields.Add("content", new DataverseDataTypeWrapper(FileItem.FileContentToImport, DataverseFieldType.String)); importFileFields.Add("enableduplicatedetection", new DataverseDataTypeWrapper(FileItem.EnableDuplicateDetection, DataverseFieldType.Boolean)); - importFileFields.Add("usesystemmap", new DataverseDataTypeWrapper(importRequest.UseSystemMap, DataverseFieldType.Boolean)); // Use the System Map to get somthing done. + importFileFields.Add("usesystemmap", new DataverseDataTypeWrapper(importRequest.UseSystemMap, DataverseFieldType.Boolean)); // Use the System Map to get somthing done. importFileFields.Add("sourceentityname", new DataverseDataTypeWrapper(FileItem.SourceEntityName, DataverseFieldType.String)); importFileFields.Add("targetentityname", new DataverseDataTypeWrapper(FileItem.TargetEntityName, DataverseFieldType.String)); - importFileFields.Add("datadelimitercode", new DataverseDataTypeWrapper(FileItem.DataDelimiter, DataverseFieldType.Picklist)); // 1 = " | 2 = | 3 = ' - importFileFields.Add("fielddelimitercode", new DataverseDataTypeWrapper(FileItem.FieldDelimiter, DataverseFieldType.Picklist)); // 1 = : | 2 = , | 3 = ' + importFileFields.Add("datadelimitercode", new DataverseDataTypeWrapper(FileItem.DataDelimiter, DataverseFieldType.Picklist)); // 1 = " | 2 = | 3 = ' + importFileFields.Add("fielddelimitercode", new DataverseDataTypeWrapper(FileItem.FieldDelimiter, DataverseFieldType.Picklist)); // 1 = : | 2 = , | 3 = ' importFileFields.Add("isfirstrowheader", new DataverseDataTypeWrapper(FileItem.IsFirstRowHeader, DataverseFieldType.Boolean)); importFileFields.Add("processcode", new DataverseDataTypeWrapper(1, DataverseFieldType.Picklist)); if (FileItem.IsRecordOwnerATeam) @@ -3110,7 +3132,7 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil #endregion - // if We have an Import File... Activate Import. + // if We have an Import File... Activate Import. if (continueImport) { ParseImportResponse parseResp = (ParseImportResponse)Command_Execute(new ParseImportRequest() { ImportId = ImportId }, @@ -3153,7 +3175,7 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil } else { - // Error.. Clean up the other records. + // Error.. Clean up the other records. string err = LastError; Exception ex = LastException; @@ -3169,7 +3191,7 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil if (ImportId != Guid.Empty) DeleteEntity("import", ImportId); - // This is done to allow the error to be available to the user after the class cleans things up. + // This is done to allow the error to be available to the user after the class cleans things up. if (ex != null) _logEntry.Log(err, TraceEventType.Error, ex); else @@ -3189,7 +3211,7 @@ public Guid SubmitImportRequest(ImportRequest importRequest, DateTime delayUntil /// Returns ID of the datamap or Guid.Empty public Guid ImportDataMap(string dataMapXml, bool replaceIds = true, bool dataMapXmlIsFilePath = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -3204,7 +3226,7 @@ public Guid ImportDataMap(string dataMapXml, bool replaceIds = true, bool dataMa if (dataMapXmlIsFilePath) { - // try to load the file from the file system + // try to load the file from the file system if (File.Exists(dataMapXml)) { try @@ -3278,7 +3300,7 @@ public Guid ImportDataMap(string dataMapXml, bool replaceIds = true, bool dataMa /// - /// Import Solution Async used Execute Async pattern to run a solution import. + /// Import Solution Async used Execute Async pattern to run a solution import. /// /// Path to the Solution File /// Activate Plugin's and workflows on the Solution @@ -3323,13 +3345,13 @@ public Guid ImportSolution(string solutionPath, out Guid importId, bool activate /// Returns the Async Job ID. To find the status of the job, query the AsyncOperation Entity using GetEntityDataByID using the returned value of this method public Guid DeleteAndPromoteSolutionAsync(string uniqueName) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); return Guid.Empty; } - // Test for non blank unique name. + // Test for non blank unique name. if (string.IsNullOrEmpty(uniqueName)) { _logEntry.Log("Solution UniqueName is required.", TraceEventType.Error); @@ -3365,14 +3387,14 @@ public Guid DeleteAndPromoteSolutionAsync(string uniqueName) /// /// - /// Request Dataverse to install sample data shipped with Dataverse. Note this is process will take a few moments to execute. + /// Request Dataverse to install sample data shipped with Dataverse. Note this is process will take a few moments to execute. /// This method will return once the request has been submitted. /// /// /// ID of the Async job executing the request public Guid InstallSampleData() { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -3385,7 +3407,7 @@ public Guid InstallSampleData() return Guid.Empty; } - // Create Request to Install Sample data. + // Create Request to Install Sample data. InstallSampleDataRequest loadSampledataRequest = new InstallSampleDataRequest() { RequestId = Guid.NewGuid() }; InstallSampleDataResponse resp = (InstallSampleDataResponse)Command_Execute(loadSampledataRequest, "Executing InstallSampleDataRequest for InstallSampleData"); if (resp == null) @@ -3396,14 +3418,14 @@ public Guid InstallSampleData() /// /// - /// Request Dataverse to remove sample data shipped with Dataverse. Note this is process will take a few moments to execute. + /// Request Dataverse to remove sample data shipped with Dataverse. Note this is process will take a few moments to execute. /// This method will return once the request has been submitted. /// /// /// ID of the Async job executing the request public Guid UninstallSampleData() { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -3425,14 +3447,14 @@ public Guid UninstallSampleData() } /// - /// Determines if the Dataverse sample data has been installed + /// Determines if the Dataverse sample data has been installed /// /// True if the sample data is installed, False if not. public ImportStatus IsSampleDataInstalled() { try { - // Query the Org I'm connected to to get the sample data import info. + // Query the Org I'm connected to to get the sample data import info. Dictionary> theOrg = GetEntityDataBySearchParams("organization", new Dictionary(), LogicalSearchOperator.None, new List() { "sampledataimportid" }); @@ -3499,7 +3521,7 @@ public enum ImportStatus #endregion /// - /// Associates one Entity to another where an M2M Relationship Exists. + /// Associates one Entity to another where an M2M Relationship Exists. /// /// Entity on one side of the relationship /// The Id of the record on the first side of the relationship @@ -3511,7 +3533,7 @@ public enum ImportStatus /// true on success, false on fail public bool CreateEntityAssociation(string entityName1, Guid entity1Id, string entityName2, Guid entity2Id, string relationshipName, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -3542,7 +3564,7 @@ public enum ImportStatus } /// - /// Associates multiple entities of the same time to a single entity + /// Associates multiple entities of the same time to a single entity /// /// Entity that things will be related too. /// ID of entity that things will be related too @@ -3555,7 +3577,7 @@ public enum ImportStatus /// true on success, false on fail public bool CreateMultiEntityAssociation(string targetEntity, Guid targetEntity1Id, string sourceEntityName, List sourceEntitieIds, string relationshipName, Guid batchId = default(Guid), bool bypassPluginExecution = false, bool isReflexiveRelationship = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -3570,7 +3592,7 @@ public enum ImportStatus AssociateRequest req = new AssociateRequest(); req.Relationship = new Relationship(relationshipName); - if (isReflexiveRelationship) // used to determine if the relationship role is reflexive. + if (isReflexiveRelationship) // used to determine if the relationship role is reflexive. req.Relationship.PrimaryEntityRole = EntityRole.Referenced; req.RelatedEntities = new EntityReferenceCollection(); foreach (Guid g in sourceEntitieIds) @@ -3591,7 +3613,7 @@ public enum ImportStatus } /// - /// Removes the Association between 2 entity items where an M2M Relationship Exists. + /// Removes the Association between 2 entity items where an M2M Relationship Exists. /// /// Entity on one side of the relationship /// The Id of the record on the first side of the relationship @@ -3603,7 +3625,7 @@ public enum ImportStatus /// true on success, false on fail public bool DeleteEntityAssociation(string entityName1, Guid entity1Id, string entityName2, Guid entity2Id, string relationshipName, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -3643,7 +3665,7 @@ public enum ImportStatus /// public bool AssignEntityToUser(Guid userId, string entityName, Guid entityId, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null || userId == Guid.Empty || entityId == Guid.Empty) { return false; @@ -3665,7 +3687,7 @@ public enum ImportStatus } /// - /// This will route a Entity to a public queue, + /// This will route a Entity to a public queue, /// /// ID of the Entity to route /// Name of the Entity that the Id describes @@ -3677,7 +3699,7 @@ public enum ImportStatus /// true on success public bool AddEntityToQueue(Guid entityId, string entityName, string queueName, Guid workingUserId, bool setWorkingByUser = false, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null || entityId == Guid.Empty) { return false; @@ -3707,7 +3729,7 @@ public enum ImportStatus req.DestinationQueueId = guQueueID; req.Target = new EntityReference(entityName, entityId); - // Set the worked by user if the request includes it. + // Set the worked by user if the request includes it. if (setWorkingByUser) { Entity queItm = new Entity("queueitem"); @@ -3730,7 +3752,7 @@ public enum ImportStatus } /// - /// this will send an Email to the + /// this will send an Email to the /// /// ID of the Email activity /// Tracking Token or Null @@ -3739,7 +3761,7 @@ public enum ImportStatus /// public bool SendSingleEmail(Guid emailid, string token, Guid batchId = default(Guid), bool bypassPluginExecution = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null || emailid == Guid.Empty) { return false; @@ -3748,11 +3770,11 @@ public enum ImportStatus if (token == null) token = string.Empty; - // Send the mail now. + // Send the mail now. SendEmailRequest req = new SendEmailRequest(); req.EmailId = emailid; req.TrackingToken = token; - req.IssueSend = true; // Send it now. + req.IssueSend = true; // Send it now. if (AddRequestToBatch(batchId, req, string.Format(CultureInfo.InvariantCulture, "Send Direct email ({0}) tracking token {1}", emailid.ToString(), token), string.Format(CultureInfo.InvariantCulture, "Request to Send Direct email ({0}) tracking token {1} Queued", emailid.ToString(), token), bypassPluginExecution)) @@ -3766,7 +3788,7 @@ public enum ImportStatus } /// - /// Returns the user ID of the currently logged in user. + /// Returns the user ID of the currently logged in user. /// /// public Guid GetMyUserId() @@ -3787,14 +3809,14 @@ public Guid GetMyUserId() /// public PickListMetaElement GetPickListElementFromMetadataEntity(string targetEntity, string attribName) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService != null) { List attribDataList = _dynamicAppUtility.GetAttributeDataByEntity(targetEntity, attribName); if (attribDataList.Count > 0) { - // have data.. - // need to make sure its really a pick list. + // have data.. + // need to make sure its really a pick list. foreach (AttributeData attributeData in attribDataList) { switch (attributeData.AttributeType) @@ -3824,13 +3846,13 @@ public PickListMetaElement GetPickListElementFromMetadataEntity(string targetEnt } /// - /// Gets a global option set from Dataverse. + /// Gets a global option set from Dataverse. /// /// Name of the Option Set To get /// OptionSetMetadata or null public OptionSetMetadata GetGlobalOptionSetMetadata(string globalOptionSetName) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -3857,7 +3879,7 @@ public OptionSetMetadata GetGlobalOptionSetMetadata(string globalOptionSetName) /// public List GetAllEntityMetadata(bool onlyPublished = true, EntityFilters filter = EntityFilters.Default) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error #region Basic Checks if (DataverseService == null) { @@ -3878,14 +3900,14 @@ public List GetAllEntityMetadata(bool onlyPublished = true, Enti } /// - /// Returns the Metadata for an entity from Dataverse, defaults to basic data only. + /// Returns the Metadata for an entity from Dataverse, defaults to basic data only. /// /// Logical name of the entity /// filter to apply to the query, defaults to default entity data. /// public EntityMetadata GetEntityMetadata(string entityLogicalname, EntityFilters queryFilter = EntityFilters.Default) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error #region Basic Checks if (DataverseService == null) { @@ -3906,7 +3928,7 @@ public EntityMetadata GetEntityMetadata(string entityLogicalname, EntityFilters } /// - /// Returns the Form Entity References for a given form type. + /// Returns the Form Entity References for a given form type. /// /// logical name of the entity you are querying for form data. /// Form Type you want @@ -3952,7 +3974,7 @@ public List GetEntityFormIdListByType(string entityLogicalname, /// public List GetAllAttributesForEntity(string entityLogicalname) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error #region Basic Checks if (DataverseService == null) { @@ -3985,7 +4007,7 @@ public List GetAllAttributesForEntity(string entityLogicalnam /// public AttributeMetadata GetEntityAttributeMetadataForAttribute(string entityLogicalname, string attribName) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error #region Basic Checks if (DataverseService == null) { @@ -4011,7 +4033,7 @@ public AttributeMetadata GetEntityAttributeMetadataForAttribute(string entityLog } /// - /// Gets an Entity Name by Logical name or Type code. + /// Gets an Entity Name by Logical name or Type code. /// /// logical name of the entity /// Type code for the entity @@ -4022,7 +4044,7 @@ public string GetEntityDisplayName(string entityName, int entityTypeCode = -1) } /// - /// Gets an Entity Name by Logical name or Type code. + /// Gets an Entity Name by Logical name or Type code. /// /// logical name of the entity /// Type code for the entity @@ -4033,7 +4055,7 @@ public string GetEntityDisplayNamePlural(string entityName, int entityTypeCode = } /// - /// This will clear the Metadata cache for either all entities or the specified entity + /// This will clear the Metadata cache for either all entities or the specified entity /// /// Optional: name of the entity to clear cached info for public void ResetLocalMetadataCache(string entityName = "") @@ -4043,7 +4065,7 @@ public void ResetLocalMetadataCache(string entityName = "") } /// - /// Gets the Entity Display Name. + /// Gets the Entity Display Name. /// /// /// @@ -4051,7 +4073,7 @@ public void ResetLocalMetadataCache(string entityName = "") /// private string GetEntityDisplayNameImpl(string entityName, int entityTypeCode = -1, bool getPlural = false) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error #region Basic Checks if (DataverseService == null) { @@ -4068,7 +4090,7 @@ private string GetEntityDisplayNameImpl(string entityName, int entityTypeCode = try { - // Get the entity by type code if necessary. + // Get the entity by type code if necessary. if (entityTypeCode != -1) entityName = _metadataUtlity.GetEntityLogicalName(entityTypeCode); @@ -4080,7 +4102,7 @@ private string GetEntityDisplayNameImpl(string entityName, int entityTypeCode = - // Pull Object type code for this object. + // Pull Object type code for this object. EntityMetadata entData = _metadataUtlity.GetEntityMetadata(EntityFilters.Entity, entityName); @@ -4091,14 +4113,14 @@ private string GetEntityDisplayNameImpl(string entityName, int entityTypeCode = if (entData.DisplayCollectionName != null && entData.DisplayCollectionName.UserLocalizedLabel != null) return entData.DisplayCollectionName.UserLocalizedLabel.Label; else - return entityName; // Default to echo the same name back + return entityName; // Default to echo the same name back } else { if (entData.DisplayName != null && entData.DisplayName.UserLocalizedLabel != null) return entData.DisplayName.UserLocalizedLabel.Label; else - return entityName; // Default to echo the same name back + return entityName; // Default to echo the same name back } } @@ -4111,13 +4133,13 @@ private string GetEntityDisplayNameImpl(string entityName, int entityTypeCode = } /// - /// Gets the typecode of an entity by name. + /// Gets the typecode of an entity by name. /// /// name of the entity to get the type code on /// public string GetEntityTypeCode(string entityName) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error #region Basic Checks if (DataverseService == null) { @@ -4135,7 +4157,7 @@ public string GetEntityTypeCode(string entityName) try { - // Pull Object type code for this object. + // Pull Object type code for this object. EntityMetadata entData = _metadataUtlity.GetEntityMetadata(EntityFilters.Entity, entityName); @@ -4167,7 +4189,7 @@ public string GetEntityName(int entityTypeCode) /// - /// Adds an option to a pick list on an entity. + /// Adds an option to a pick list on an entity. /// /// Entity Name to Target /// Attribute Name on the Entity @@ -4177,7 +4199,7 @@ public string GetEntityName(int entityTypeCode) /// true on success, on fail check last error. public bool CreateOrUpdatePickListElement(string targetEntity, string attribName, List locLabelList, int valueData, bool publishOnComplete) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error #region Basic Checks if (DataverseService == null) { @@ -4203,9 +4225,9 @@ public bool CreateOrUpdatePickListElement(string targetEntity, string attribName return false; } - LoadLCIDs(); // Load current languages . + LoadLCIDs(); // Load current languages . - // Clear out the Metadata for this object. + // Clear out the Metadata for this object. if (_metadataUtlity != null) _metadataUtlity.ClearCachedEntityMetadata(targetEntity); @@ -4214,7 +4236,7 @@ public bool CreateOrUpdatePickListElement(string targetEntity, string attribName if (!entData.IsCustomEntity.Value) { - // Only apply this if the entity is not a custom entity + // Only apply this if the entity is not a custom entity if (valueData <= 199999) { _logEntry.Log("Option Value must exceed 200000", TraceEventType.Error); @@ -4225,7 +4247,7 @@ public bool CreateOrUpdatePickListElement(string targetEntity, string attribName #endregion - // get the values for the requested attribute. + // get the values for the requested attribute. PickListMetaElement listData = GetPickListElementFromMetadataEntity(targetEntity, attribName); if (listData == null) { @@ -4235,7 +4257,7 @@ public bool CreateOrUpdatePickListElement(string targetEntity, string attribName bool isUpdate = false; if (listData.Items != null && listData.Items.Count != 0) { - // Check to see if the value we are looking to insert already exists by name or value. + // Check to see if the value we are looking to insert already exists by name or value. List DisplayLabels = new List(); foreach (LocalizedLabel loclbl in locLabelList) { @@ -4247,7 +4269,7 @@ public bool CreateOrUpdatePickListElement(string targetEntity, string attribName foreach (PickListItem pItem in listData.Items) { - // check the value by id. + // check the value by id. if (pItem.PickListItemId == valueData) { if (DisplayLabels.Contains(pItem.DisplayLabel)) @@ -4260,10 +4282,10 @@ public bool CreateOrUpdatePickListElement(string targetEntity, string attribName break; } - //// Check the value by name... by putting this hear, we will handle a label update vs a Duplicate label. + //// Check the value by name... by putting this hear, we will handle a label update vs a Duplicate label. if (DisplayLabels.Contains(pItem.DisplayLabel)) { - // THis is an ERROR State... While Dataverse will allow 2 labels with the same text, it looks weird. + // THis is an ERROR State... While Dataverse will allow 2 labels with the same text, it looks weird. DisplayLabels.Clear(); _logEntry.Log("Label Name exists, Please use a different display name for the label.", TraceEventType.Error); return false; @@ -4303,8 +4325,8 @@ public bool CreateOrUpdatePickListElement(string targetEntity, string attribName } else { - // create request. - // Create a new insert request + // create request. + // Create a new insert request InsertOptionValueRequest req = new InsertOptionValueRequest(); req.AttributeLogicalName = attribName; @@ -4333,7 +4355,7 @@ public bool CreateOrUpdatePickListElement(string targetEntity, string attribName } - // Publish the update if asked to. + // Publish the update if asked to. if (publishOnComplete) return PublishEntity(targetEntity); else @@ -4341,14 +4363,14 @@ public bool CreateOrUpdatePickListElement(string targetEntity, string attribName } /// - /// Publishes an entity to the production system, + /// Publishes an entity to the production system, /// used in conjunction with the Metadata services. /// /// Name of the entity to publish /// True on success public bool PublishEntity(string entityName) { - // Now Publish the update. + // Now Publish the update. string sPublishUpdateXml = string.Format(CultureInfo.InvariantCulture, "{0}", entityName); @@ -4369,13 +4391,13 @@ public bool PublishEntity(string entityName) /// private bool LoadLCIDs() { - // Now Publish the update. - // Check to see if the Language ID's are loaded. + // Now Publish the update. + // Check to see if the Language ID's are loaded. if (_loadedLCIDList == null) { _loadedLCIDList = new List(); - // load the Dataverse Language List. + // load the Dataverse Language List. RetrieveAvailableLanguagesRequest lanReq = new RetrieveAvailableLanguagesRequest(); RetrieveAvailableLanguagesResponse rsp = (RetrieveAvailableLanguagesResponse)Command_Execute(lanReq, "Reading available languages from Dataverse"); if (rsp == null) @@ -4412,8 +4434,8 @@ public static bool RemoveOAuthTokenCache(string tokenCachePath = "") //if (_CdsServiceClientTokenCache == null) // _CdsServiceClientTokenCache = new CdsServiceClientTokenCache(tokenCachePath); //return _CdsServiceClientTokenCache.Clear(tokenCachePath); - //TODO: Update for new Token cache providers. - //return false; + //TODO: Update for new Token cache providers. + //return false; } #endregion @@ -4455,7 +4477,7 @@ private String AddPagingParametersToFetchXml(string fetchXml, int pageCount, int } /// - /// Makes a secure string + /// Makes a secure string /// /// /// @@ -4468,14 +4490,14 @@ public static SecureString MakeSecureString(string pass) { _pass.AppendChar(c); } - _pass.MakeReadOnly(); // Lock it down. + _pass.MakeReadOnly(); // Lock it down. return _pass; } return null; } /// - /// Builds the Query expression to use with a Search. + /// Builds the Query expression to use with a Search. /// /// /// @@ -4494,7 +4516,7 @@ private static QueryExpression BuildQueryFilter(string entityName, List filters = BuildFilterList(searchParams); - // Link Filter. + // Link Filter. FilterExpression Queryfilter = new FilterExpression(); Queryfilter.Filters.AddRange(filters); @@ -4505,21 +4527,21 @@ private static QueryExpression BuildQueryFilter(string entityName, List - /// Creates a SearchFilterList from a Search string Dictionary + /// Creates a SearchFilterList from a Search string Dictionary /// /// Inbound Search Strings /// List that will be populated @@ -4574,7 +4596,7 @@ private static List BuildFilterList(List - /// Get the localize label from a Dataverse Label. + /// Get the localize label from a Dataverse Label. /// /// /// @@ -4582,7 +4604,7 @@ private static string GetLocalLabel(Label localLabel) { foreach (LocalizedLabel lbl in localLabel.LocalizedLabels) { - // try to get the current display langue code. + // try to get the current display langue code. if (lbl.LanguageCode == CultureInfo.CurrentUICulture.LCID) { return lbl.Label; @@ -4662,7 +4684,7 @@ private Guid GetLookupValueForEntity(string entName, string Value) } - // High effort objects that are generally not changed during the live cycle of a connection are cached here. + // High effort objects that are generally not changed during the live cycle of a connection are cached here. if (guResultID != Guid.Empty) { if (!_CachObject.ContainsKey(entName.ToString())) @@ -4677,7 +4699,7 @@ private Guid GetLookupValueForEntity(string entName, string Value) } /// - /// Lookup a entity ID by a single search element. + /// Lookup a entity ID by a single search element. /// Used for Lookup Lists. /// /// Text to search for @@ -4725,7 +4747,7 @@ private Guid LookupEntitiyID(string SearchValue, string ent, string IDField, str internal void AddValueToPropertyList(KeyValuePair Field, AttributeCollection PropertyList) { if (string.IsNullOrEmpty(Field.Key)) - // throw exception + // throw exception throw new System.ArgumentOutOfRangeException("valueArray", "Missing Dataverse field name"); try @@ -4805,10 +4827,10 @@ private static Dictionary> CreateResultDataSe Dictionary> Results = new Dictionary>(); foreach (Entity bEnt in resp.Entities) { - // Not really doing an update here... just turning it into something I can walk. + // Not really doing an update here... just turning it into something I can walk. Dictionary SearchRstls = new Dictionary(); AddDataToResultSet(ref SearchRstls, bEnt); - // Add Ent name and ID + // Add Ent name and ID SearchRstls.Add("ReturnProperty_EntityName", bEnt.LogicalName); SearchRstls.Add("ReturnProperty_Id ", bEnt.Id); Results.Add(Guid.NewGuid().ToString(), SearchRstls); @@ -4821,7 +4843,7 @@ private static Dictionary> CreateResultDataSe /// /// Adds a request to a batch with display and handling logic - /// will fail out if batching is not enabled. + /// will fail out if batching is not enabled. /// /// ID of the batch to add too /// Organization request to Add @@ -4850,7 +4872,7 @@ internal bool AddRequestToBatch(Guid batchId, OrganizationRequest req, string ba } else { - // Error and fall though. + // Error and fall though. _logEntry.Log("Unable to add request to batch, Batching is not currently available, Executing normally", TraceEventType.Warning); } } @@ -4862,18 +4884,18 @@ internal bool AddRequestToBatch(Guid batchId, OrganizationRequest req, string ba #region Public Access to direct commands. /// - /// Executes a web request against Xrm WebAPI. + /// Executes a web request against Xrm WebAPI. /// - /// Here you would pass the path and query parameters that you wish to pass onto the WebAPI. + /// Here you would pass the path and query parameters that you wish to pass onto the WebAPI. /// The format used here is as follows: - /// {APIURI}/api/data/v{instance version}/querystring. - /// For example, - /// if you wanted to get data back from an account, you would pass the following: + /// {APIURI}/api/data/v{instance version}/querystring. + /// For example, + /// if you wanted to get data back from an account, you would pass the following: /// accounts(id) /// which creates: get - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts(id) /// if you were creating an account, you would pass the following: - /// accounts - /// which creates: post - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts - body contains the data. + /// accounts + /// which creates: post - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts - body contains the data. /// /// Method to use for the request /// Content your passing to the request @@ -4882,7 +4904,7 @@ internal bool AddRequestToBatch(Guid batchId, OrganizationRequest req, string ba /// public HttpResponseMessage ExecuteWebRequest(HttpMethod method, string queryString, string body, Dictionary> customHeaders, string contentType = default(string)) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) { _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); @@ -4897,7 +4919,7 @@ internal bool AddRequestToBatch(Guid batchId, OrganizationRequest req, string ba if (Uri.TryCreate(queryString, UriKind.Absolute, out var urlPath)) { - // Was able to create a URL here... Need to make sure that we strip out everything up to the last segment. + // Was able to create a URL here... Need to make sure that we strip out everything up to the last segment. string baseQueryString = urlPath.Segments.Last(); if (!string.IsNullOrEmpty(urlPath.Query)) queryString = baseQueryString + urlPath.Query; @@ -4935,7 +4957,7 @@ private OrganizationResponse ExecuteOrganizationRequestImpl(OrganizationRequest } else { - // use Web API. + // use Web API. return Command_WebAPIProcess_ExecuteAsync(req, logMessageTag, bypassPluginExecution, new CancellationToken()).Result; } } @@ -4957,7 +4979,7 @@ private async Task ExecuteOrganizationRequestAsyncImpl(Org } else { - // use Web API. + // use Web API. return await Command_WebAPIProcess_ExecuteAsync(req, logMessageTag, bypassPluginExecution , cancellationToken); } } @@ -4974,27 +4996,27 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga { HttpMethod methodToExecute = HttpMethod.Get; Entity cReq = null; - if (req.Parameters.ContainsKey("Target") && req.Parameters["Target"] is Entity ent) // this should cover things that have targets. + if (req.Parameters.ContainsKey("Target") && req.Parameters["Target"] is Entity ent) // this should cover things that have targets. { - cReq = ent; + cReq = ent; } - else if (req.Parameters.ContainsKey("Target") && req.Parameters["Target"] is EntityReference entRef) // this should cover things that have targets. + else if (req.Parameters.ContainsKey("Target") && req.Parameters["Target"] is EntityReference entRef) // this should cover things that have targets. { - cReq = new Entity(entRef.LogicalName, entRef.Id); + cReq = new Entity(entRef.LogicalName, entRef.Id); } if (cReq != null) { - // if CRUD type. get Entity + // if CRUD type. get Entity var EntityData = _metadataUtlity.GetEntityMetadata(EntityFilters.Relationships, cReq.LogicalName); if (EntityData == null) { _logEntry.Log($"Execute Organization Request failed, failed to acquire entity data for {cReq.LogicalName}", TraceEventType.Warning); return null; } - // Get Entity Set name from Entity being addressed + // Get Entity Set name from Entity being addressed var EntSetname = EntityData.EntitySetName; - // generate webAPI Create request. + // generate webAPI Create request. string postUri = string.Empty; string requestName = req.RequestName.ToLower(); @@ -5029,7 +5051,7 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga } else { - // its just a post. + // its just a post. postUri = $"{EntityData.EntitySetName}"; } @@ -5042,7 +5064,7 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga if (requestBodyObject != null) bodyOfRequest = Newtonsoft.Json.JsonConvert.SerializeObject(requestBodyObject); - // Process request params. + // Process request params. if (req.Parameters.ContainsKey(Utilities.RequestHeaders.BYPASSCUSTOMPLUGINEXECUTION)) { if (req.Parameters[Utilities.RequestHeaders.BYPASSCUSTOMPLUGINEXECUTION].GetType() == typeof(bool) && @@ -5068,7 +5090,7 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga if (req.Parameters[Utilities.RequestHeaders.SUPPRESSDUPLICATEDETECTION].GetType() == typeof(bool) && (bool)req.Parameters[Utilities.RequestHeaders.SUPPRESSDUPLICATEDETECTION]) { - suppressDuplicateDetection = true; + suppressDuplicateDetection = true; } } @@ -5084,16 +5106,16 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga string rowVersion = string.Empty; string IfMatchHeaderTag = string.Empty; - if ( req.Parameters.ContainsKey(Utilities.RequestHeaders.CONCURRENCYBEHAVIOR) && + if ( req.Parameters.ContainsKey(Utilities.RequestHeaders.CONCURRENCYBEHAVIOR) && (ConcurrencyBehavior)req.Parameters[Utilities.RequestHeaders.CONCURRENCYBEHAVIOR] != ConcurrencyBehavior.Default) { - // Found concurrency flag. + // Found concurrency flag. if (!string.IsNullOrEmpty(cReq.RowVersion)) { rowVersion = cReq.RowVersion; - // Now manage behavior. - // if IfRowVersionMatches == Upsert/update/Delete should only work if record exists by rowRowVersion. == If-Match + RowVersion. - // If AlwaysOverwrite == Upsert/update/Delete should only work if record exists at all == No IF-MatchTag. + // Now manage behavior. + // if IfRowVersionMatches == Upsert/update/Delete should only work if record exists by rowRowVersion. == If-Match + RowVersion. + // If AlwaysOverwrite == Upsert/update/Delete should only work if record exists at all == No IF-MatchTag. if ((ConcurrencyBehavior)req.Parameters[Utilities.RequestHeaders.CONCURRENCYBEHAVIOR] == ConcurrencyBehavior.AlwaysOverwrite) { IfMatchHeaderTag = "If-Match"; @@ -5112,7 +5134,7 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga } } - // Setup headers. + // Setup headers. Dictionary> headers = new Dictionary>(); headers.Add("Prefer", new List() { "odata.include-annotations=*" }); @@ -5143,7 +5165,7 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga headers.Add($"{Utilities.RequestHeaders.DATAVERSEHEADERPROPERTYPREFIX}{Utilities.RequestHeaders.SUPPRESSDUPLICATEDETECTION}", new List() { "true" }); } - string addedQueryParams = ""; + string addedQueryParams = ""; // modify post URI if (!string.IsNullOrEmpty(tagValue)) { @@ -5153,20 +5175,20 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga addedQueryParams = paramValues.ToString(); } - // add queryParms to the PostUri. + // add queryParms to the PostUri. if (!string.IsNullOrEmpty(addedQueryParams)) { - postUri = $"{postUri}?{addedQueryParams}"; + postUri = $"{postUri}?{addedQueryParams}"; } - // Execute request + // Execute request var sResp = await Command_WebExecuteAsync(postUri, bodyOfRequest, methodToExecute, headers, "application/json", logMessageTag); if (sResp != null && sResp.IsSuccessStatusCode) { if (req is CreateRequest) { Guid createdRecId = Guid.Empty; - // find location code. + // find location code. if (sResp.Headers.Location != null) { string locationReferance = sResp.Headers.Location.Segments.Last(); @@ -5183,12 +5205,12 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga } else if (req is DeleteRequest) { - return new DeleteResponse(); + return new DeleteResponse(); } else if (req is UpsertRequest) { //var upsertReturn = new UpsertResponse(); - return null; + return null; } else return null; @@ -5197,7 +5219,7 @@ private async Task Command_WebAPIProcess_ExecuteAsync(Orga return null; } else - return null; + return null; } else { @@ -5259,7 +5281,7 @@ public bool ExecuteEntityDeleteRequest(string entName, Guid entId, string logMes /// Returns the Import Solution Job ID. To find the status of the job, query the ImportJob Entity using GetEntityDataByID using the returned value of this method internal Guid ImportSolutionToImpl(string solutionPath, out Guid importId, bool activatePlugIns, bool overwriteUnManagedCustomizations, bool skipDependancyOnProductUpdateCheckOnInstall, bool importAsHoldingSolution, bool isInternalUpgrade, bool useAsync, Dictionary extraParameters) { - _logEntry.ResetLastError(); // Reset Last Error + _logEntry.ResetLastError(); // Reset Last Error importId = Guid.Empty; if (DataverseService == null) { @@ -5288,15 +5310,15 @@ internal Guid ImportSolutionToImpl(string solutionPath, out Guid importId, bool desiredLayerOrder = extraParameters.ContainsKey(ImportSolutionProperties.DESIREDLAYERORDERPARAM) ? extraParameters[ImportSolutionProperties.DESIREDLAYERORDERPARAM] as LayerDesiredOrder : null; componetsToProcess = extraParameters.ContainsKey(ImportSolutionProperties.COMPONENTPARAMETERSPARAM) ? extraParameters[ImportSolutionProperties.COMPONENTPARAMETERSPARAM] as EntityCollection : null; - // Pick up the data from the request, if the request has the AsyncRibbonProcessing flag, pick up the value of it. + // Pick up the data from the request, if the request has the AsyncRibbonProcessing flag, pick up the value of it. asyncRibbonProcessing = extraParameters.ContainsKey(ImportSolutionProperties.ASYNCRIBBONPROCESSING) ? extraParameters[ImportSolutionProperties.ASYNCRIBBONPROCESSING] as bool? : null; // If the value is populated, and t if (asyncRibbonProcessing != null && asyncRibbonProcessing.HasValue) { if (isConnectedToOnPrem) { - // Not supported for OnPrem. - // reset the asyncRibbonProcess to Null. + // Not supported for OnPrem. + // reset the asyncRibbonProcess to Null. this._logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.ASYNCRIBBONPROCESSING} property. This is not valid for OnPremise deployments and will be removed", TraceEventType.Warning); asyncRibbonProcessing = null; } @@ -5304,7 +5326,7 @@ internal Guid ImportSolutionToImpl(string solutionPath, out Guid importId, bool { if (!Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(_connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.AllowAsyncRibbonProcessing)) { - // Not supported on this version of Dataverse + // Not supported on this version of Dataverse this._logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.ASYNCRIBBONPROCESSING} property. This request Dataverse version {Utilities.FeatureVersionMinimums.AllowAsyncRibbonProcessing.ToString()} or above. Current Dataverse version is {_connectionSvc?.OrganizationVersion}. This property will be removed", TraceEventType.Warning); asyncRibbonProcessing = null; } @@ -5322,7 +5344,7 @@ internal Guid ImportSolutionToImpl(string solutionPath, out Guid importId, bool { if (!Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(_connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.AllowComponetInfoProcessing)) { - // Not supported on this version of Dataverse + // Not supported on this version of Dataverse this._logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.COMPONENTPARAMETERSPARAM} property. This request Dataverse version {Utilities.FeatureVersionMinimums.AllowComponetInfoProcessing.ToString()} or above. Current Dataverse version is {_connectionSvc?.OrganizationVersion}. This property will be removed", TraceEventType.Warning); componetsToProcess = null; } @@ -5332,7 +5354,7 @@ internal Guid ImportSolutionToImpl(string solutionPath, out Guid importId, bool string solutionNameForLogging = string.IsNullOrWhiteSpace(solutionName) ? string.Empty : string.Concat(solutionName, " - "); - // try to load the file from the file system + // try to load the file from the file system if (File.Exists(solutionPath)) { try @@ -5378,12 +5400,12 @@ internal Guid ImportSolutionToImpl(string solutionPath, out Guid importId, bool SolutionImportRequest.SkipProductUpdateDependencies = skipDependancyOnProductUpdateCheckOnInstall; } - if (importAsHoldingSolution) // If Import as Holding is set.. + if (importAsHoldingSolution) // If Import as Holding is set.. { - // Check for Min version of Dataverse for support of Import as Holding solution. + // Check for Min version of Dataverse for support of Import as Holding solution. if (Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(_connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.ImportHoldingSolution)) { - // Use Parameters to add the property here to support the underlying Xrm API on the incorrect version. + // Use Parameters to add the property here to support the underlying Xrm API on the incorrect version. SolutionImportRequest.Parameters.Add("HoldingSolution", importAsHoldingSolution); } } @@ -5402,7 +5424,7 @@ internal Guid ImportSolutionToImpl(string solutionPath, out Guid importId, bool // Assign Tracking ID Guid requestTrackingId = Guid.NewGuid(); SolutionImportRequest.RequestId = requestTrackingId; - // Creating Async Solution Import request. + // Creating Async Solution Import request. ExecuteAsyncRequest req = new ExecuteAsyncRequest() { Request = SolutionImportRequest }; _logEntry.Log(string.Format(CultureInfo.InvariantCulture, "{1}Created Async ImportSolutionRequest : RequestID={0} ", requestTrackingId.ToString(), solutionNameForLogging), TraceEventType.Verbose); @@ -5467,8 +5489,8 @@ internal Guid ImportSolutionToImpl(string solutionPath, out Guid importId, bool } /// - /// Executes a Dataverse Create Request and returns the organization response object. - /// Uses an Async pattern to allow for the thread to be backgrounded. + /// Executes a Dataverse Create Request and returns the organization response object. + /// Uses an Async pattern to allow for the thread to be backgrounded. /// /// Request to run /// Formatted Error string @@ -5482,7 +5504,7 @@ internal async Task Command_ExecuteAsync(OrganizationReque /// - /// Executes a Dataverse Create Request and returns the organization response object. + /// Executes a Dataverse Create Request and returns the organization response object. /// /// Request to run /// Formatted Error string @@ -5501,7 +5523,7 @@ internal OrganizationResponse Command_Execute(OrganizationRequest req, string er { try { - _retryPauseTimeRunning = _retryPauseTime; // Set the default time for each loop. + _retryPauseTimeRunning = _retryPauseTime; // Set the default time for each loop. retry = false; if (!_disableConnectionLocking) if (_lockObject == null) @@ -5511,15 +5533,15 @@ internal OrganizationResponse Command_Execute(OrganizationRequest req, string er _connectionSvc.CalledbyExecuteRequest = true; OrganizationResponse rsp = null; - // Check to see if a Tracking ID has allready been assigned, + // Check to see if a Tracking ID has allready been assigned, if (!req.RequestId.HasValue || (req.RequestId.HasValue && req.RequestId.Value == Guid.Empty)) { - // Assign Tracking ID + // Assign Tracking ID req.RequestId = requestTrackingId; } else { - // assign request Id to the tracking id. + // assign request Id to the tracking id. requestTrackingId = req.RequestId.Value; } @@ -5621,7 +5643,7 @@ private bool ShouldRetry(OrganizationRequest req, Exception ex, int retryCount, OrgEx.Detail.ErrorCode == _timeLimitExceededErrorCode || OrgEx.Detail.ErrorCode == _concurrencyLimitExceededErrorCode) { - // Error was raised by a instance throttle trigger. + // Error was raised by a instance throttle trigger. if (OrgEx.Detail.ErrorCode == _rateLimitExceededErrorCode) { // Use Retry-After delay when specified @@ -5686,15 +5708,15 @@ private void LogFailure(OrganizationRequest req, Guid requestTrackingId, TimeSpa var errorMessage = DataverseTraceLogger.GetFirstLineFromString(contentBody["error"]["message"].ToString()).Trim(); _logEntry.Log(string.Format(CultureInfo.InvariantCulture, "{6}Failed to Execute Command - {0}{1} : {5}RequestID={2} {3}: {8} duration={4} ExceptionMessage = {7}", - webUriMessageReq, - _disableConnectionLocking ? " : DisableCrossThreadSafeties=true :" : string.Empty, - requestTrackingId.ToString(), - string.Empty, + webUriMessageReq, + _disableConnectionLocking ? " : DisableCrossThreadSafeties=true :" : string.Empty, + requestTrackingId.ToString(), + string.Empty, logDt.Elapsed.ToString(), - SessionTrackingId.HasValue && SessionTrackingId.Value != Guid.Empty ? $"SessionID={SessionTrackingId.Value.ToString()} : " : "", - isTerminalFailure ? "[TerminalFailure] " : "", - errorMessage, - errorStringCheck), + SessionTrackingId.HasValue && SessionTrackingId.Value != Guid.Empty ? $"SessionID={SessionTrackingId.Value.ToString()} : " : "", + isTerminalFailure ? "[TerminalFailure] " : "", + errorMessage, + errorStringCheck), TraceEventType.Error, ex); } } @@ -5756,16 +5778,16 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr /// /// Makes a web request to the connected XRM instance. /// - /// Here you would pass the path and query parameters that you wish to pass onto the WebAPI. + /// Here you would pass the path and query parameters that you wish to pass onto the WebAPI. /// The format used here is as follows: - /// {APIURI}/api/data/v{instance version}/querystring. - /// For example, - /// if you wanted to get data back from an account, you would pass the following: + /// {APIURI}/api/data/v{instance version}/querystring. + /// For example, + /// if you wanted to get data back from an account, you would pass the following: /// accounts(id) /// which creates: get - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts(id) /// if you were creating an account, you would pass the following: - /// accounts - /// which creates: post - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts - body contains the data. + /// accounts + /// which creates: post - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts - body contains the data. /// /// Http Method you want to pass. /// Content your passing to the request @@ -5784,14 +5806,14 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr requestTrackingId = Guid.NewGuid(); - // Default Odata 4.0 headers. + // Default Odata 4.0 headers. Dictionary defaultODataHeaders = new Dictionary(); defaultODataHeaders.Add("Accept", "application/json"); defaultODataHeaders.Add("OData-MaxVersion", "4.0"); defaultODataHeaders.Add("OData-Version", "4.0"); //defaultODataHeaders.Add("If-None-Match", ""); - // Supported Version Check. + // Supported Version Check. if (!(Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(ConnectedOrgVersion , Utilities.FeatureVersionMinimums.WebAPISupported))) { _logEntry.Log(string.Format("Web API Service is not supported by the ServiceClient in {0} version of XRM", ConnectedOrgVersion), TraceEventType.Error, new InvalidOperationException(string.Format("Web API Service is not supported by the ServiceClient in {0} version of XRM", ConnectedOrgVersion))); @@ -5817,7 +5839,7 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr return null; } - // Add Headers. + // Add Headers. if (customHeaders == null) customHeaders = new Dictionary>(); else @@ -5841,7 +5863,7 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr } } - // Add Default headers. + // Add Default headers. foreach (var hdr in defaultODataHeaders) { if (customHeaders.ContainsKey(hdr.Key)) @@ -5850,7 +5872,7 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr customHeaders.Add(hdr.Key, new List() { hdr.Value }); } - // Add headers. + // Add headers. if (CallerId != Guid.Empty) { { @@ -5861,7 +5883,7 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr { if (CallerAADObjectId.HasValue) { - // Value in Caller object ID. + // Value in Caller object ID. if (CallerAADObjectId.Value != null && CallerAADObjectId.Value != Guid.Empty) { customHeaders.Add(Utilities.RequestHeaders.AAD_CALLER_OBJECT_ID_HTTP_HEADER, new List() { CallerAADObjectId.ToString() }); @@ -5869,7 +5891,7 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr } } - // Add authorization header. + // Add authorization header. if (!customHeaders.ContainsKey(Utilities.RequestHeaders.AUTHORIZATION_HEADER)) customHeaders.Add(Utilities.RequestHeaders.AUTHORIZATION_HEADER, new List() { string.Format("Bearer {0}", await _connectionSvc.RefreshWebProxyClientTokenAsync()) }); @@ -5888,7 +5910,7 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr if (guTempId == Guid.Empty) // passed in value did not parse. { - // Assign Tracking Guid in + // Assign Tracking Guid in customHeaders.Remove(Utilities.RequestHeaders.X_MS_CLIENT_REQUEST_ID); customHeaders.Add(Utilities.RequestHeaders.X_MS_CLIENT_REQUEST_ID, new List() { requestTrackingId.ToString() }); } @@ -5896,18 +5918,18 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr requestTrackingId = guTempId; } - // Session id. + // Session id. if (SessionTrackingId.HasValue && SessionTrackingId != Guid.Empty && !customHeaders.ContainsKey(Utilities.RequestHeaders.X_MS_CLIENT_SESSION_ID)) customHeaders.Add(Utilities.RequestHeaders.X_MS_CLIENT_SESSION_ID, new List() { SessionTrackingId.Value.ToString() }); - // Add force Consistency + // Add force Consistency if (ForceServerMetadataCacheConsistency && !customHeaders.ContainsKey(Utilities.RequestHeaders.FORCE_CONSISTENCY)) customHeaders.Add(Utilities.RequestHeaders.FORCE_CONSISTENCY, new List() { "Strong" }); HttpResponseMessage resp = null; do { - logDt.Restart(); // start clock. + logDt.Restart(); // start clock. _logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Execute Command - {0}{1}: RequestID={2} {3}", $"{method} {queryString}", @@ -5918,15 +5940,15 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr try { resp = await ConnectionService.ExecuteHttpRequestAsync( - TargetUri.ToString(), - method, - body: body, - customHeaders: customHeaders, - logSink: _logEntry, - contentType: contentType, - requestTrackingId: requestTrackingId, - sessionTrackingId: SessionTrackingId.HasValue ? SessionTrackingId.Value : Guid.Empty, - suppressDebugMessage:true , + TargetUri.ToString(), + method, + body: body, + customHeaders: customHeaders, + logSink: _logEntry, + contentType: contentType, + requestTrackingId: requestTrackingId, + sessionTrackingId: SessionTrackingId.HasValue ? SessionTrackingId.Value : Guid.Empty, + suppressDebugMessage:true , providedHttpClient: _connectionSvc.WebApiHttpClient == null ? ClientServiceProviders.Instance.GetService().CreateClient("DataverseHttpClientFactory") : _connectionSvc.WebApiHttpClient ).ConfigureAwait(false); @@ -5959,7 +5981,7 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr } else { - retry = false; + retry = false; _logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Failed to Execute Command - {3} {0} : {2}RequestID={1}", queryString, requestTrackingId.ToString(), SessionTrackingId.HasValue && SessionTrackingId.Value != Guid.Empty ? $"SessionID={SessionTrackingId.Value.ToString()} : " : "", method), TraceEventType.Verbose); _logEntry.Log(string.Format(CultureInfo.InvariantCulture, "************ Exception - {2} : {0} |=> {1}", errorStringCheck, ex.Message, queryString), TraceEventType.Error, ex); return null; @@ -6001,20 +6023,20 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, out bool isThrottli // || ((((RetrieveMultipleRequest)req).Query is QueryExpression) && Utilities.ShouldAutoRetryRetrieveByEntityName(((QueryExpression)((RetrieveMultipleRequest)req).Query).EntityName)) // ))) // return true; - //else + //else if (errorCode.Equals("-2147204784") || errorCode.Equals("-2146233087") && errorMessage.Contains("SQL")) return true; else if (httpOperationException.Response.StatusCode == HttpStatusCode.BadGateway) return true; else if (httpOperationException.Response.StatusCode == HttpStatusCode.ServiceUnavailable) { - _retryPauseTimeRunning = _retryPauseTime; // default timespan. - isThrottlingRetry = true; + _retryPauseTimeRunning = _retryPauseTime; // default timespan. + isThrottlingRetry = true; } else if ((int)httpOperationException.Response.StatusCode == 429 || httpOperationException.Response.StatusCode == HttpStatusCode.ServiceUnavailable) { - // Throttled. need to react according. + // Throttled. need to react according. if (errorCode == _rateLimitExceededErrorCode.ToString() || errorCode == _timeLimitExceededErrorCode.ToString() || errorCode == _concurrencyLimitExceededErrorCode.ToString()) @@ -6025,7 +6047,7 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, out bool isThrottli if (httpOperationException.Response.Headers.ContainsKey("Retry-After")) _retryPauseTimeRunning = TimeSpan.Parse(httpOperationException.Response.Headers["Retry-After"].FirstOrDefault()); else - _retryPauseTimeRunning = _retryPauseTime; // default timespan. + _retryPauseTimeRunning = _retryPauseTime; // default timespan. } else { @@ -6040,7 +6062,7 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, out bool isThrottli else return false; - return false; + return false; } @@ -6081,7 +6103,7 @@ public PickListMetaElement() } /// - /// Constructs a PickList item with data. + /// Constructs a PickList item with data. /// /// /// @@ -6117,7 +6139,7 @@ public PickListItem() } /// - /// Constructor with data. + /// Constructor with data. /// /// /// @@ -6143,7 +6165,7 @@ public sealed class DataverseSearchFilter public LogicalOperator FilterOperator { get; set; } /// - /// Creates an empty Dataverse Search Filter. + /// Creates an empty Dataverse Search Filter. /// public DataverseSearchFilter() { @@ -6152,7 +6174,7 @@ public DataverseSearchFilter() } /// - /// Dataverse Filter item. + /// Dataverse Filter item. /// public sealed class DataverseFilterConditionItem { @@ -6187,7 +6209,7 @@ public sealed class ImportRequest /// public ImportMode Mode { get; set; } - // Import Map Items. + // Import Map Items. /// /// ID of the DataMap to use /// @@ -6199,12 +6221,12 @@ public sealed class ImportRequest public string DataMapFileName { get; set; } /// - /// if True, infers the map from the type of entity requested.. + /// if True, infers the map from the type of entity requested.. /// public bool UseSystemMap { get; set; } /// - /// List of files to import in this job, there must be at least one. + /// List of files to import in this job, there must be at least one. /// public List Files { get; set; } @@ -6237,7 +6259,7 @@ public ImportRequest() } /// - /// Describes an Individual Import Item. + /// Describes an Individual Import Item. /// public class ImportFileItem { @@ -6258,11 +6280,11 @@ public class ImportFileItem /// public bool EnableDuplicateDetection { get; set; } /// - /// Name of the entity that Originated the data. + /// Name of the entity that Originated the data. /// public string SourceEntityName { get; set; } /// - /// Name of the entity that Target Entity the data. + /// Name of the entity that Target Entity the data. /// public string TargetEntityName { get; set; } /// @@ -6278,7 +6300,7 @@ public class ImportFileItem /// public bool IsFirstRowHeader { get; set; } /// - /// UserID or Team ID of the Record Owner ( from systemuser ) + /// UserID or Team ID of the Record Owner ( from systemuser ) /// public Guid RecordOwner { get; set; } /// @@ -6298,11 +6320,11 @@ public enum DataDelimiterCode /// /// Specifies no delimiter /// - None = 2, // + None = 2, // /// /// Specifies ' /// - SingleQuote = 3 // ' + SingleQuote = 3 // ' } /// @@ -6319,7 +6341,7 @@ public enum FieldDelimiterCode /// Comma = 2, /// - /// Specifies ' + /// Specifies ' /// SingleQuote = 3 } @@ -6342,7 +6364,7 @@ public enum FileTypeCode } /// - /// Logical Search Pram to apply to over all search. + /// Logical Search Pram to apply to over all search. /// public enum LogicalSearchOperator { @@ -6361,7 +6383,7 @@ public enum LogicalSearchOperator } /// - /// Logical Search Pram to apply to over all search. + /// Logical Search Pram to apply to over all search. /// public enum LogicalSortOrder { @@ -6385,7 +6407,7 @@ public enum FormTypeId /// Dashboard = 0, /// - /// Appointment book, for service requests. + /// Appointment book, for service requests. /// AppointmentBook = 1, /// @@ -6433,11 +6455,11 @@ public void Associate(string entityName, Guid entityId, Relationship relationshi /// ID of newly created entity public Guid Create(Entity entity) { - // Relay to Update request. + // Relay to Update request. CreateResponse resp = (CreateResponse)ExecuteOrganizationRequestImpl( - new CreateRequest() - { - Target = entity + new CreateRequest() + { + Target = entity } , "Create To Dataverse via IOrganizationService" , useWebAPI:true); @@ -6448,7 +6470,7 @@ public Guid Create(Entity entity) } /// - /// Issues a Delete request to Dataverse + /// Issues a Delete request to Dataverse /// /// Entity name to delete /// ID if entity to delete @@ -6498,7 +6520,7 @@ public OrganizationResponse Execute(OrganizationRequest request) } /// - /// Issues a Retrieve Request to Dataverse + /// Issues a Retrieve Request to Dataverse /// /// Entity name to request /// ID of the entity to request @@ -6534,16 +6556,16 @@ public EntityCollection RetrieveMultiple(QueryBase query) } /// - /// Issues an update to Dataverse. + /// Issues an update to Dataverse. /// /// Entity to update into Dataverse public void Update(Entity entity) { - // Relay to Update request. + // Relay to Update request. UpdateResponse resp = (UpdateResponse)ExecuteOrganizationRequestImpl( - new UpdateRequest() - { - Target = entity + new UpdateRequest() + { + Target = entity } , "UpdateRequest To Dataverse via IOrganizationService" , useWebAPI:true); @@ -6586,7 +6608,7 @@ public async Task AssociateAsync(string entityName, Guid entityId, Relationship /// The ID of the created record public async Task CreateAsync(Entity entity, CancellationToken cancellationToken = default) { - // Relay to Update request. + // Relay to Update request. CreateResponse resp = (CreateResponse) await ExecuteOrganizationRequestAsyncImpl( new CreateRequest() { @@ -6719,7 +6741,7 @@ public async Task RetrieveMultipleAsync(QueryBase query, Cance /// Propagates notification that operations should be canceled. public async Task UpdateAsync(Entity entity, CancellationToken cancellationToken = default) { - // Relay to Update request. + // Relay to Update request. UpdateResponse resp = (UpdateResponse) await ExecuteOrganizationRequestAsyncImpl( new UpdateRequest() { @@ -6773,7 +6795,7 @@ private void Dispose(bool disposing) /// - /// Disposed the resources used by the ServiceClient. + /// Disposed the resources used by the ServiceClient. /// public void Dispose() { diff --git a/src/GeneralTools/DataverseClient/Client/TraceLoggerBase.cs b/src/GeneralTools/DataverseClient/Client/TraceLoggerBase.cs index f6f58c6..e2a2fe0 100644 --- a/src/GeneralTools/DataverseClient/Client/TraceLoggerBase.cs +++ b/src/GeneralTools/DataverseClient/Client/TraceLoggerBase.cs @@ -9,7 +9,7 @@ namespace Microsoft.PowerPlatform.Dataverse.Client { /// - /// TraceLoggerBase Class. + /// TraceLoggerBase Class. /// [LocalizableAttribute(false)] #pragma warning disable CA1063 // Implement IDisposable Correctly @@ -35,7 +35,7 @@ public abstract class TraceLoggerBase : IDisposable private Exception _lastException = null; private TraceSource _source; - + #endregion #region Protected fields @@ -62,7 +62,7 @@ protected TraceSource Source System.Diagnostics.EventLog.WriteEntry("application", errMsg, System.Diagnostics.EventLogEntryType.Error); #endif } - catch + catch { //error in writing to event log string log = string.Format("UNABLE TO WRITE TO EVENT LOG FOR: {0}", errMsg); @@ -109,7 +109,7 @@ public string LastError set { _lastError = value; } } /// - /// Last Exception from CRM + /// Last Exception from CRM /// public Exception LastException { @@ -118,7 +118,7 @@ public Exception LastException } /// - /// Current Trace level + /// Current Trace level /// public SourceLevels CurrentTraceLevel { @@ -137,7 +137,7 @@ protected TraceLoggerBase() } /// - /// Initialize Trace Source + /// Initialize Trace Source /// protected void Initialize() { @@ -148,20 +148,20 @@ protected void Initialize() RefreshListeners(TraceSourceSettingStore.TraceSourceSettingsCollection); } } - + /// /// Reset the last Stored Error /// public abstract void ResetLastError(); /// - /// Log a Message as an Information event. + /// Log a Message as an Information event. /// /// public abstract void Log(string message); /// - /// Log a Trace event + /// Log a Trace event /// /// /// @@ -203,13 +203,13 @@ public void RefreshListeners(List traceSourceSettingCollecti #region IDisposable Support /// - /// + /// /// #pragma warning disable CA1063 // Implement IDisposable Correctly public void Dispose() #pragma warning restore CA1063 // Implement IDisposable Correctly { - // Always need this to be called. + // Always need this to be called. TraceListenerBroker.UnRegisterTraceLogger(this); } #endregion diff --git a/src/GeneralTools/DataverseClient/Client/Utils/DataverseConnectionStringProcessor.cs b/src/GeneralTools/DataverseClient/Client/Utils/DataverseConnectionStringProcessor.cs index c3ff5ec..503c378 100644 --- a/src/GeneralTools/DataverseClient/Client/Utils/DataverseConnectionStringProcessor.cs +++ b/src/GeneralTools/DataverseClient/Client/Utils/DataverseConnectionStringProcessor.cs @@ -9,12 +9,13 @@ using System.ServiceModel.Description; using Microsoft.PowerPlatform.Dataverse.Client.Model; using Microsoft.PowerPlatform.Dataverse.Client.Auth; +using Microsoft.Extensions.Logging; namespace Microsoft.PowerPlatform.Dataverse.Client { /// - /// Stores Parsed connection info from the use of a CDS connection string. - /// This is only populated when the CDS Connection string object is used, this is read only. + /// Stores Parsed connection info from the use of a CDS connection string. + /// This is only populated when the CDS Connection string object is used, this is read only. /// internal class DataverseConnectionStringProcessor { @@ -120,7 +121,7 @@ internal string Password internal string CertThumbprint { get; set; } /// - /// if set to true, then the org URI should be used directly. + /// if set to true, then the org URI should be used directly. /// internal bool SkipDiscovery { get; set; } @@ -188,12 +189,12 @@ public string TokenCacheStorePath } /// - /// When true, specifies a unique instance of the connection should be created. + /// When true, specifies a unique instance of the connection should be created. /// public bool UseUniqueConnectionInstance { get; internal set; } /// - /// When set to true and oAuth Mode ( not Cert ) attempts to run the login using the current user identity. + /// When set to true and oAuth Mode ( not Cert ) attempts to run the login using the current user identity. /// public bool UseCurrentUser { get; set; } @@ -201,7 +202,7 @@ public DataverseConnectionStringProcessor() { } - private DataverseConnectionStringProcessor(IDictionary connection) + private DataverseConnectionStringProcessor(IDictionary connection , ILogger logger) : this( connection.FirstNotNullOrEmpty(ConnectionStringConstants.ServiceUri), connection.FirstNotNullOrEmpty(ConnectionStringConstants.UserName), @@ -218,12 +219,13 @@ private DataverseConnectionStringProcessor(IDictionary connectio connection.FirstNotNullOrEmpty(ConnectionStringConstants.CertThumbprint), connection.FirstNotNullOrEmpty(ConnectionStringConstants.SkipDiscovery), connection.FirstNotNullOrEmpty(ConnectionStringConstants.IntegratedSecurity), - connection.FirstNotNullOrEmpty(ConnectionStringConstants.ClientSecret) + connection.FirstNotNullOrEmpty(ConnectionStringConstants.ClientSecret), + logger ) { } private DataverseConnectionStringProcessor(string serviceUri, string userName, string password, string domain, string homeRealmUri, string authType, string requireNewInstance, string clientId, string redirectUri, - string tokenCacheStorePath, string loginPrompt, string certStoreName, string certThumbprint, string skipDiscovery, string IntegratedSecurity , string clientSecret) + string tokenCacheStorePath, string loginPrompt, string certStoreName, string certThumbprint, string skipDiscovery, string IntegratedSecurity , string clientSecret , ILogger logger) { DataverseTraceLogger logEntry = new DataverseTraceLogger(); Uri _serviceuriName, _realmUri; @@ -232,7 +234,7 @@ private DataverseConnectionStringProcessor(string serviceUri, string userName, s if (bool.TryParse(skipDiscovery, out tempbool)) SkipDiscovery = tempbool; else - SkipDiscovery = true; // changed to change defaulting behavior of skip discovery. + SkipDiscovery = true; // changed to change defaulting behavior of skip discovery. ServiceUri = GetValidUri(serviceUri, out _serviceuriName) ? _serviceuriName : null; @@ -247,16 +249,16 @@ private DataverseConnectionStringProcessor(string serviceUri, string userName, s CertStoreName = certStoreName; CertThumbprint = certThumbprint; - // Check to see if use current user is configured. + // Check to see if use current user is configured. bool _IntegratedSecurity = false; if (!string.IsNullOrEmpty(IntegratedSecurity)) bool.TryParse(IntegratedSecurity, out _IntegratedSecurity); - bool useUniqueConnection = true; // Set default to true to follow the old behavior. + bool useUniqueConnection = true; // Set default to true to follow the old behavior. if (!string.IsNullOrEmpty(requireNewInstance)) bool.TryParse(requireNewInstance, out useUniqueConnection); UseUniqueConnectionInstance = useUniqueConnection; - + //UserIdentifier = !string.IsNullOrWhiteSpace(UserId) ? new UserIdentifier(UserId, UserIdentifierType.OptionalDisplayableId) : null; AuthenticationType authenticationType; @@ -322,7 +324,7 @@ private bool GetValidUri(string uriSource, out Uri validUriResult) /// private void SetOrgnameAndOnlineRegion(Uri serviceUri) { - // uses publicaly exposed connection parser to parse + // uses publicaly exposed connection parser to parse string orgRegion = string.Empty; string orgName = string.Empty; bool isOnPrem = false; @@ -334,13 +336,14 @@ private void SetOrgnameAndOnlineRegion(Uri serviceUri) /// - /// Parse the connection sting + /// Parse the connection sting /// /// + /// Logging provider /// - public static DataverseConnectionStringProcessor Parse(string connectionString ) + public static DataverseConnectionStringProcessor Parse(string connectionString , ILogger logger = null) { - return new DataverseConnectionStringProcessor(connectionString.ToDictionary()); + return new DataverseConnectionStringProcessor(connectionString.ToDictionary(), logger); } } diff --git a/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs b/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs index 05d2107..87ce4d5 100644 --- a/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs +++ b/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs @@ -101,15 +101,15 @@ public static void GetOrgnameAndOnlineRegionFromServiceUri(Uri serviceUri, out s // Determine deployment region from Uri List elements = new List(serviceUri.Host.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries)); organizationName = elements[0]; - elements.RemoveAt(0); // remove the first ( org name ) from the Uri. + elements.RemoveAt(0); // remove the first ( org name ) from the Uri. - // construct Prospective CRM Online path. + // construct Prospective CRM Online path. System.Text.StringBuilder buildPath = new System.Text.StringBuilder(); foreach (var item in elements) { if (item.Equals("api")) - continue; // Skip the .api. when running via this path. + continue; // Skip the .api. when running via this path. buildPath.AppendFormat("{0}.", item); } string crmKey = buildPath.ToString().TrimEnd('.').TrimEnd('/'); @@ -118,7 +118,7 @@ public static void GetOrgnameAndOnlineRegionFromServiceUri(Uri serviceUri, out s { using (DiscoveryServers discoSvcs = new DiscoveryServers()) { - // drop in the discovery region if it can be determined. if not, default to scanning. + // drop in the discovery region if it can be determined. if not, default to scanning. var locatedDiscoServer = discoSvcs.OSDPServers.Where(w => w.DiscoveryServerUri != null && w.DiscoveryServerUri.Host.Contains(crmKey)).FirstOrDefault(); if (locatedDiscoServer != null && !string.IsNullOrEmpty(locatedDiscoServer.ShortName)) onlineRegion = locatedDiscoServer.ShortName; @@ -142,7 +142,7 @@ public static void GetOrgnameAndOnlineRegionFromServiceUri(Uri serviceUri, out s } /// - /// returns ( if possible ) the org detail for a given organization name from the list of orgs in discovery + /// returns ( if possible ) the org detail for a given organization name from the list of orgs in discovery /// /// OrgList to Parse though /// Name to find @@ -153,7 +153,7 @@ public static OrgByServer DeterminOrgDataFromOrgInfo(OrgList orgList, string org if (orgDetail == null) orgDetail = orgList.OrgsList.Where(o => o.OrgDetail.FriendlyName.Equals(organizationName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault(); - // still not found... try by URI name. + // still not found... try by URI name. if (orgDetail == null) { string formatedOrgName = string.Format("://{0}.", organizationName).ToLowerInvariant(); @@ -163,7 +163,7 @@ public static OrgByServer DeterminOrgDataFromOrgInfo(OrgList orgList, string org } /// - /// returns ( if possible ) the org detail for a given organization name from the list of orgs in discovery + /// returns ( if possible ) the org detail for a given organization name from the list of orgs in discovery /// /// OrgList to Parse though /// Name to find @@ -174,7 +174,7 @@ public static OrganizationDetail DeterminOrgDataFromOrgInfo(OrganizationDetailCo if (orgDetail == null) orgDetail = orgList.Where(o => o.FriendlyName.Equals(organizationName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault(); - // still not found... try by URI name. + // still not found... try by URI name. if (orgDetail == null) { string formatedOrgName = string.Format("://{0}.", organizationName).ToLowerInvariant(); @@ -184,7 +184,7 @@ public static OrganizationDetail DeterminOrgDataFromOrgInfo(OrganizationDetailCo } /// - /// Parses an OrgURI to determine what the supporting discovery server is. + /// Parses an OrgURI to determine what the supporting discovery server is. /// /// Service Uri to parse /// Geo Code for region (Optional) @@ -195,7 +195,7 @@ public static DiscoveryServer DeterminDiscoveryDataFromOrgDetail(Uri serviceUri, //support for detecting a Live/Online URI in the path and rerouting to use that.. if (IsValidOnlineHost(serviceUri)) { - // Check for Geo code and to make sure that the region is not on our internal list. + // Check for Geo code and to make sure that the region is not on our internal list. if (!string.IsNullOrEmpty(Geo) && !(serviceUri.Host.ToUpperInvariant().Contains("CRMLIVETIE.COM") || serviceUri.Host.ToUpperInvariant().Contains("CRMLIVETODAY.COM")) @@ -203,7 +203,7 @@ public static DiscoveryServer DeterminDiscoveryDataFromOrgDetail(Uri serviceUri, { using (DiscoveryServers discoSvcs = new DiscoveryServers()) { - // Find by Geo, if null fall though to next check + // Find by Geo, if null fall though to next check var locatedDiscoServer = discoSvcs.OSDPServers.Where(w => !string.IsNullOrEmpty(w.GeoCode) && w.GeoCode == Geo).FirstOrDefault(); if (locatedDiscoServer != null && !string.IsNullOrEmpty(locatedDiscoServer.ShortName)) return locatedDiscoServer; @@ -216,15 +216,15 @@ public static DiscoveryServer DeterminDiscoveryDataFromOrgDetail(Uri serviceUri, // Determine deployment region from Uri List elements = new List(serviceUri.Host.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries)); - elements.RemoveAt(0); // remove the first ( org name ) from the Uri. + elements.RemoveAt(0); // remove the first ( org name ) from the Uri. - // construct Prospective Dataverse Online path. + // construct Prospective Dataverse Online path. System.Text.StringBuilder buildPath = new System.Text.StringBuilder(); foreach (var item in elements) { if (item.Equals("api")) - continue; // Skip the .api. when running via this path. + continue; // Skip the .api. when running via this path. buildPath.AppendFormat("{0}.", item); } string crmKey = buildPath.ToString().TrimEnd('.').TrimEnd('/'); @@ -233,7 +233,7 @@ public static DiscoveryServer DeterminDiscoveryDataFromOrgDetail(Uri serviceUri, { using (DiscoveryServers discoSvcs = new DiscoveryServers()) { - // drop in the discovery region if it can be determined. if not, default to scanning. + // drop in the discovery region if it can be determined. if not, default to scanning. var locatedDiscoServer = discoSvcs.OSDPServers.Where(w => w.DiscoveryServerUri != null && w.DiscoveryServerUri.Host.Contains(crmKey)).FirstOrDefault(); if (locatedDiscoServer != null && !string.IsNullOrEmpty(locatedDiscoServer.ShortName)) return locatedDiscoServer; @@ -269,7 +269,7 @@ public static bool IsValidOnlineHost(Uri hostUri) || hostUri.DnsSafeHost.ToUpperInvariant().Contains("CRMLIVETIE.COM") || hostUri.DnsSafeHost.ToUpperInvariant().Contains("CRMLIVETODAY.COM")) #else - if (hostUri.DnsSafeHost.ToUpperInvariant().Contains("DYNAMICS.COM") + if (hostUri.DnsSafeHost.ToUpperInvariant().Contains("DYNAMICS.COM") || hostUri.DnsSafeHost.ToUpperInvariant().Contains("MICROSOFTDYNAMICS.DE") || hostUri.DnsSafeHost.ToUpperInvariant().Contains("MICROSOFTDYNAMICS.US") || hostUri.DnsSafeHost.ToUpperInvariant().Contains("APPSPLATFORM.US") @@ -287,7 +287,7 @@ public static bool IsValidOnlineHost(Uri hostUri) /// /// Determines if the request type can be translated to WebAPI - /// This is a temp method to support the staged transition to the webAPI and will be removed or reintegrated with the overall pipeline at some point in the future. + /// This is a temp method to support the staged transition to the webAPI and will be removed or reintegrated with the overall pipeline at some point in the future. /// /// /// @@ -301,20 +301,20 @@ internal static bool IsRequestValidForTranslationToWebAPI(OrganizationRequest re case "delete": return true; case "upsert": - // Disabling WebAPI support for upsert right now due to issues with generating the response. + // Disabling WebAPI support for upsert right now due to issues with generating the response. // avoid bug in WebAPI around Support for key's as EntityRefeances //TODO: TEMP //Xrm.Sdk.Messages.UpsertRequest upsert = (Xrm.Sdk.Messages.UpsertRequest)req; //if (upsert.Target.KeyAttributes?.Any(a => a.Value is string) != true) // return false; //else - //return true; + //return true; default: return false; } } /// - /// Parses an attribute array into a object that can be used to create a JSON request. + /// Parses an attribute array into a object that can be used to create a JSON request. /// /// Entity to process /// @@ -323,14 +323,14 @@ internal static ExpandoObject ToExpandoObject(Entity sourceEntity , MetadataUtil { dynamic expando = new ExpandoObject(); - // Check for primary Id info: + // Check for primary Id info: if (sourceEntity.Id != Guid.Empty) - sourceEntity = UpdateEntityAttributesForPrimaryId(sourceEntity, mUtil); + sourceEntity = UpdateEntityAttributesForPrimaryId(sourceEntity, mUtil); - AttributeCollection entityAttributes = sourceEntity.Attributes; + AttributeCollection entityAttributes = sourceEntity.Attributes; if (!(entityAttributes != null) && (entityAttributes.Count > 0 )) { - return expando; + return expando; } var expandoObject = (IDictionary)expando; @@ -346,11 +346,11 @@ internal static ExpandoObject ToExpandoObject(Entity sourceEntity , MetadataUtil var key = keyValuePair.Key; if (value is EntityReference entityReference) { - // Get Lookup attribute meta data for the ER to check for polymorphic relationship. + // Get Lookup attribute meta data for the ER to check for polymorphic relationship. var attributeInfo = mUtil.GetAttributeMetadata(sourceEntity.LogicalName, key.ToLower()); if (attributeInfo is Xrm.Sdk.Metadata.LookupAttributeMetadata attribData) { - // Now get relationship to make sure we use the correct name. + // Now get relationship to make sure we use the correct name. var eData = mUtil.GetEntityMetadata(Xrm.Sdk.Metadata.EntityFilters.Relationships, sourceEntity.LogicalName); var ERNavName = eData.ManyToOneRelationships.FirstOrDefault(w => w.ReferencingAttribute.Equals(attribData.LogicalName) && w.ReferencedEntity.Equals(entityReference.LogicalName)) @@ -363,7 +363,7 @@ internal static ExpandoObject ToExpandoObject(Entity sourceEntity , MetadataUtil } else if (attributeInfo == null) { - // Fault here. + // Fault here. throw new DataverseOperationException($"Entity Reference {key.ToLower()} was not found for entity {sourceEntity.LogicalName}.", null); } @@ -385,14 +385,14 @@ internal static ExpandoObject ToExpandoObject(Entity sourceEntity , MetadataUtil { if (value is EntityCollection) { - // try to get the participation type id from the key. + // try to get the participation type id from the key. int PartyTypeId = PartyListHelper.GetParticipationtypeMasks(key); - bool isActivityParty = PartyTypeId != -1; // if the partytypeID is -1 this is not a activity party collection. + bool isActivityParty = PartyTypeId != -1; // if the partytypeID is -1 this is not a activity party collection. if (isActivityParty && partiesCollection == null) - partiesCollection = new List(); // Only build it when needed. + partiesCollection = new List(); // Only build it when needed. - // build linked collection here. + // build linked collection here. foreach (var ent in (value as EntityCollection).Entities) { ExpandoObject rslt = ToExpandoObject(ent, mUtil); @@ -406,7 +406,7 @@ internal static ExpandoObject ToExpandoObject(Entity sourceEntity , MetadataUtil if (isActivityParty) continue; - // Note.. if this is not an activity party but instead an embedded entity.. this will fall though and fail with trying to embed an entity. + // Note.. if this is not an activity party but instead an embedded entity.. this will fall though and fail with trying to embed an entity. } else { @@ -460,7 +460,7 @@ internal static ExpandoObject ToExpandoObject(Entity sourceEntity , MetadataUtil expandoObject.Add(key, value); } - // Check to see if this contained an activity party + // Check to see if this contained an activity party if (partiesCollection?.Count > 0) { expandoObject.Add($"{sourceEntity.LogicalName}_activity_parties", partiesCollection); @@ -471,8 +471,8 @@ internal static ExpandoObject ToExpandoObject(Entity sourceEntity , MetadataUtil } /// - /// checks to see if an attribute has been added to the collection containing the ID of the entity . - /// this is required for the WebAPI to properly function. + /// checks to see if an attribute has been added to the collection containing the ID of the entity . + /// this is required for the WebAPI to properly function. /// /// /// @@ -482,9 +482,9 @@ private static Entity UpdateEntityAttributesForPrimaryId(Entity sourceEntity, Me if ( sourceEntity.Id != Guid.Empty ) { var entMeta = mUtil.GetEntityMetadata(sourceEntity.LogicalName); - sourceEntity.Attributes[entMeta.PrimaryIdAttribute] = sourceEntity.Id; + sourceEntity.Attributes[entMeta.PrimaryIdAttribute] = sourceEntity.Id; } - return sourceEntity; + return sourceEntity; } /// @@ -498,12 +498,12 @@ private static Entity UpdateEntityAttributesForPrimaryId(Entity sourceEntity, Me internal static ExpandoObject ReleatedEntitiesToExpandoObject(ExpandoObject rootExpando, string entityName, RelatedEntityCollection entityCollection, MetadataUtility mUtil) { if (rootExpando == null) - return rootExpando; + return rootExpando; if ( entityCollection != null && entityCollection.Count == 0 ) { - // nothing to do, just return. - return rootExpando; + // nothing to do, just return. + return rootExpando; } foreach (var entItem in entityCollection) @@ -519,7 +519,7 @@ internal static ExpandoObject ReleatedEntitiesToExpandoObject(ExpandoObject root // Get the Entity relationship key and entity and reverse it back to the entity key name var eData = mUtil.GetEntityMetadata(Xrm.Sdk.Metadata.EntityFilters.Relationships, entItem.Value.Entities[0].LogicalName); - // Find the relationship that is referenced. + // Find the relationship that is referenced. var ERM21 = eData.ManyToOneRelationships.FirstOrDefault(w1 => w1.SchemaName.ToLower().Equals(entItem.Key.SchemaName.ToLower())); var ERM2M = eData.ManyToManyRelationships.FirstOrDefault(w2 => w2.SchemaName.ToLower().Equals(entItem.Key.SchemaName.ToLower())); var ER12M = eData.OneToManyRelationships.FirstOrDefault(w3 => w3.SchemaName.ToLower().Equals(entItem.Key.SchemaName.ToLower())); @@ -527,7 +527,7 @@ internal static ExpandoObject ReleatedEntitiesToExpandoObject(ExpandoObject root // Determine which one hit if (ERM21 != null) { - isArrayRequired = true; + isArrayRequired = true; key = ERM21.ReferencedEntityNavigationPropertyName; } else if (ERM2M != null) @@ -547,20 +547,20 @@ internal static ExpandoObject ReleatedEntitiesToExpandoObject(ExpandoObject root key = ER12M.ReferencingAttribute; } - if ( string.IsNullOrEmpty(key) ) // Failed to find key + if ( string.IsNullOrEmpty(key) ) // Failed to find key { throw new DataverseOperationException($"Relationship key {entItem.Key.SchemaName} cannot be found for related entities of {entityName}."); } foreach (var ent in entItem.Value.Entities) { - // Check to see if the entity itself has related entities + // Check to see if the entity itself has related entities if (ent.RelatedEntities != null && ent.RelatedEntities.Count > 0) { childEntities = ReleatedEntitiesToExpandoObject(childEntities, entityName, ent.RelatedEntities, mUtil); } - // generate object. + // generate object. ExpandoObject ent1 = ToExpandoObject(ent, mUtil); if (((IDictionary)childEntities).Count() > 0) @@ -577,11 +577,11 @@ internal static ExpandoObject ReleatedEntitiesToExpandoObject(ExpandoObject root else ((IDictionary)rootExpando).Add(key, childCollection); } - return rootExpando; + return rootExpando; } /// - /// Parses Key attribute collection for alt key support. + /// Parses Key attribute collection for alt key support. /// /// alt key's for object /// webAPI compliant key string @@ -599,15 +599,15 @@ internal static string ParseAltKeyCollection(KeyAttributeCollection keyValues) keycollection += $"{itm.Key}='{itm.Value}',"; } } - return keycollection.Remove(keycollection.Length - 1); // remove trailing , + return keycollection.Remove(keycollection.Length - 1); // remove trailing , } /// - /// List of entities to retry retrieves on. + /// List of entities to retry retrieves on. /// private static List _autoRetryRetrieveEntityList = null; /// - /// if the Incoming query has an entity on the retry list, returns true. else returns false. + /// if the Incoming query has an entity on the retry list, returns true. else returns false. /// /// string containing entity name to check against /// true if found, false if not @@ -628,7 +628,7 @@ internal static bool ShouldAutoRetryRetrieveByEntityName(string queryStringToPar } /// - /// Creates or Adds scopes and returns the current scope + /// Creates or Adds scopes and returns the current scope /// /// /// @@ -657,19 +657,19 @@ internal static class RequestHeaders /// public static readonly string USER_AGENT_HTTP_HEADER = "User-Agent"; /// - /// Session ID used to track all operations associated with a given group of calls. + /// Session ID used to track all operations associated with a given group of calls. /// public static readonly string X_MS_CLIENT_SESSION_ID = "x-ms-client-session-id"; /// - /// PerRequest ID used to track a specific request. + /// PerRequest ID used to track a specific request. /// public static readonly string X_MS_CLIENT_REQUEST_ID = "x-ms-client-request-id"; /// - /// Content type of WebAPI request. + /// Content type of WebAPI request. /// public static readonly string CONTENT_TYPE = "Content-Type"; /// - /// Header loaded with the AADObjectID of the user to impersonate + /// Header loaded with the AADObjectID of the user to impersonate /// public static readonly string AAD_CALLER_OBJECT_ID_HTTP_HEADER = "CallerObjectId"; /// @@ -681,11 +681,11 @@ internal static class RequestHeaders /// public static readonly string AUTHORIZATION_HEADER = "Authorization"; /// - /// Header requesting the connection be kept alive. + /// Header requesting the connection be kept alive. /// public static readonly string CONNECTION_KEEP_ALIVE = "Keep-Alive"; /// - /// Header requiring Cache Consistency Server side. + /// Header requiring Cache Consistency Server side. /// public static readonly string FORCE_CONSISTENCY = "Consistency"; @@ -695,25 +695,25 @@ internal static class RequestHeaders public const string BYPASSCUSTOMPLUGINEXECUTION = "BypassCustomPluginExecution"; /// - /// key used to apply the operation to a given solution. + /// key used to apply the operation to a given solution. /// See: https://docs.microsoft.com/powerapps/developer/common-data-service/org-service/use-messages#passing-optional-parameters-with-a-request /// public const string SOLUTIONUNIQUENAME = "SolutionUniqueName"; /// - /// used to apply duplicate detection behavior to a given request. + /// used to apply duplicate detection behavior to a given request. /// See: https://docs.microsoft.com/powerapps/developer/common-data-service/org-service/use-messages#passing-optional-parameters-with-a-request /// public const string SUPPRESSDUPLICATEDETECTION = "SuppressDuplicateDetection"; /// - /// used to pass data though Dataverse to a plugin or downstream system on a request. + /// used to pass data though Dataverse to a plugin or downstream system on a request. /// See: https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/org-service/use-messages#add-a-shared-variable-from-the-organization-service /// public const string TAG = "tag"; /// - /// used to identify concurrencybehavior property in an organization request. + /// used to identify concurrencybehavior property in an organization request. /// public const string CONCURRENCYBEHAVIOR = "ConcurrencyBehavior"; @@ -725,12 +725,12 @@ internal static class RequestHeaders } /// - /// Minim Version numbers for various features of Dataverse API's. + /// Minim Version numbers for various features of Dataverse API's. /// internal static class FeatureVersionMinimums { /// - /// returns true of the feature version is valid for this environment. + /// returns true of the feature version is valid for this environment. /// /// Instance version of the Dataverse Instance /// MinFeatureVersion @@ -740,21 +740,21 @@ internal static bool IsFeatureValidForEnviroment ( Version instanceVersion , Ver if (instanceVersion != null && (instanceVersion >= featureVersion)) return true; else - return false; + return false; } /// - /// Lowest server version that can be connected too. + /// Lowest server version that can be connected too. /// internal static Version DataverseVersionForThisAPI = new Version("5.0.9688.1533"); /// - /// Minimum version that supports batch Operations. + /// Minimum version that supports batch Operations. /// internal static Version BatchOperations = new Version("5.0.9690.3000"); /// - /// Minimum version that supports holding solutions. + /// Minimum version that supports holding solutions. /// internal static Version ImportHoldingSolution = new Version("7.2.0.9"); @@ -764,37 +764,37 @@ internal static bool IsFeatureValidForEnviroment ( Version instanceVersion , Ver internal static Version InternalUpgradeSolution = new Version("9.0.0.0"); /// - /// MinVersion that supports AAD Caller ID. + /// MinVersion that supports AAD Caller ID. /// internal static Version AADCallerIDSupported = new Version("8.1.0.0"); /// - /// MinVersion that supports Session ID Telemetry Tracking. + /// MinVersion that supports Session ID Telemetry Tracking. /// internal static Version SessionTrackingSupported = new Version("9.0.2.0"); /// - /// MinVersion that supports Forcing Cache Sync. + /// MinVersion that supports Forcing Cache Sync. /// internal static Version ForceConsistencySupported = new Version("9.1.0.0"); /// - /// Minimum version to allow plug in bypass param. + /// Minimum version to allow plug in bypass param. /// internal static Version AllowBypassCustomPlugin = new Version("9.1.0.20918"); /// - /// Minimum version supported by the Web API + /// Minimum version supported by the Web API /// internal static Version WebAPISupported = new Version("8.0.0.0"); /// - /// Minimum version supported for AsyncRibbonProcessing. + /// Minimum version supported for AsyncRibbonProcessing. /// internal static Version AllowAsyncRibbonProcessing = new Version("9.1.0.15400"); /// - /// Minimum version supported for Passing Component data to Dataverse as part of solution deployment.. + /// Minimum version supported for Passing Component data to Dataverse as part of solution deployment.. /// internal static Version AllowComponetInfoProcessing = new Version("9.1.0.16547"); } diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs index f30cf7d..08c7fc4 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs +++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using System.Linq; using Xunit; using Microsoft.Xrm.Sdk; using Moq; @@ -30,6 +30,7 @@ public partial class ClientTests TestSupport testSupport = new TestSupport(); ITestOutputHelper outputListner; + ILogger Ilogger = null; #endregion public ClientTests(ITestOutputHelper output) @@ -40,6 +41,14 @@ public ClientTests(ITestOutputHelper output) TraceConsoleSupport traceConsoleSupport = new TraceConsoleSupport(outputListner); TraceControlSettings.CloseListeners(); TraceControlSettings.AddTraceListener(traceConsoleSupport); + + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => + builder.AddConsole(options => + { + options.IncludeScopes = true; + options.TimestampFormat = "hh:mm:ss "; + })); + Ilogger = loggerFactory.CreateLogger(); } [Fact] @@ -52,7 +61,7 @@ public void ExecuteCrmOrganizationRequest() var rsp = (WhoAmIResponse)cli.ExecuteOrganizationRequest(new WhoAmIRequest()); - // Validate that the user ID sent in is the UserID that comes out. + // Validate that the user ID sent in is the UserID that comes out. Assert.Equal(rsp.UserId, testSupport._UserId); } @@ -82,7 +91,7 @@ public void LogWriteTest() ILogger Ilogger = loggerFactory.CreateLogger(); DataverseTraceLogger logger = new DataverseTraceLogger(Ilogger); - logger.EnabledInMemoryLogCapture = true; + logger.EnabledInMemoryLogCapture = true; logger.Log("TEST INFO MESSAGE"); logger.Log("TEST WARNING MESSAGE", TraceEventType.Warning); @@ -91,7 +100,7 @@ public void LogWriteTest() logger.Log("TEST CRITICAL MESSAGE", TraceEventType.Critical); - // error throw. + // error throw. Microsoft.Rest.HttpOperationException operationException = new Microsoft.Rest.HttpOperationException("HTTPOPEXC"); HttpResponseMessage Resp500 = new HttpResponseMessage(System.Net.HttpStatusCode.ServiceUnavailable); Resp500.Headers.Add("REQ_ID", "39393F77-8F8B-4416-846E-28B4D2AA5667"); @@ -112,7 +121,7 @@ public void DeleteRequestTests() testSupport.SetupMockAndSupport(out orgSvc, out fakHttpMethodHander, out cli); - // Setup handlers to deal with both orgRequest and WebAPI request. + // Setup handlers to deal with both orgRequest and WebAPI request. fakHttpMethodHander.Setup(s => s.Send(It.Is(f => f.Method.ToString().Equals("delete", StringComparison.OrdinalIgnoreCase)))).Returns(new HttpResponseMessage(System.Net.HttpStatusCode.OK)); orgSvc.Setup(f => f.Execute(It.Is(p => p.Target.LogicalName.Equals("account") && p.Target.Id.Equals(testSupport._DefaultId)))).Returns(new DeleteResponse()); @@ -133,7 +142,7 @@ public void CreateRequestTests() testSupport.SetupMockAndSupport(out orgSvc, out fakHttpMethodHander, out cli); - // Set up Responses + // Set up Responses CreateResponse testCreate = new CreateResponse(); testCreate.Results.AddOrUpdateIfNotNull("accountid", testSupport._DefaultId); testCreate.Results.AddOrUpdateIfNotNull("id", testSupport._DefaultId); @@ -143,13 +152,13 @@ public void CreateRequestTests() createRespMsg.Headers.Add("Location", $"https://deploymenttarget02.crm.dynamics.com/api/data/v9.1/accounts({testSupport._DefaultId})"); createRespMsg.Headers.Add("OData-EntityId", $"https://deploymenttarget02.crm.dynamics.com/api/data/v9.1/accounts({testSupport._DefaultId})"); - // Setup handlers to deal with both orgRequest and WebAPI request. + // Setup handlers to deal with both orgRequest and WebAPI request. fakHttpMethodHander.Setup(s => s.Send(It.Is(f => f.Method.ToString().Equals("post", StringComparison.OrdinalIgnoreCase)))).Returns(createRespMsg); orgSvc.Setup(f => f.Execute(It.Is(p => p.Target.LogicalName.Equals("account")))).Returns(testCreate); - // Setup request - // use create operation to setup request + // Setup request + // use create operation to setup request Dictionary newFields = new Dictionary(); newFields.Add("name", new DataverseDataTypeWrapper("CrudTestAccount", DataverseFieldType.String)); newFields.Add("dateonlyfield", new DataverseDataTypeWrapper(new DateTime(2000, 01, 01), DataverseFieldType.DateTime)); @@ -188,7 +197,7 @@ public void DataTypeParsingTest() testSupport.SetupMockAndSupport(out orgSvc, out fakHttpMethodHander, out cli); - // Set up Responses + // Set up Responses CreateResponse testCreate = new CreateResponse(); testCreate.Results.AddOrUpdateIfNotNull("accountid", testSupport._DefaultId); testCreate.Results.AddOrUpdateIfNotNull("id", testSupport._DefaultId); @@ -210,14 +219,14 @@ public void DataTypeParsingTest() createRespMsg.Headers.Add("Location", $"https://deploymenttarget02.crm.dynamics.com/api/data/v9.1/accounts({testSupport._DefaultId})"); createRespMsg.Headers.Add("OData-EntityId", $"https://deploymenttarget02.crm.dynamics.com/api/data/v9.1/accounts({testSupport._DefaultId})"); - // Setup handlers to deal with both orgRequest and WebAPI request. + // Setup handlers to deal with both orgRequest and WebAPI request. fakHttpMethodHander.Setup(s => s.Send(It.Is(f => f.Method.ToString().Equals("post", StringComparison.OrdinalIgnoreCase)))).Returns(createRespMsg); orgSvc.Setup(f => f.Execute(It.Is(p => p.Target.LogicalName.Equals("account")))).Returns(testCreate); orgSvc.Setup(f => f.Execute(It.Is(p => p.LogicalName.Equals("field02", StringComparison.OrdinalIgnoreCase) && p.EntityLogicalName.Equals("account", StringComparison.OrdinalIgnoreCase)))).Returns(attribfield02Resp); orgSvc.Setup(f => f.Execute(It.Is(p => p.LogicalName.Equals("field07", StringComparison.OrdinalIgnoreCase) && p.EntityLogicalName.Equals("account", StringComparison.OrdinalIgnoreCase)))).Returns(attribfield07Resp); // Setup request for all datatypes - // use create operation to setup request + // use create operation to setup request Dictionary newFields = new Dictionary(); newFields.Add("name", new DataverseDataTypeWrapper("CrudTestAccount", DataverseFieldType.String)); newFields.Add("Field01", new DataverseDataTypeWrapper(false, DataverseFieldType.Boolean)); @@ -272,7 +281,7 @@ public void GetCurrentUser() var rsp01 = cli.GetMyUserId(); - // Validate that the user ID sent in is the UserID that comes out. + // Validate that the user ID sent in is the UserID that comes out. Assert.Equal(rsp01, testSupport._UserId); } @@ -307,26 +316,26 @@ public void BatchTest() string BatchRequestName = "TestBatch"; Guid batchid = cli.CreateBatchOperationRequest(BatchRequestName); - // use create operation to setup request + // use create operation to setup request Dictionary newFields = new Dictionary(); newFields.Add("name", new DataverseDataTypeWrapper("CrudTestAccount", DataverseFieldType.String)); newFields.Add("accountnumber", new DataverseDataTypeWrapper("12345", DataverseFieldType.String)); newFields.Add("telephone1", new DataverseDataTypeWrapper("555-555-5555", DataverseFieldType.String)); newFields.Add("donotpostalmail", new DataverseDataTypeWrapper(true, DataverseFieldType.Boolean)); - // issue request as a batch: + // issue request as a batch: Guid result = cli.CreateAnnotation("account", testSupport._DefaultId, newFields, batchid); Assert.Equal(Guid.Empty, result); OrganizationRequest req = cli.GetBatchRequestAtPosition(batchid, 0); - // Executes the batch request. + // Executes the batch request. cli.ExecuteBatch(batchid); - // Request Batch by name + // Request Batch by name Guid OperationId = cli.GetBatchOperationIdRequestByName(BatchRequestName); - // Request batch back + // Request batch back RequestBatch reqBatch = cli.GetBatchById(batchid); Assert.NotNull(reqBatch); Assert.Equal(BatchRequestName, reqBatch.BatchName); @@ -413,7 +422,7 @@ public void GetEntityDisplayNameTest() testSupport.SetupMockAndSupport(out orgSvc, out fakHttpMethodHander, out cli); - // Test for plural name + // Test for plural name string response = cli.GetEntityDisplayNamePlural("account"); Assert.Equal("Accounts", response); @@ -421,11 +430,11 @@ public void GetEntityDisplayNameTest() response = cli.GetEntityDisplayNamePlural("account", 1); Assert.Equal("Accounts", response); - // Test for non plural name + // Test for non plural name response = cli.GetEntityDisplayName("account"); Assert.Equal("Account", response); - // Test for non plural name ETC + // Test for non plural name ETC response = cli.GetEntityDisplayName("account", 1); Assert.Equal("Account", response); @@ -546,8 +555,8 @@ public void ConnectUsingServiceIdentity_ClientSecret_CtorV1() var Conn_Secret = System.Environment.GetEnvironmentVariable("XUNITCONNTESTSECRET"); var Conn_Url = System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI"); - // Connection params. - var client = new ServiceClient(new Uri(Conn_Url), Conn_AppID, Conn_Secret, true); + // Connection params. + var client = new ServiceClient(new Uri(Conn_Url), Conn_AppID, Conn_Secret, true , Ilogger); Assert.True(client.IsReady, "Failed to Create Connection via Constructor"); // Validate connection @@ -564,8 +573,8 @@ public void ConnectUsingServiceIdentity_ClientSecret_CtorV2() var Conn_Secret = System.Environment.GetEnvironmentVariable("XUNITCONNTESTSECRET"); var Conn_Url = System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI"); - // connection params + secure string. - var client = new ServiceClient(new Uri(Conn_Url), Conn_AppID, ServiceClient.MakeSecureString(Conn_Secret), true); + // connection params + secure string. + var client = new ServiceClient(new Uri(Conn_Url), Conn_AppID, ServiceClient.MakeSecureString(Conn_Secret), true , logger: Ilogger); Assert.True(client.IsReady, "Failed to Create Connection via Constructor"); // Validate connection @@ -582,9 +591,9 @@ public void ConnectUsingServiceIdentity_ClientSecret_ConStr() var Conn_Secret = System.Environment.GetEnvironmentVariable("XUNITCONNTESTSECRET"); var Conn_Url = System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI"); - // Connection string; + // Connection string; var connStr = $"AuthType=ClientSecret;AppId={Conn_AppID};ClientSecret={Conn_Secret};Url={Conn_Url}"; - var client = new ServiceClient(connStr); + var client = new ServiceClient(connStr, Ilogger); Assert.True(client.IsReady, "Failed to Create Connection via Connection string"); // Check user before we validate connection @@ -616,7 +625,7 @@ public void ConnectUsingUserIdentity_UIDPW_ConStr() // Connection string - Direct connect using Sample ApplicationID's var connStr = $"AuthType=OAuth;Username={Conn_UserName};Password={Conn_PW};Url={Conn_Url};AppId={testSupport._SampleAppID.ToString()};RedirectUri={testSupport._SampleAppRedirect.ToString()};TokenCacheStorePath=c:\\MyTokenCache;LoginPrompt=Never"; - var client = new ServiceClient(connStr); + var client = new ServiceClient(connStr, Ilogger); Assert.True(client.IsReady, "Failed to Create Connection via Connection string"); // Check user before we validate connection @@ -629,7 +638,7 @@ public void ConnectUsingUserIdentity_UIDPW_ConStr() // Validate connection ValidateConnection(client); - // Check user after we validate connection again as it gets it from cached token + // Check user after we validate connection again as it gets it from cached token client._connectionSvc.AuthContext.Account.Username.Should().BeEquivalentTo(Conn_UserName); } @@ -646,7 +655,7 @@ public void ConnectUsingUserIdentity_UIDPW_CtorV1_Discovery() var Conn_PW = System.Environment.GetEnvironmentVariable("XUNITCONNTESTPW"); var Conn_Url = System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI"); - //Connection params. + //Connection params. string onlineRegion = string.Empty; string orgName = string.Empty; bool isOnPrem = false; @@ -671,7 +680,7 @@ public void ConnectUsingUserIdentity_UIDPW_CtorV1_Discovery() // Validate connection ValidateConnection(client); - // Check user after we validate connection again as it gets it from cached token + // Check user after we validate connection again as it gets it from cached token client._connectionSvc.AuthContext.Account.Username.Should().BeEquivalentTo(Conn_UserName); } @@ -688,7 +697,7 @@ public void ConnectUsingUserIdentity_UIDPW_CtorV2() var Conn_PW = System.Environment.GetEnvironmentVariable("XUNITCONNTESTPW"); var Conn_Url = System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI"); - // Connection params. + // Connection params. var client = new ServiceClient(Conn_UserName, ServiceClient.MakeSecureString(Conn_PW), new Uri(Conn_Url), true, DataverseConnectionStringProcessor.sampleClientId, new Uri(DataverseConnectionStringProcessor.sampleRedirectUrl), PromptBehavior.Never); Assert.True(client.IsReady, "Failed to Create Connection via Constructor - Direct Connect"); @@ -702,7 +711,7 @@ public void ConnectUsingUserIdentity_UIDPW_CtorV2() // Validate connection ValidateConnection(client); - // Check user after we validate connection again as it gets it from cached token + // Check user after we validate connection again as it gets it from cached token client._connectionSvc.AuthContext.Account.Username.Should().BeEquivalentTo(Conn_UserName); } #region connectionValidationHelper @@ -711,18 +720,27 @@ private void ValidateConnection(ServiceClient client) { client._connectionSvc.AuthContext.Should().NotBeNull(); - // Validate it + // Validate it var rslt = client.Execute(new WhoAmIRequest()); Assert.IsType(rslt); + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => + builder.AddConsole(options => + { + options.IncludeScopes = true; + options.TimestampFormat = "hh:mm:ss "; + })); + + ILogger locallogger = loggerFactory.CreateLogger(); + locallogger.BeginScope("Beginning CloneLogger"); // Clone it. - Validate use - using (var client2 = client.Clone()) + using (var client2 = client.Clone(locallogger)) { rslt = client2.Execute(new WhoAmIRequest()); Assert.IsType(rslt); } - // Create clone chain an break linkage. + // Create clone chain an break linkage. var client3 = client.Clone(); var client4 = client3.Clone(); rslt = client3.Execute(new WhoAmIRequest()); @@ -741,7 +759,7 @@ private void ValidateConnection(ServiceClient client) [Trait("Category", "Live Connect Required")] public void RelatedEntityLiveTest() { - // Live test is required due to support on server side being required to parse various configurations. + // Live test is required due to support on server side being required to parse various configurations. // use ClientSecretConnection System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; @@ -749,8 +767,8 @@ public void RelatedEntityLiveTest() var Conn_Secret = System.Environment.GetEnvironmentVariable("XUNITCONNTESTSECRET"); var Conn_Url = System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI"); - // connection params + secure string. - var client = new ServiceClient(new Uri(Conn_Url), Conn_AppID, ServiceClient.MakeSecureString(Conn_Secret), true); + // connection params + secure string. + var client = new ServiceClient(new Uri(Conn_Url), Conn_AppID, ServiceClient.MakeSecureString(Conn_Secret), true , logger: Ilogger); Assert.True(client.IsReady, "Failed to Create Connection via Constructor"); @@ -796,7 +814,7 @@ public void RelatedEntityLiveTest() Assert.True(accountid != Guid.Empty); // Now delete - client.Delete("account", accountid); + client.Delete("account", accountid); diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestSupport.cs b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestSupport.cs index 97c0b4c..c971fc3 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestSupport.cs +++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestSupport.cs @@ -28,7 +28,7 @@ public class TestSupport public void SetupMockAndSupport( out Mock moqOrgSvc , out Mock moqHttpHandler , out ServiceClient cdsServiceClient , Version requestedCdsVersion = null) { if (requestedCdsVersion is null) - requestedCdsVersion = new Version("9.1.2.0"); + requestedCdsVersion = new Version("9.1.2.0"); var orgSvc = new Mock(); var fakHttpMethodHander = new Mock { CallBase = true }; @@ -39,14 +39,14 @@ public void SetupMockAndSupport( out Mock moqOrgSvc , out moqOrgSvc = orgSvc; moqHttpHandler = fakHttpMethodHander; - cdsServiceClient = cli; + cdsServiceClient = cli; } #endregion #region PreSetupResponses private void SetupWhoAmIHandlers(Mock orgSvc) { - // Who Am I Response + // Who Am I Response var whoAmIResponse = new WhoAmIResponse(); whoAmIResponse.Results = new ParameterCollection(); whoAmIResponse.Results.Add("UserId", _UserId); @@ -126,7 +126,7 @@ private void SetupMetadataHandlersForAccount(Mock orgSvc) ReferencingAttribute = "field07", ReferencedEntity = "account", ReferencingEntityNavigationPropertyName = "field07account" - } + } }; System.Reflection.PropertyInfo proInfo = entityMetadata.GetType().GetProperty("ManyToOneRelationships"); @@ -140,7 +140,7 @@ private void SetupMetadataHandlersForAccount(Mock orgSvc) { proInfo1.SetValue(entityMetadata, 1, null); } - + System.Reflection.PropertyInfo proInfo3 = entityMetadata.GetType().GetProperty("Attributes"); if (proInfo3 != null) { diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt index e77b2d2..2206178 100644 --- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt +++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt @@ -1,46 +1,49 @@ -Notice: +Notice: This package is in a public ALPHA release. This package is intended to work with .net full framework 4.6.2, 4.7.2 and 4.8, .net core 3.0, 3.1 and 5.0 We have not stabilized on NameSpace or Class names with this package as of yet and things will change as we move though the preview. - General Documentation is the same as CrmServiceClient and can be found here: + General Documentation is the same as CrmServiceClient and can be found here: https://docs.microsoft.com/en-us/dotnet/api/microsoft.xrm.tooling.connector.crmserviceclient?view=dynamics-xrmtooling-ce-9 - Connection String Docs can be found here: + Connection String Docs can be found here: https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/xrm-tooling/use-connection-strings-xrm-tooling-connect - Note: that only OAuth, Certificate, ClientSecret Authentication types are supported at this time. + Note: that only OAuth, Certificate, ClientSecret Authentication types are supported at this time. ++CURRENTRELEASEID++ -Added support for related entity create as part of general "create" operation. +Exposing ILogger support on all constructors and Static API commands. + +0.4.4: +Added support for related entity create as part of general "create" operation. Updated Async Operations to return task's for async methods that were returning void's Updated DataverseOperationException to include HttpOperationResponse, where it exists. -Fixed issue with Cloned Connections not refreshing their tokens on token expiration +Fixed issue with Cloned Connections not refreshing their tokens on token expiration Updated Logger to remove string builder property -Added Initial code for ILogger implementation. (Not exposed as of yet but coming soon) -Corrected an issue when using the Entity myEnt = new Entity(string, guid) not properly setting the primary id. +Added Initial code for ILogger implementation. (Not exposed as of yet but coming soon) +Corrected an issue when using the Entity myEnt = new Entity(string, guid) not properly setting the primary id. 0.4.1: -Supersedes Microsoft.PowerPlatform.Cds.Client, Previous nuget has been retired. +Supersedes Microsoft.PowerPlatform.Cds.Client, Previous nuget has been retired. ***** MAJOR Breaking Changes ***** -Complete renaming of CdsServiceClient for Dataverse naming. +Complete renaming of CdsServiceClient for Dataverse naming. Major differences: Namespace: Microsoft.PowerPlatform.Cds.Client => Microsoft.PowerPlatform.Dataverse.Client CdsServiceClient is now ServiceClient All methods in CdsServiceClient that had Cds in the name have been updated to remove cds Name - Exception's have been updated to - CdsConnectionException => DataverseConnectionException + Exception's have been updated to + CdsConnectionException => DataverseConnectionException CdsOperationException => DataverseOperationException 0.3.10-Alpha: Initial async implementation of SDK Client. - note this is a WORK IN PROGRESS and may not work as expected under all circumstances. Please report bugs or behaviors issues in the normal way -Fixes and issue with exceptions being thrown by code that is supposed to log exceptions. -Corrected issue with DateOnly data types when using CUD events. -Included additions to logging when failures occur during connection string based login. - credit to GIT:@nicknow for the suggestion of fix. +Fixes and issue with exceptions being thrown by code that is supposed to log exceptions. +Corrected issue with DateOnly data types when using CUD events. +Included additions to logging when failures occur during connection string based login. - credit to GIT:@nicknow for the suggestion of fix. 0.3.6-Alpha: Fixed an issue where Activity Party types were not being properly processed -Fixed an issue where the incorrect format was used for Data only types. -Fixed an issue where BypassPluginExecution property on request was not being understood properly by the server. +Fixed an issue where the incorrect format was used for Data only types. +Fixed an issue where BypassPluginExecution property on request was not being understood properly by the server. 0.3.1-Alpha ***** Breaking Changes ***** @@ -48,117 +51,117 @@ Fixed an issue where BypassPluginExecution property on request was not being und MSAL Port completed. Auth processes have been ported to MSAL. UserID/PW flows are now enabled on .net core for login where possible ( per AAD security rules and host OS) - Auth flow Logging capture is still under development. + Auth flow Logging capture is still under development. Authentication constructors are now updated to remove some ADAL.net patterns Removed Token Cache path from constructors and ignore on connection string at this time - Token Cache handle support will be revisited, however if you have a pressing need, we recommend you use the external token management constructor in the mean time and self manage that. - For now, only MSAL In-Memory support will be provided for token cache -Refactored to support Shared HTTPClientFactory for WebAPI connections to cut down on some noise for folks that are doing repeated logins. -Refactored code a bit to help with clarity on various functional areas. -Renamed/Refactored Discovery methods to as Async methods. + Token Cache handle support will be revisited, however if you have a pressing need, we recommend you use the external token management constructor in the mean time and self manage that. + For now, only MSAL In-Memory support will be provided for token cache +Refactored to support Shared HTTPClientFactory for WebAPI connections to cut down on some noise for folks that are doing repeated logins. +Refactored code a bit to help with clarity on various functional areas. +Renamed/Refactored Discovery methods to as Async methods. 0.2.31-Alpha: -Rerouted all Upsert Request to use OrganizationService API. -Added new InMemory Logging Options to support returning Logs captured either as Array of Tuples or a string list. +Rerouted all Upsert Request to use OrganizationService API. +Added new InMemory Logging Options to support returning Logs captured either as Array of Tuples or a string list. Added new public property for OrganizationDetail Information called "OrganizationDetail" for the currently connected environment. Added new enum for ImportSolution additional property options called "ImportSolutionProperties" - this contains valid options for additional properties for the ImportSolution handler Fixed an issue with telemetry for the client that was using incorrect format for useragent content -Updated Core CDS Sdk assemblies +Updated Core CDS Sdk assemblies 0.2.24-Alpha: Fixed an issue with .Clone not correctly supporting adding telemetry handlers to cloned connections 0.2.23-Alpha: Adding Switzerland Geo to Regions List. -Added support for Alternate key use on entity references as part of Create Update Delete Operations running over the webAPI. -Added concurrency support to Create Update Delete Operations running over the webAPI. +Added support for Alternate key use on entity references as part of Create Update Delete Operations running over the webAPI. +Added concurrency support to Create Update Delete Operations running over the webAPI. 0.2.17-Alpha: ***BREAKING CHANGE*** Renamed Exception CdsConnectionException to CdsClientConnectionException. No other change to the Exception *** -Improved handling of cached connection retrieval failures from memory when an invalid object is returned. +Improved handling of cached connection retrieval failures from memory when an invalid object is returned. Fixed a bug where a WebAPI navigation property did not match with its metadata descriptor. Fixed Retry delay issue when making calls to the CDS WebAPI – thanks to GIT user stvonolf for the catch of this issue. Added new Exception, CdsClientOperationException - this will be used when an exception is raised via the CDSClient when CDSClient is using WebAPI. Added IntelliSense Doc Support -Added support for bypassing custom Plug-in Execution during SDK Operation. - This is a special use capability that requires a specialized permission in the CDS infrastructure to use. +Added support for bypassing custom Plug-in Execution during SDK Operation. + This is a special use capability that requires a specialized permission in the CDS infrastructure to use. Currently this is only permitted for use by users that have the System Administrator Security Role. 0.2.16-Alpha: -Fixed issue when CUD via WebAPI when entity contains a entity reference that is a polymorphic reference. ( IE customer data type ) +Fixed issue when CUD via WebAPI when entity contains a entity reference that is a polymorphic reference. ( IE customer data type ) 0.2.15-Alpha: -Corrected a bug in Org request to WebAPI operation when attempting to set fields to null values. -Added initial support for in memory log capture support. +Corrected a bug in Org request to WebAPI operation when attempting to set fields to null values. +Added initial support for in memory log capture support. 0.2.14-Alpha: Update to internal support libraries 0.2.8-Alpha: -Fixed an issue with sovereign cloud discovery reference data that would prevent a future login control from picking the correct global discovery server. +Fixed an issue with sovereign cloud discovery reference data that would prevent a future login control from picking the correct global discovery server. -0.2.7-Alpha: -Fixed missing ConfigureAwait on ADAL Authority acquire flow. -Added some additional logging to understand what version of ADAL is being used. -Fixed an issue with translation of Money types to WebAPI requests. +0.2.7-Alpha: +Fixed missing ConfigureAwait on ADAL Authority acquire flow. +Added some additional logging to understand what version of ADAL is being used. +Fixed an issue with translation of Money types to WebAPI requests. 0.2.5-Alpha: Fixed Authority property data loss post clone process Added temp logic to run upsert requests that have anything other than string keys via the Native OrgAPI vs webAPI, this is a workaround for an open issue with the webAPI where it does not support none string key's on upsert(patch) requests that result in a create operation. - Rerouted CdsServiceClient Helpers to use WebAPI path for CUD requests. + Rerouted CdsServiceClient Helpers to use WebAPI path for CUD requests. -0.2.2-Alpha: -Several updates to Create / Update / Delete operations to move the underlying features over to use the WebAPI. -Please report any bugs that you run into with this. This has been "mostly" tested with our current test suite, however we have not full tested all data type translation at this point. +0.2.2-Alpha: +Several updates to Create / Update / Delete operations to move the underlying features over to use the WebAPI. +Please report any bugs that you run into with this. This has been "mostly" tested with our current test suite, however we have not full tested all data type translation at this point. -0.2.1-Alpha: +0.2.1-Alpha: BREAKING CHANGE!!! Altering Nuget Version to 3 digit semantic version Namespace change from Microsoft.Powerplatform.xxxx to Microsoft.PowerPlatform.xxxx -Fixed an error with setting CallerId when ClientSecret Auth type is used. - Thanks for all the bug reports on this and repro's that really helped us run it down. +Fixed an error with setting CallerId when ClientSecret Auth type is used. + Thanks for all the bug reports on this and repro's that really helped us run it down. 0.0.1.11-Alpha: -Fixed a number of discovery related issue that were broken during the port. -Added new forms of DiscoveryGlobalOrganziations to allow for a token function to be passed to enabled this for use in WebSites and functions where it makes sense. +Fixed a number of discovery related issue that were broken during the port. +Added new forms of DiscoveryGlobalOrganziations to allow for a token function to be passed to enabled this for use in WebSites and functions where it makes sense. Updated several sovereign GEO's configs to reflect use of Global discovery for those GEOs -Fixed a bug in LinQ create methods that would appear on non-windows deployments. - This would manifest as a missing method exception when trying to create a sequential guid. +Fixed a bug in LinQ create methods that would appear on non-windows deployments. + This would manifest as a missing method exception when trying to create a sequential guid. 0.0.1.9-Alpha: Fixes a bug in using ExecuteCdsWebRequest that blocked connections using clientSecretAuth types. 0.0.1.8-Alpha: -Removed IOverrideAuthHookWrapper and property and replaced with a new constructor form. - This capability was replaced with a new constructor that accepts a function pointer as a parameter to the constructor. +Removed IOverrideAuthHookWrapper and property and replaced with a new constructor form. + This capability was replaced with a new constructor that accepts a function pointer as a parameter to the constructor. This user provided function takes the form of string FunctionName (string InstanceUri), When called, the current instance URI will be - passed in and the function is expected to return an access token for access to that instance. + passed in and the function is expected to return an access token for access to that instance. This form allows an given instance of the CdsServiceClient to be associated with a Auth Processor vs having the auth processor at the process level. Fixed a bug reported in .clone in .net core builds. When cloning a connection in .net core, the system would think you were using an invalid auth type. Known issues: - None at this time.. Please report any issues on the GitHub Issues site. + None at this time.. Please report any issues on the GitHub Issues site. 0.0.1.7-Alpha Fixed a Nuget Package dependency issue in Microsoft.Dynamics.Sdk.Messages 0.0.1.6-Alpha -Intial Alpha release of Microsoft.Cds.Client.CdsServiceClient - WARNING: This is an ALPHA release. +Intial Alpha release of Microsoft.Cds.Client.CdsServiceClient + WARNING: This is an ALPHA release. This library is a rename and renamespace of the Microsoft.Xrm.Tooling.Connector.CrmSeriviceClient. If you are familiar with the CrmServiceClient, By Renaming each occurrence of Crm to Cds, you should find that API's match up. Major changes from CrmServiceClient: - This library does not support User Interactive or User Password Auth flows in .net core. - This library does not support AD or ADFS login to OnPremise, - to use against an onPrem instance of CDS, your instance must be configured with + This library does not support User Interactive or User Password Auth flows in .net core. + This library does not support AD or ADFS login to OnPremise, + to use against an onPrem instance of CDS, your instance must be configured with ADFS + OAuth and the application ID you wish to use registered. - This library removes several Dynamics specific helper methods from CrmServiceClient. + This library removes several Dynamics specific helper methods from CrmServiceClient. this additional methods can be found by include the nuget package Microsoft.Cds.Client.Dynamics - +