diff --git a/src/Build.Shared.props b/src/Build.Shared.props index bac9f20..4a0fd60 100644 --- a/src/Build.Shared.props +++ b/src/Build.Shared.props @@ -3,8 +3,8 @@ 2.9.1 3.19.8 - 4.5.4487 - 4.5.4487 + 4.6.875-weekly-2011.2 + 4.6.875-weekly-2011.2 10.0.3 2.3.20 9.0.2.25 @@ -33,7 +33,7 @@ - $(RepoRoot)\build\crmKey\35MSSharedLib1024.snk + $(RepoRoot)\build\crmkey\35MSSharedLib1024.snk true diff --git a/src/GeneralTools/CDSClient/Client/CdsConnectionService.cs b/src/GeneralTools/CDSClient/Client/CdsConnectionService.cs index d5a5bce..bc7a095 100644 --- a/src/GeneralTools/CDSClient/Client/CdsConnectionService.cs +++ b/src/GeneralTools/CDSClient/Client/CdsConnectionService.cs @@ -309,6 +309,11 @@ internal System.Net.NetworkCredential CdsServiceAccessCredential /// internal string InternetProtocalToUse { get { return _InternetProtocalToUse; } set { _InternetProtocalToUse = value; } } + /// + /// returns the connected organization detail object. + /// + internal OrganizationDetail ConnectedOrganizationDetail { get { return _OrgDetail; } } + /// /// /// @@ -1438,11 +1443,14 @@ internal void SetClonedProperties(CdsServiceClient sourceClient) } // Add User Agent and request id to send. - string Agent = string.Empty; + string Agent = "Unknown"; if (AppDomain.CurrentDomain != null) { Agent = AppDomain.CurrentDomain.FriendlyName; } + Agent = $"{Agent} (CdsSvcClient:{Environs.FileVersion})"; + + if (!_httpRequest.Headers.Contains(Utilities.CDSRequestHeaders.USER_AGENT_HTTP_HEADER)) _httpRequest.Headers.TryAddWithoutValidation(Utilities.CDSRequestHeaders.USER_AGENT_HTTP_HEADER, string.IsNullOrEmpty(Agent) ? "" : Agent); diff --git a/src/GeneralTools/CDSClient/Client/CdsServiceClient.cs b/src/GeneralTools/CDSClient/Client/CdsServiceClient.cs index 2e80f2e..eb978ff 100644 --- a/src/GeneralTools/CDSClient/Client/CdsServiceClient.cs +++ b/src/GeneralTools/CDSClient/Client/CdsServiceClient.cs @@ -88,31 +88,6 @@ public sealed class CdsServiceClient : IOrganizationService, IDisposable private bool _disableConnectionLocking = false; - /// - /// MinVersion that supports AAD Caller ID. - /// - private Version _minAADCallerIDSupportedVersion = new Version("8.1.0.0"); - - /// - /// MinVersion that supports Session ID Telemetry Tracking. - /// - private Version _minSessionTrackingSupportedVersion = new Version("9.0.2.0"); - - /// - /// MinVersion that supports Forcing Cache Sync. - /// - private Version _minForceConsistencySupportedVersion = new Version("9.1.0.0"); - - /// - /// Minimum version to allow plugin bypass param. - /// - private Version _minAllowBypassCustomPluginVersion = new Version("9.1.0.20918"); - - /// - /// Minimum version supported by the Web API - /// - private Version _minWebAPISupportedVersion = new Version("8.0.0.0"); - /// /// SDK Version property backer. /// @@ -147,16 +122,6 @@ public sealed class CdsServiceClient : IOrganizationService, IDisposable /// private const int ConcurrencyLimitExceededErrorCode = -2147015898; - /// - /// Parameter used to change the default layering behavior during solution import - /// - private const string DESIREDLAYERORDERPARAM = "DesiredLayerOrder"; - - /// - /// Parameter used to pass the solution name - Telemetry only - /// - private const string SOLUTIONNAMEPARAM = "SolutionName"; - /// /// Internal Organization Service Interface used for Testing /// @@ -402,6 +367,11 @@ internal WhoAmIResponse _SystemUser /// public EndpointCollection ConnectedOrgPublishedEndpoints { get { if (CdsConnectionSvc != null) return CdsConnectionSvc.ConnectedOrgPublishedEndpoints; else return null; } } + /// + /// OrganizationDetails for the currently connected environment. + /// + public OrganizationDetail OrganizationDetail { get { if (CdsConnectionSvc != null) return CdsConnectionSvc.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 CDS queries via Linq to lock the connection /// @@ -463,7 +433,7 @@ public Guid? CallerAADObjectId } set { - if (CdsConnectionSvc != null && CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= _minAADCallerIDSupportedVersion)) + if (CdsConnectionSvc != null && CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= Utilities.CDSFeatureVersionMinimums.AADCallerIDSupported)) CdsConnectionSvc.CallerAADObjectId = value; else { @@ -495,7 +465,7 @@ public Guid? SessionTrackingId set { - if (CdsConnectionSvc != null && CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= _minSessionTrackingSupportedVersion)) + if (CdsConnectionSvc != null && CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= Utilities.CDSFeatureVersionMinimums.SessionTrackingSupported)) CdsConnectionSvc.SessionTrackingId = value; else { @@ -527,7 +497,7 @@ public bool ForceServerMetadataCacheConsistency } set { - if (CdsConnectionSvc != null && CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= _minForceConsistencySupportedVersion)) + if (CdsConnectionSvc != null && CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= Utilities.CDSFeatureVersionMinimums.ForceConsistencySupported)) CdsConnectionSvc.ForceServerCacheConsistency = value; else { @@ -1054,8 +1024,7 @@ internal void CreateCdsServiceConnection( if (fv != null) { Version fileVersion = new Version(fv.ProductVersion); - Version minVersion = new Version("5.0.9688.1533"); - if (!(fileVersion >= minVersion)) + if (!(fileVersion >= Utilities.CDSFeatureVersionMinimums.CDSVersionForThisAPI)) { logEntry.Log("!!WARNING!!! The version of the CDS product assemblies is less than the recommend version for this API; you must use version 5.0.9688.1533 or newer (Newer then the Oct-2011 service release)", TraceEventType.Warning); logEntry.Log(string.Format(CultureInfo.InvariantCulture, "CDS Version found is {0}", fv.ProductVersion), TraceEventType.Warning); @@ -1137,8 +1106,7 @@ internal void CreateCdsServiceConnection( CACHOBJECNAME = CdsConnectionSvc.ServiceCACHEName + ".LookupCache"; // Min supported version for batch operations. - Version minVersion = new Version("5.0.9690.3000"); - if (CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= minVersion)) + if (CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= Utilities.CDSFeatureVersionMinimums.BatchOperations)) _BatchManager = new BatchManager(logEntry); else logEntry.Log("Batch System disabled, CDS Server does not support this message call", TraceEventType.Information); @@ -1164,13 +1132,19 @@ internal void CreateCdsServiceConnection( /// public IEnumerable> GetAllLogs() { - IEnumerable> source1 = logEntry == null ? Enumerable.Empty>() : logEntry.Logs; - IEnumerable> source2 = this.CdsConnectionSvc == null - ? Enumerable.Empty>() - : this.CdsConnectionSvc.GetAllLogs(); + var source1 = logEntry == null ? Enumerable.Empty>() : logEntry.Logs; + var source2 = CdsConnectionSvc == null ? Enumerable.Empty>(): CdsConnectionSvc.GetAllLogs(); return source1.Union(source2); } + /// + /// Enabled only if InMemoryLogCollectionEnabled is true. + /// Return all logs currently stored for the cdsserviceclient in queue in string list format with [UTCDateTime][LogEntry]. + /// + public string[] GetAllLogsAsStringList() + { + return GetAllLogs().OrderBy(x => x.Item1).Select(x => $"[{x.Item1:yyyy-MM-dd HH:mm:ss:fffffff}]{x.Item2}").ToArray(); + } /// /// Clone, 'Clones" the current CDS Service client with a new connection to CDS. @@ -4818,7 +4792,7 @@ internal bool AddRequestToBatch(Guid batchId, OrganizationRequest req, string ba if (batchId != Guid.Empty) { // if request should bypass plugin exec. - if (bypassPluginExecution && ConnectedOrgVersion >= _minAllowBypassCustomPluginVersion) + if (bypassPluginExecution && ConnectedOrgVersion >= Utilities.CDSFeatureVersionMinimums.AllowBypassCustomPlugin) req.Parameters.Add(Utilities.CDSRequestHeaders.BYPASSCUSTOMPLUGINEXECUTION, "true"); if (IsBatchOperationsAvailable) @@ -5088,7 +5062,7 @@ private OrganizationResponse CdsCommand_WebAPIProcess_Execute(OrganizationReques } } - if (bypassPluginExecution && ConnectedOrgVersion >= _minAllowBypassCustomPluginVersion) + if (bypassPluginExecution && ConnectedOrgVersion >= Utilities.CDSFeatureVersionMinimums.AllowBypassCustomPlugin) { headers.Add($"{Utilities.CDSRequestHeaders.CDSHEADERPROPERTYPREFIX}{Utilities.CDSRequestHeaders.BYPASSCUSTOMPLUGINEXECUTION}", new List() { "true" }); } @@ -5144,7 +5118,13 @@ private OrganizationResponse CdsCommand_WebAPIProcess_Execute(OrganizationReques else if (req is DeleteRequest) { return new DeleteResponse(); - } else + } + else if (req is UpsertRequest) + { + //var upsertReturn = new UpsertResponse(); + return null; + } + else return null; } else @@ -5227,14 +5207,62 @@ internal Guid ImportSolutionToCdsImpl(string solutionPath, out Guid importId, bo return Guid.Empty; } + // determine if the system is connected to OnPrem + bool isConnectedToOnPrem = (CdsConnectionSvc.ConnectedOrganizationDetail != null && string.IsNullOrEmpty(CdsConnectionSvc.ConnectedOrganizationDetail.Geo)); + //Extract extra parameters if they exist string solutionName = string.Empty; LayerDesiredOrder desiredLayerOrder = null; + bool? asyncRibbonProcessing = null; + EntityCollection componetsToProcess = null; if (extraParameters != null) { - solutionName = extraParameters.ContainsKey(SOLUTIONNAMEPARAM) ? extraParameters[SOLUTIONNAMEPARAM].ToString() : string.Empty; - desiredLayerOrder = extraParameters.ContainsKey(DESIREDLAYERORDERPARAM) ? extraParameters[DESIREDLAYERORDERPARAM] as LayerDesiredOrder : null; + solutionName = extraParameters.ContainsKey(ImportSolutionProperties.SOLUTIONNAMEPARAM) ? extraParameters[ImportSolutionProperties.SOLUTIONNAMEPARAM].ToString() : string.Empty; + 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. + 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. + this.logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.ASYNCRIBBONPROCESSING} property. This is not valid for OnPremise deployments and will be removed", TraceEventType.Warning); + asyncRibbonProcessing = null; + } + else + { + if ((CdsConnectionSvc.OrganizationVersion == null) || + (CdsConnectionSvc.OrganizationVersion != null && CdsConnectionSvc.OrganizationVersion < Utilities.CDSFeatureVersionMinimums.AllowAsyncRibbonProcessing)) + { + // Not supported on this version of CDS + this.logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.ASYNCRIBBONPROCESSING} property. This request CDS version {Utilities.CDSFeatureVersionMinimums.AllowAsyncRibbonProcessing.ToString()} or above. This property will be removed", TraceEventType.Warning); + asyncRibbonProcessing = null; + } + } + } + + if (componetsToProcess != null) + { + if (isConnectedToOnPrem) + { + this.logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.COMPONENTPARAMETERSPARAM} property. This is not valid for OnPremise deployments and will be removed", TraceEventType.Warning); + componetsToProcess = null; + } + else + { + if (CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion <= Utilities.CDSFeatureVersionMinimums.AllowComponetInfoProcessing)) + { + // Not supported on this version of CDS + this.logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.COMPONENTPARAMETERSPARAM} property. This request CDS version {Utilities.CDSFeatureVersionMinimums.AllowComponetInfoProcessing.ToString()} or above. This property will be removed", TraceEventType.Warning); + componetsToProcess = null; + } + } + } } string solutionNameForLogging = string.IsNullOrWhiteSpace(solutionName) ? string.Empty : string.Concat(solutionName, " - "); @@ -5267,6 +5295,18 @@ internal Guid ImportSolutionToCdsImpl(string solutionPath, out Guid importId, bo logEntry.Log(string.Format(CultureInfo.InvariantCulture, "{0}DesiredLayerOrder clause present: Type: {1}, Solutions: {2}", solutionNameForLogging, desiredLayerOrder.Type, solutionsInHint), TraceEventType.Verbose); } + if (asyncRibbonProcessing != null && asyncRibbonProcessing == true) + { + SolutionImportRequest.AsyncRibbonProcessing = true; + SolutionImportRequest.SkipQueueRibbonJob = true; + logEntry.Log(string.Format(CultureInfo.InvariantCulture, "{0} AsyncRibbonProcessing: {1}", solutionNameForLogging, true), TraceEventType.Verbose); + } + + if (componetsToProcess != null) + { + SolutionImportRequest.ComponentParameters = componetsToProcess; + } + if (IsBatchOperationsAvailable) { // Support for features added in UR12 @@ -5276,8 +5316,7 @@ internal Guid ImportSolutionToCdsImpl(string solutionPath, out Guid importId, bo if (importAsHoldingSolution) // If Import as Holding is set.. { // Check for Min version of CDS for support of Import as Holding solution. - Version minVersion = new Version("7.2.0.9"); - if (CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= minVersion)) + if (CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= Utilities.CDSFeatureVersionMinimums.ImportHoldingSolution)) { // Use Parameters to add the property here to support the underlying Xrm API on the incorrect version. SolutionImportRequest.Parameters.Add("HoldingSolution", importAsHoldingSolution); @@ -5287,7 +5326,7 @@ internal Guid ImportSolutionToCdsImpl(string solutionPath, out Guid importId, bo // Set IsInternalUpgrade flag on request only for upgrade scenario for V9 only. if (isInternalUpgrade) { - if (CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= new Version("9.0.0.0"))) + if (CdsConnectionSvc.OrganizationVersion != null && (CdsConnectionSvc.OrganizationVersion >= Utilities.CDSFeatureVersionMinimums.InternalUpgradeSolution)) { SolutionImportRequest.Parameters["IsInternalUpgrade"] = true; } @@ -5405,7 +5444,7 @@ internal OrganizationResponse CdsCommand_Execute(OrganizationRequest req, string } // if request should bypass plugin exec. - if (bypassPluginExecution && ConnectedOrgVersion >= _minAllowBypassCustomPluginVersion) + if (bypassPluginExecution && ConnectedOrgVersion >= Utilities.CDSFeatureVersionMinimums.AllowBypassCustomPlugin) req.Parameters.Add(Utilities.CDSRequestHeaders.BYPASSCUSTOMPLUGINEXECUTION, "true"); logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Execute Command - {0}{1}: RequestID={2} {3}", @@ -5667,7 +5706,7 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr //defaultODataHeaders.Add("If-None-Match", ""); // Supported Version Check. - if (!(ConnectedOrgVersion > _minWebAPISupportedVersion)) + if (!(ConnectedOrgVersion > Utilities.CDSFeatureVersionMinimums.WebAPISupported)) { logEntry.Log(string.Format("Web API Service is not supported by the CdsServiceClient in {0} version of XRM", ConnectedOrgVersion), TraceEventType.Error, new InvalidOperationException(string.Format("Web API Service is not supported by the CdsServiceClient in {0} version of XRM", ConnectedOrgVersion))); return null; diff --git a/src/GeneralTools/CDSClient/Client/MetadataUtility.cs b/src/GeneralTools/CDSClient/Client/MetadataUtility.cs index 93b7ff7..3a50b35 100644 --- a/src/GeneralTools/CDSClient/Client/MetadataUtility.cs +++ b/src/GeneralTools/CDSClient/Client/MetadataUtility.cs @@ -3,6 +3,7 @@ using System.Globalization; using Microsoft.Xrm.Sdk.Metadata; using Microsoft.Xrm.Sdk.Messages; +using System.Collections.Concurrent; namespace Microsoft.PowerPlatform.Cds.Client { @@ -15,19 +16,19 @@ internal class MetadataUtility /// /// MetadataCache object. /// - private Dictionary _entityMetadataCache = new Dictionary(); + private ConcurrentDictionary _entityMetadataCache = new ConcurrentDictionary(); /// /// Attribute metadata cache object /// - private Dictionary _attributeMetadataCache = new Dictionary(); + private ConcurrentDictionary _attributeMetadataCache = new ConcurrentDictionary(); /// /// Global option metadata cache object. /// - private Dictionary _globalOptionMetadataCache = new Dictionary(); + private ConcurrentDictionary _globalOptionMetadataCache = new ConcurrentDictionary(); /// /// Entity Name catch object /// - private Dictionary _entityNameCache = new Dictionary(); + private ConcurrentDictionary _entityNameCache = new ConcurrentDictionary(); /// /// Lock object /// @@ -52,13 +53,17 @@ public MetadataUtility(CdsServiceClient svcActions) /// public void ClearCachedEntityMetadata(string entityName) { + TouchMetadataDate(); // Not clearing the ETC ID's as they do not change... - lock (_lockObject) + if (_entityMetadataCache.ContainsKey(entityName)) { - if (_entityMetadataCache.ContainsKey(entityName)) - _entityMetadataCache.Remove(entityName); - if (_attributeMetadataCache.ContainsKey(entityName)) - _attributeMetadataCache.Remove(entityName); + EntityMetadata removedEntData; + _entityMetadataCache.TryRemove(entityName, out removedEntData); + } + if (_attributeMetadataCache.ContainsKey(entityName)) + { + AttributeMetadata removedAttribData; + _attributeMetadataCache.TryRemove(entityName, out removedAttribData); } } @@ -82,21 +87,19 @@ public List GetAllEntityMetadata(bool onlyPublished, EntityFilte { foreach (var entity in response.EntityMetadata) { - lock (_lockObject) - { - if (_entityMetadataCache.ContainsKey(entity.LogicalName)) - _entityMetadataCache[entity.LogicalName] = entity; // Update local copy of the entity... - else - _entityMetadataCache.Add(entity.LogicalName, entity); - - results.Add(entity); - // Preload the entity data catch as this has been called already - if (_entityNameCache.ContainsKey(entity.ObjectTypeCode.Value)) - continue; - else - _entityNameCache.Add(entity.ObjectTypeCode.Value, entity.LogicalName); - } + if (_entityMetadataCache.ContainsKey(entity.LogicalName)) + _entityMetadataCache[entity.LogicalName] = entity; // Update local copy of the entity... + else + _entityMetadataCache.TryAdd(entity.LogicalName, entity); + + results.Add(entity); + // Preload the entity data catch as this has been called already + if (_entityNameCache.ContainsKey(entity.ObjectTypeCode.Value)) + continue; + else + _entityNameCache.TryAdd(entity.ObjectTypeCode.Value, entity.LogicalName); } + TouchMetadataDate(); } return results; @@ -169,16 +172,13 @@ public EntityMetadata GetEntityMetadata(EntityFilters requestType, String entity if (response != null) { entityMetadata = response.EntityMetadata; - lock (_lockObject) - { - if (!_entityMetadataCache.ContainsKey(entityName)) - _entityMetadataCache.Add(entityName, entityMetadata); - else - _entityMetadataCache[entityName] = entityMetadata; + if (!_entityMetadataCache.ContainsKey(entityName)) + _entityMetadataCache.TryAdd(entityName, entityMetadata); + else + _entityMetadataCache[entityName] = entityMetadata; - if (!bSelectiveUpdate) - _metadataLastValidatedAt = DateTime.Now; - } + if (!bSelectiveUpdate) + TouchMetadataDate(); } } return entityMetadata; @@ -204,20 +204,17 @@ public string GetEntityLogicalName(int entityTypeCode) RetrieveAllEntitiesResponse response = (RetrieveAllEntitiesResponse)svcAct.CdsCommand_Execute(request, "GetEntityLogicalName"); if (response != null) { - lock (_lockObject) + foreach (EntityMetadata metadata in response.EntityMetadata) { - foreach (EntityMetadata metadata in response.EntityMetadata) - { - _entityNameCache.Add(metadata.ObjectTypeCode.Value, metadata.LogicalName); - - // reload metadata cache. - if (_entityMetadataCache.ContainsKey(metadata.LogicalName)) - continue; - else - _entityMetadataCache.Add(metadata.LogicalName, metadata); - } - _metadataLastValidatedAt = DateTime.Now; + _entityNameCache.TryAdd(metadata.ObjectTypeCode.Value, metadata.LogicalName); + + // reload metadata cache. + if (_entityMetadataCache.ContainsKey(metadata.LogicalName)) + continue; + else + _entityMetadataCache.TryAdd(metadata.LogicalName, metadata); } + TouchMetadataDate(); } } } @@ -249,11 +246,8 @@ public AttributeMetadata GetAttributeMetadata(string entityName, string attribut if (response != null) { attributeMetadata = response.AttributeMetadata; - lock (_lockObject) - { - _attributeMetadataCache.Add(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", entityName, attributeName), attributeMetadata); - _metadataLastValidatedAt = DateTime.Now; - } + _attributeMetadataCache.TryAdd(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", entityName, attributeName), attributeMetadata); + _metadataLastValidatedAt = DateTime.UtcNow; } } } @@ -333,10 +327,8 @@ public OptionSetMetadata GetGlobalOptionSetMetadata(string optionSetName) { if (response.OptionSetMetadata is OptionSetMetadata && (OptionSetMetadata)response.OptionSetMetadata != null) { - lock (_lockObject) - { - _globalOptionMetadataCache.Add(optionSetName, (OptionSetMetadata)response.OptionSetMetadata); - } + _globalOptionMetadataCache.TryAdd(optionSetName, (OptionSetMetadata)response.OptionSetMetadata); + TouchMetadataDate(); return _globalOptionMetadataCache[optionSetName]; } } @@ -348,17 +340,22 @@ public OptionSetMetadata GetGlobalOptionSetMetadata(string optionSetName) /// private void ValidateMetadata() { - if (DateTime.Now.Subtract(_metadataLastValidatedAt).TotalHours > 1) - { - lock (_lockObject) - { - _metadataLastValidatedAt = DateTime.Now; - _attributeMetadataCache.Clear(); - _entityMetadataCache.Clear(); - _entityNameCache.Clear(); - _globalOptionMetadataCache.Clear(); - } - } - } - } + if (DateTime.UtcNow.Subtract(_metadataLastValidatedAt).TotalHours > 1) + { + TouchMetadataDate(); + _attributeMetadataCache.Clear(); + _entityMetadataCache.Clear(); + _entityNameCache.Clear(); + _globalOptionMetadataCache.Clear(); + } + } + + private void TouchMetadataDate() + { + lock (_lockObject) + { + _metadataLastValidatedAt = DateTime.UtcNow; + } + } + } } diff --git a/src/GeneralTools/CDSClient/Client/Microsoft.Powerplatform.Cds.Client.csproj b/src/GeneralTools/CDSClient/Client/Microsoft.Powerplatform.Cds.Client.csproj index f7126bc..7cb7812 100644 --- a/src/GeneralTools/CDSClient/Client/Microsoft.Powerplatform.Cds.Client.csproj +++ b/src/GeneralTools/CDSClient/Client/Microsoft.Powerplatform.Cds.Client.csproj @@ -6,7 +6,7 @@ true true - + false diff --git a/src/GeneralTools/CDSClient/Client/Utils/ImportSolutionProperties.cs b/src/GeneralTools/CDSClient/Client/Utils/ImportSolutionProperties.cs new file mode 100644 index 0000000..234416d --- /dev/null +++ b/src/GeneralTools/CDSClient/Client/Utils/ImportSolutionProperties.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.PowerPlatform.Cds.Client +{ + /// + /// Properties valid for the extraParameters collection of ImportSolution. + /// + public static class ImportSolutionProperties + { + /// + /// Parameter used to change the default layering behavior during solution import + /// + public static string DESIREDLAYERORDERPARAM = "DesiredLayerOrder"; + /// + /// Parameter used to specify whether Solution Import processed ribbon metadata asynchronously + /// + public static string ASYNCRIBBONPROCESSING = "AsyncRibbonProcessing"; + /// + /// Parameter used to pass the solution name - Telemetry only + /// + public static string SOLUTIONNAMEPARAM = "SolutionName"; + /// + /// Parameter used to pass a collection of component parameters to the import job. + /// + public static string COMPONENTPARAMETERSPARAM = "ComponentParameters"; + } +} diff --git a/src/GeneralTools/CDSClient/Client/Utils/Utils.cs b/src/GeneralTools/CDSClient/Client/Utils/Utils.cs index fba00fa..aeff954 100644 --- a/src/GeneralTools/CDSClient/Client/Utils/Utils.cs +++ b/src/GeneralTools/CDSClient/Client/Utils/Utils.cs @@ -301,12 +301,14 @@ 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. + // 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; + //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; default: return false; } @@ -419,7 +421,7 @@ internal static string ParseAltKeyCollection (KeyAttributeCollection keyValues) { if (itm.Value is EntityReference er) { - keycollection += $"_{itm.Key}_value='{er.Id.ToString("P")}',"; + keycollection += $"_{itm.Key}_value={er.Id.ToString("P")},"; } else { @@ -531,5 +533,67 @@ internal static class CDSRequestHeaders } + /// + /// Minim Version numbers for various features of CDS API's. + /// + internal static class CDSFeatureVersionMinimums + { + /// + /// Lowest server version that can be connected too. + /// + internal static Version CDSVersionForThisAPI = new Version("5.0.9688.1533"); + + /// + /// Minimum version that supports batch Operations. + /// + internal static Version BatchOperations = new Version("5.0.9690.3000"); + + /// + /// Minimum version that supports holding solutions. + /// + internal static Version ImportHoldingSolution = new Version("7.2.0.9"); + + /// + /// Minimum version that supports the Internal Upgrade Flag + /// + internal static Version InternalUpgradeSolution = new Version("9.0.0.0"); + + /// + /// MinVersion that supports AAD Caller ID. + /// + internal static Version AADCallerIDSupported = new Version("8.1.0.0"); + + /// + /// MinVersion that supports Session ID Telemetry Tracking. + /// + internal static Version SessionTrackingSupported = new Version("9.0.2.0"); + + /// + /// MinVersion that supports Forcing Cache Sync. + /// + internal static Version ForceConsistencySupported = new Version("9.1.0.0"); + + /// + /// 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 + /// + internal static Version WebAPISupported = new Version("8.0.0.0"); + + /// + /// Minimum version supported for AsyncRibbonProcessing. + /// + internal static Version AllowAsyncRibbonProcessing = new Version("9.1.0.15400"); + + /// + /// Minimum version supported for Passing Component data to CDS as part of solution deployment.. + /// + internal static Version AllowComponetInfoProcessing = new Version("9.1.0.16547"); + } + + } } diff --git a/src/GeneralTools/CDSClient/Extensions/DynamicsExtension/Microsoft.Powerplatform.Cds.Client.Dynamics.csproj b/src/GeneralTools/CDSClient/Extensions/DynamicsExtension/Microsoft.Powerplatform.Cds.Client.Dynamics.csproj index 5f84a3e..9291a54 100644 --- a/src/GeneralTools/CDSClient/Extensions/DynamicsExtension/Microsoft.Powerplatform.Cds.Client.Dynamics.csproj +++ b/src/GeneralTools/CDSClient/Extensions/DynamicsExtension/Microsoft.Powerplatform.Cds.Client.Dynamics.csproj @@ -5,7 +5,7 @@ CdsClient true - + false diff --git a/src/GeneralTools/CDSClient/Extensions/Microsoft.Dynamics.Sdk.Messages/Microsoft.Dynamics.Sdk.Messages.Shell.csproj b/src/GeneralTools/CDSClient/Extensions/Microsoft.Dynamics.Sdk.Messages/Microsoft.Dynamics.Sdk.Messages.Shell.csproj index 92eafb4..7d54ccc 100644 --- a/src/GeneralTools/CDSClient/Extensions/Microsoft.Dynamics.Sdk.Messages/Microsoft.Dynamics.Sdk.Messages.Shell.csproj +++ b/src/GeneralTools/CDSClient/Extensions/Microsoft.Dynamics.Sdk.Messages/Microsoft.Dynamics.Sdk.Messages.Shell.csproj @@ -5,7 +5,7 @@ true true - + diff --git a/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/CdsClient_Core_UnitTests.csproj b/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/CdsClient_Core_UnitTests.csproj index ade2ad8..1912b9a 100644 --- a/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/CdsClient_Core_UnitTests.csproj +++ b/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/CdsClient_Core_UnitTests.csproj @@ -3,12 +3,12 @@ true - net462;netcoreapp3.0 + net462;netcoreapp3.1 true CdsClient-Tests false - + @@ -24,4 +24,10 @@ + + + PreserveNewest + + + diff --git a/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/CdsServiceClientTests.cs b/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/CdsServiceClientTests.cs index b3fe65e..0d6c147 100644 --- a/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/CdsServiceClientTests.cs +++ b/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/CdsServiceClientTests.cs @@ -16,6 +16,7 @@ using CdsClient_Core_UnitTests; using System.Net.Http; using System.Threading.Tasks; +using System.IO; namespace CdsClient_Core_Tests { @@ -383,6 +384,36 @@ public void GetEntityDisplayNameTest() } + + [Fact] + public void ImportSolutionTest_AsyncRibbon_ComponetProcessor() + { + Mock orgSvc = null; + Mock fakHttpMethodHander = null; + CdsServiceClient cli = null; + testSupport.SetupMockAndSupport(out orgSvc, out fakHttpMethodHander, out cli); + + ImportSolutionResponse importResponse = new ImportSolutionResponse(); + orgSvc.Setup(f => f.Execute(It.Is( + (p) => + p.CustomizationFile != null && + p.AsyncRibbonProcessing.Equals(true) && + p.ComponentParameters != null))).Returns(importResponse); + + string SampleSolutionPath = Path.Combine("TestMaterial", "EnvVarsSample_1_0_0_2.zip"); + + EntityCollection entCollection = new EntityCollection(); + + Dictionary importParams = new Dictionary(); + importParams.Add(ImportSolutionProperties.ASYNCRIBBONPROCESSING, true); + importParams.Add(ImportSolutionProperties.COMPONENTPARAMETERSPARAM, entCollection); + Guid importId = Guid.Empty; + var result = cli.ImportSolutionToCds(SampleSolutionPath, out importId, activatePlugIns: true, extraParameters: importParams); + + Assert.NotEqual(result, Guid.Empty); + } + + [Fact] public void GetEntityTypeCodeTest() { diff --git a/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/TestMaterial/EnvVarsSample_1_0_0_2.zip b/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/TestMaterial/EnvVarsSample_1_0_0_2.zip new file mode 100644 index 0000000..ff64c7a Binary files /dev/null and b/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/TestMaterial/EnvVarsSample_1_0_0_2.zip differ diff --git a/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/TestSupport.cs b/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/TestSupport.cs index d236d23..4267c50 100644 --- a/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/TestSupport.cs +++ b/src/GeneralTools/CDSClient/UnitTests/CdsClient_Core_Tests/TestSupport.cs @@ -22,7 +22,6 @@ public class TestSupport public Guid _DefaultId = Guid.Parse("0A2E187D-0946-4FF8-B894-3391D4569CCC"); #endregion - #region BoilerPlate public void SetupMockAndSupport( out Mock moqOrgSvc , out Mock moqHttpHandler , out CdsServiceClient cdsServiceClient ) { @@ -64,6 +63,7 @@ private void SetupWhoAmIHandlers(Mock orgSvc) d.FriendlyName = "DIRECTSET"; d.OrganizationId = _OrganizationId; d.OrganizationVersion = "9.1.2.0"; + d.Geo = "NAM"; d.State = OrganizationState.Enabled; d.UniqueName = "HOLD"; d.UrlName = "HOLD"; diff --git a/src/nuspecs/Microsoft.Dynamics.Sdk.Messages.ReleaseNotes.txt b/src/nuspecs/Microsoft.Dynamics.Sdk.Messages.ReleaseNotes.txt index 0c8eac5..f57b2f9 100644 --- a/src/nuspecs/Microsoft.Dynamics.Sdk.Messages.ReleaseNotes.txt +++ b/src/nuspecs/Microsoft.Dynamics.Sdk.Messages.ReleaseNotes.txt @@ -8,7 +8,7 @@ Notice: https://docs.microsoft.com/en-us/dotnet/api/microsoft.crm.sdk.messages?view=dynamics-general-ce-9 ++CURRENTRELEASEID++ - No updates here. + Updated Core CDS Sdk assemblies 0.2.23-Alpha: No updates here. diff --git a/src/nuspecs/Microsoft.Powerplatform.Cds.Client.Dynamics.ReleaseNotes.txt b/src/nuspecs/Microsoft.Powerplatform.Cds.Client.Dynamics.ReleaseNotes.txt index 5345fcf..54617d6 100644 --- a/src/nuspecs/Microsoft.Powerplatform.Cds.Client.Dynamics.ReleaseNotes.txt +++ b/src/nuspecs/Microsoft.Powerplatform.Cds.Client.Dynamics.ReleaseNotes.txt @@ -5,7 +5,7 @@ Notice: 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. ++CURRENTRELEASEID++ - No updates here. + Updated Core CDS Sdk assemblies 0.2.23-Alpha: No updates here. diff --git a/src/nuspecs/Microsoft.Powerplatform.Cds.Client.ReleaseNotes.txt b/src/nuspecs/Microsoft.Powerplatform.Cds.Client.ReleaseNotes.txt index 086c763..3cbbfbc 100644 --- a/src/nuspecs/Microsoft.Powerplatform.Cds.Client.ReleaseNotes.txt +++ b/src/nuspecs/Microsoft.Powerplatform.Cds.Client.ReleaseNotes.txt @@ -9,6 +9,14 @@ Notice: Note: that only OAuth, Certificate, ClientSecret Authentication types are supported at this time. ++CURRENTRELEASEID++ +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 + +0.2.24-Alpha: Fixed an issue with .Clone not correctly supporting adding telemetry handlers to cloned connections 0.2.23-Alpha: