diff --git a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs index 6555109..422215f 100644 --- a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs +++ b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs @@ -616,8 +616,9 @@ internal ConnectionService(IOrganizationService testIOrganziationSvc , string ba /// Sets up an initialized the Dataverse Service interface. /// /// This is an initialized organization web Service proxy + /// Authentication Type to use /// incoming Log Sink - internal ConnectionService(OrganizationWebProxyClientAsync externalOrgWebProxyClient, DataverseTraceLogger logSink = null) + internal ConnectionService(OrganizationWebProxyClientAsync externalOrgWebProxyClient, AuthenticationType authType, DataverseTraceLogger logSink = null) { if (logSink == null) { @@ -643,7 +644,7 @@ internal ConnectionService(OrganizationWebProxyClientAsync externalOrgWebProxyCl } UseExternalConnection = true; GenerateCacheKeys(true); - _eAuthType = AuthenticationType.OAuth; + _eAuthType = authType; } /// @@ -1337,82 +1338,103 @@ private async Task RefreshInstanceDetails(IOrganizationService dvService, Uri ur // Load the organization instance details if (dvService != null) { - //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(); - dtQueryTimer.Restart(); - - var request = new RetrieveCurrentOrganizationRequest() { AccessType = 0, RequestId = guRequestId }; - RetrieveCurrentOrganizationResponse resp; - - if (_configuration.Value.UseWebApiLoginFlow) + try { - OrganizationResponse orgResp = await Command_WebAPIProcess_ExecuteAsync( - request, null, false, null, Guid.Empty, false, _configuration.Value.MaxRetryCount, _configuration.Value.RetryPauseTime, new CancellationToken(), uriOfInstance).ConfigureAwait(false); - try + //TODO:// Add Logic here to improve perf by connecting to global disco. + Guid trackingID = Guid.NewGuid(); + logEntry.Log(string.Format("Querying Organization Instance Details. Request ID: {0}", trackingID)); + Stopwatch dtQueryTimer = new Stopwatch(); + dtQueryTimer.Restart(); + + var request = new RetrieveCurrentOrganizationRequest() { AccessType = 0, RequestId = trackingID }; + RetrieveCurrentOrganizationResponse resp; + logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Execute Command - RetrieveCurrentOrganizationRequest : RequestId={0}", dtQueryTimer.Elapsed.ToString())); + if (_configuration.Value.UseWebApiLoginFlow) { - resp = (RetrieveCurrentOrganizationResponse)orgResp; + OrganizationResponse orgResp = await Command_WebAPIProcess_ExecuteAsync( + request, null, false, null, Guid.Empty, false, _configuration.Value.MaxRetryCount, _configuration.Value.RetryPauseTime, new CancellationToken(), uriOfInstance).ConfigureAwait(false); + try + { + resp = (RetrieveCurrentOrganizationResponse)orgResp; + } + catch (Exception ex) + { + dtQueryTimer.Stop(); + logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Failed to Executed Command - RetrieveCurrentOrganizationRequest : RequestId={1} : total duration: {0}", dtQueryTimer.Elapsed.ToString(), trackingID.ToString()), TraceEventType.Error); + logEntry.Log("************ Exception - Failed to lookup current organization information", TraceEventType.Error, ex); + throw new DataverseOperationException($"Failure to convert OrganziationResponse to requested type - request was {request.RequestName}", ex); + } } - catch ( Exception ex ) + else { - throw new DataverseOperationException($"Failure to convert OrganziationResponse to requested type - request was {request.RequestName}", ex); + try + { + resp = (RetrieveCurrentOrganizationResponse)dvService.Execute(request); + } + catch(Exception ex) + { + dtQueryTimer.Stop(); + logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Failed to Executed Command - RetrieveCurrentOrganizationRequest : RequestId={1} : total duration: {0}", dtQueryTimer.Elapsed.ToString(), trackingID.ToString()), TraceEventType.Error); + logEntry.Log("************ Exception - Failed to lookup current organization information", TraceEventType.Error, ex); + throw new DataverseOperationException("Exception - Failed to lookup current organization data", ex); + } } - } - else - { - resp = (RetrieveCurrentOrganizationResponse)dvService.Execute(request); - } + dtQueryTimer.Stop(); + // Left in information mode intentionally + logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Executed Command - RetrieveCurrentOrganizationRequest : RequestId={1} : total duration: {0}", dtQueryTimer.Elapsed.ToString(), trackingID.ToString())); - dtQueryTimer.Stop(); - logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Completed Querying Organization Instance Details, total duration: {0}", dtQueryTimer.Elapsed.ToString())); - if (resp.Detail != null) - { - _OrgDetail = new OrganizationDetail(); - //Add Endpoints. - foreach (var ep in resp.Detail.Endpoints) + if (resp.Detail != null) { - string endPointName = ep.Key.ToString(); - EndpointType epd = EndpointType.OrganizationDataService; - Enum.TryParse(endPointName, out epd); + _OrgDetail = new OrganizationDetail(); + //Add Endpoints. + foreach (var ep in resp.Detail.Endpoints) + { + string endPointName = ep.Key.ToString(); + EndpointType epd = EndpointType.OrganizationDataService; + Enum.TryParse(endPointName, out epd); - if (!_OrgDetail.Endpoints.ContainsKey(epd)) - _OrgDetail.Endpoints.Add(epd, ep.Value); - else - _OrgDetail.Endpoints[epd] = ep.Value; + if (!_OrgDetail.Endpoints.ContainsKey(epd)) + _OrgDetail.Endpoints.Add(epd, ep.Value); + else + _OrgDetail.Endpoints[epd] = ep.Value; + } + _OrgDetail.FriendlyName = resp.Detail.FriendlyName; + _OrgDetail.OrganizationId = resp.Detail.OrganizationId; + _OrgDetail.OrganizationVersion = resp.Detail.OrganizationVersion; + _OrgDetail.EnvironmentId = resp.Detail.EnvironmentId; + _OrgDetail.TenantId = resp.Detail.TenantId; + _OrgDetail.Geo = resp.Detail.Geo; + _OrgDetail.UrlName = resp.Detail.UrlName; + + OrganizationState ostate = OrganizationState.Disabled; + Enum.TryParse(_OrgDetail.State.ToString(), out ostate); + + _OrgDetail.State = ostate; + _OrgDetail.UniqueName = resp.Detail.UniqueName; + _OrgDetail.UrlName = resp.Detail.UrlName; } - _OrgDetail.FriendlyName = resp.Detail.FriendlyName; - _OrgDetail.OrganizationId = resp.Detail.OrganizationId; - _OrgDetail.OrganizationVersion = resp.Detail.OrganizationVersion; - _OrgDetail.EnvironmentId = resp.Detail.EnvironmentId; - _OrgDetail.TenantId = resp.Detail.TenantId; - _OrgDetail.Geo = resp.Detail.Geo; - _OrgDetail.UrlName = resp.Detail.UrlName; - - OrganizationState ostate = OrganizationState.Disabled; - Enum.TryParse(_OrgDetail.State.ToString(), out ostate); - _OrgDetail.State = ostate; - _OrgDetail.UniqueName = resp.Detail.UniqueName; - _OrgDetail.UrlName = resp.Detail.UrlName; - } - - _organization = _OrgDetail.UniqueName; - ConnectedOrgFriendlyName = _OrgDetail.FriendlyName; - ConnectedOrgPublishedEndpoints = _OrgDetail.Endpoints; + _organization = _OrgDetail.UniqueName; + ConnectedOrgFriendlyName = _OrgDetail.FriendlyName; + ConnectedOrgPublishedEndpoints = _OrgDetail.Endpoints; - // try to create a version number from the org. - OrganizationVersion = new Version("0.0.0.0"); - try - { - Version outVer = null; - if (Version.TryParse(_OrgDetail.OrganizationVersion, out outVer)) + // try to create a version number from the org. + OrganizationVersion = new Version("0.0.0.0"); + try { - OrganizationVersion = outVer; + if (Version.TryParse(_OrgDetail.OrganizationVersion, out Version outVer)) + { + OrganizationVersion = outVer; + } } + catch { }; + logEntry.Log("Completed Parsing Organization Instance Details", TraceEventType.Verbose); + } + catch (Exception ex) + { + logEntry.Log("************ Exception - Fault While initializing client - RefreshInstanceDetails", TraceEventType.Error, ex); + throw new DataverseConnectionException("Exception - Fault While initializing client - RefreshInstanceDetails", ex); } - catch { }; - logEntry.Log("Completed Parsing Organization Instance Details", TraceEventType.Verbose); } } @@ -1421,7 +1443,7 @@ private async Task RefreshInstanceDetails(IOrganizationService dvService, Uri ur /// /// /// - internal async Task GetWhoAmIDetails(IOrganizationService dvService, Guid trackingID = default(Guid)) + internal async Task GetWhoAmIDetails(IOrganizationService dvService, Guid trackingID = default) { if (dvService != null) { @@ -1436,6 +1458,7 @@ private async Task RefreshInstanceDetails(IOrganizationService dvService, Uri ur if (trackingID != Guid.Empty) // Add Tracking number of present. req.RequestId = trackingID; + logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Execute Command - WhoAmIRequest : RequestId={0}", trackingID.ToString())); WhoAmIResponse resp; if (_configuration.Value.UseWebApiLoginFlow) { @@ -1447,7 +1470,7 @@ private async Task RefreshInstanceDetails(IOrganizationService dvService, Uri ur resp = (WhoAmIResponse)dvService.Execute(req); } - // Left in information mode intentionaly. + // Left in information mode intentionally. logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Executed Command - WhoAmIRequest : RequestId={1} : total duration: {0}", dtQueryTimer.Elapsed.ToString(), trackingID.ToString())); return resp; } @@ -1455,7 +1478,7 @@ private async Task RefreshInstanceDetails(IOrganizationService dvService, Uri ur { logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Failed to Executed Command - WhoAmIRequest : RequestId={1} : total duration: {0}", dtQueryTimer.Elapsed.ToString(), trackingID.ToString()), TraceEventType.Error); logEntry.Log("************ Exception - Failed to lookup current user", TraceEventType.Error, ex); - throw ex; + throw new DataverseOperationException("Exception - Failed to lookup current user", ex); } finally { @@ -1537,6 +1560,8 @@ internal void SetClonedProperties(ServiceClient sourceClient) internal async Task Command_WebAPIProcess_ExecuteAsync(OrganizationRequest req, string logMessageTag, bool bypassPluginExecution, MetadataUtility metadataUtlity, Guid callerId, bool disableConnectionLocking, int maxRetryCount, TimeSpan retryPauseTime, CancellationToken cancellationToken, Uri uriOfInstance = null) { + cancellationToken.ThrowIfCancellationRequested(); + if (!Utilities.IsRequestValidForTranslationToWebAPI(req)) // THIS WILL GET REMOVED AT SOME POINT, TEMP FOR TRANSTION //TODO:REMOVE ON COMPELTE { logEntry.Log("Execute Organization Request failed, WebAPI is only supported for limited type of messages at this time.", TraceEventType.Error); @@ -1558,6 +1583,7 @@ internal async Task Command_WebAPIProcess_ExecuteAsync(Org if (cReq != null) { // if CRUD type. get Entity + cancellationToken.ThrowIfCancellationRequested(); entityMetadata = metadataUtlity.GetEntityMetadata(EntityFilters.Relationships, cReq.LogicalName); if (entityMetadata == null) { @@ -1574,6 +1600,7 @@ internal async Task Command_WebAPIProcess_ExecuteAsync(Org if (cReq != null) { + cancellationToken.ThrowIfCancellationRequested(); requestBodyObject = Utilities.ToExpandoObject(cReq, metadataUtlity, methodToExecute , logEntry); if (cReq.RelatedEntities != null && cReq.RelatedEntities.Count > 0) requestBodyObject = Utilities.ReleatedEntitiesToExpandoObject(requestBodyObject, cReq.LogicalName, cReq.RelatedEntities, metadataUtlity, methodToExecute, logEntry); @@ -1721,7 +1748,7 @@ internal async Task Command_WebAPIProcess_ExecuteAsync(Org } // Execute request - var sResp = await Command_WebExecuteAsync(postUri, bodyOfRequest, methodToExecute, headers, "application/json", logMessageTag, callerId, disableConnectionLocking, maxRetryCount, retryPauseTime, uriOfInstance).ConfigureAwait(false); + var sResp = await Command_WebExecuteAsync(postUri, bodyOfRequest, methodToExecute, headers, "application/json", logMessageTag, callerId, disableConnectionLocking, maxRetryCount, retryPauseTime, uriOfInstance, cancellationToken: cancellationToken).ConfigureAwait(false); if (sResp != null && sResp.IsSuccessStatusCode) { if (req is CreateRequest) @@ -1800,9 +1827,10 @@ internal async Task Command_WebAPIProcess_ExecuteAsync(Org /// retry pause time /// uri of instance /// + /// /// internal async Task Command_WebExecuteAsync(string queryString, string body, HttpMethod method, Dictionary> customHeaders, - string contentType, string errorStringCheck, Guid callerId, bool disableConnectionLocking, int maxRetryCount, TimeSpan retryPauseTime, Uri uriOfInstance = null, Guid requestTrackingId = default(Guid)) + string contentType, string errorStringCheck, Guid callerId, bool disableConnectionLocking, int maxRetryCount, TimeSpan retryPauseTime, Uri uriOfInstance = null, Guid requestTrackingId = default, CancellationToken cancellationToken = default) { Stopwatch logDt = new Stopwatch(); int retryCount = 0; @@ -1974,6 +2002,7 @@ internal async Task Command_WebExecuteAsync(string queryStr resp = await ConnectionService.ExecuteHttpRequestAsync( TargetUri.ToString(), method, + cancellationToken: cancellationToken, body: body, customHeaders: customHeaders, logSink: logEntry, diff --git a/src/GeneralTools/DataverseClient/Client/Interfaces/IOrganizationServiceAsync2.cs b/src/GeneralTools/DataverseClient/Client/Interfaces/IOrganizationServiceAsync2.cs index 112cb61..151e4a7 100644 --- a/src/GeneralTools/DataverseClient/Client/Interfaces/IOrganizationServiceAsync2.cs +++ b/src/GeneralTools/DataverseClient/Client/Interfaces/IOrganizationServiceAsync2.cs @@ -11,8 +11,24 @@ namespace Microsoft.PowerPlatform.Dataverse.Client /// Interface containing extension methods provided by the DataverseServiceClient for the IOrganizationService Interface. /// These extensions will only operate from within the client and are not supported server side. /// - public interface IOrganizationServiceAsync2 + public interface IOrganizationServiceAsync2 : IOrganizationServiceAsync { + /// + /// Associate an entity with a set of entities + /// + /// + /// + /// + /// + /// Propagates notification that operations should be canceled. + Task AssociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken); + /// + /// Create an entity and process any related entities + /// + /// entity to create + /// Propagates notification that operations should be canceled. + /// The ID of the created record + Task CreateAsync(Entity entity, CancellationToken cancellationToken); /// /// Create an entity and process any related entities /// @@ -20,6 +36,51 @@ public interface IOrganizationServiceAsync2 /// Propagates notification that operations should be canceled. /// Returns the newly created record Task CreateAndReturnAsync(Entity entity, CancellationToken cancellationToken); - + /// + /// Delete instance of an entity + /// + /// Logical name of entity + /// Id of entity + /// Propagates notification that operations should be canceled. + Task DeleteAsync(string entityName, Guid id, CancellationToken cancellationToken); + /// + /// Disassociate an entity with a set of entities + /// + /// + /// + /// + /// + /// Propagates notification that operations should be canceled. + Task DisassociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken); + /// + /// Perform an action in an organization specified by the request. + /// + /// Refer to SDK documentation for list of messages that can be used. + /// Propagates notification that operations should be canceled. + /// Results from processing the request + Task ExecuteAsync(OrganizationRequest request, CancellationToken cancellationToken); + /// + /// Retrieves instance of an entity + /// + /// Logical name of entity + /// Id of entity + /// Column Set collection to return with the request + /// Propagates notification that operations should be canceled. + /// Selected Entity + Task RetrieveAsync(string entityName, Guid id, ColumnSet columnSet, CancellationToken cancellationToken); + /// + /// Retrieves a collection of entities + /// + /// + /// Propagates notification that operations should be canceled. + /// Returns an EntityCollection Object containing the results of the query + Task RetrieveMultipleAsync(QueryBase query, CancellationToken cancellationToken); + /// + /// Updates an entity and process any related entities + /// + /// entity to update + /// Propagates notification that operations should be canceled. + Task UpdateAsync(Entity entity, CancellationToken cancellationToken); } + } diff --git a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs index 15c5f6e..2a870ca 100644 --- a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs +++ b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs @@ -1088,7 +1088,7 @@ internal void CreateServiceConnection( // if using an user provided connection,. if (externalOrgWebProxyClient != null) { - _connectionSvc = new ConnectionService(externalOrgWebProxyClient, _logEntry); + _connectionSvc = new ConnectionService(externalOrgWebProxyClient, requestedAuthType , _logEntry); _connectionSvc.IsAClone = isCloned; if (isCloned && incomingOrgVersion != null) { @@ -4955,8 +4955,9 @@ internal bool AddRequestToBatch(Guid batchId, OrganizationRequest req, string ba /// Content your passing to the request /// Headers in addition to the default headers added by for Executing a web request /// Content Type attach to the request. this defaults to application/json if not set. + /// Cancellation token for the request /// - public HttpResponseMessage ExecuteWebRequest(HttpMethod method, string queryString, string body, Dictionary> customHeaders, string contentType = default(string)) + public HttpResponseMessage ExecuteWebRequest(HttpMethod method, string queryString, string body, Dictionary> customHeaders, string contentType = default, CancellationToken cancellationToken = default) { _logEntry.ResetLastError(); // Reset Last Error if (DataverseService == null) @@ -4981,7 +4982,7 @@ internal bool AddRequestToBatch(Guid batchId, OrganizationRequest req, string ba queryString = baseQueryString; } - var result = _connectionSvc.Command_WebExecuteAsync(queryString, body, method, customHeaders, contentType, string.Empty, CallerId, _disableConnectionLocking, MaxRetryCount, RetryPauseTime).Result; + var result = _connectionSvc.Command_WebExecuteAsync(queryString, body, method, customHeaders, contentType, string.Empty, CallerId, _disableConnectionLocking, MaxRetryCount, RetryPauseTime, cancellationToken: cancellationToken).Result; if (result == null) throw LastException; else @@ -5024,6 +5025,7 @@ private OrganizationResponse ExecuteOrganizationRequestImpl(OrganizationRequest private async Task ExecuteOrganizationRequestAsyncImpl(OrganizationRequest req, CancellationToken cancellationToken, string logMessageTag = "User Defined", bool useWebAPI = false, bool bypassPluginExecution = false) { + cancellationToken.ThrowIfCancellationRequested(); if (req != null) { useWebAPI = Utilities.IsRequestValidForTranslationToWebAPI(req); @@ -5418,6 +5420,7 @@ internal async Task Command_ExecuteAsyncImpl(OrganizationR { try { + cancellationToken.ThrowIfCancellationRequested(); _retryPauseTimeRunning = _configuration.Value.RetryPauseTime; retry = false; if (!_disableConnectionLocking) @@ -6163,6 +6166,99 @@ public void Update(Entity entity) #endregion #region IOrganzationServiceAsync helper - Proxy object + /// + /// Associate an entity with a set of entities + /// + /// + /// + /// + /// + public async Task AssociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities) + { + await AssociateAsync(entityName, entityId, relationship, relatedEntities, CancellationToken.None).ConfigureAwait(false); + return; + } + + /// + /// Create an entity and process any related entities + /// + /// entity to create + /// The ID of the created record + public async Task CreateAsync(Entity entity) + { + return await CreateAsync(entity, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Delete instance of an entity + /// + /// Logical name of entity + /// Id of entity + public async Task DeleteAsync(string entityName, Guid id) + { + await DeleteAsync(entityName, id, CancellationToken.None).ConfigureAwait(false); + return; + } + + /// + /// Disassociate an entity with a set of entities + /// + /// + /// + /// + /// + public async Task DisassociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities) + { + await DisassociateAsync(entityName, entityId, relationship, relatedEntities, CancellationToken.None).ConfigureAwait(false); + return; + } + + /// + /// Perform an action in an organization specified by the request. + /// + /// Refer to SDK documentation for list of messages that can be used. + /// Results from processing the request + public async Task ExecuteAsync(OrganizationRequest request) + { + return await ExecuteAsync(request, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Retrieves instance of an entity + /// + /// Logical name of entity + /// Id of entity + /// Column Set collection to return with the request + /// Selected Entity + public async Task RetrieveAsync(string entityName, Guid id, ColumnSet columnSet) + { + return await RetrieveAsync(entityName, id, columnSet, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Retrieves a collection of entities + /// + /// + /// Returns an EntityCollection Object containing the results of the query + public async Task RetrieveMultipleAsync(QueryBase query) + { + return await RetrieveMultipleAsync(query, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Updates an entity and process any related entities + /// + /// entity to update + public async Task UpdateAsync(Entity entity) + { + await UpdateAsync(entity, CancellationToken.None).ConfigureAwait(false); + return; + } + + + #endregion + + #region IOrganzationServiceAsync2 helper - Proxy object /// /// Associate an entity with a set of entities @@ -6192,7 +6288,7 @@ public async Task AssociateAsync(string entityName, Guid entityId, Relationship /// entity to create /// Propagates notification that operations should be canceled. /// The ID of the created record - public async Task CreateAsync(Entity entity, CancellationToken cancellationToken = default) + public async Task CreateAsync(Entity entity, CancellationToken cancellationToken) { // Relay to Update request. CreateResponse resp = (CreateResponse)await ExecuteOrganizationRequestAsyncImpl( @@ -6209,14 +6305,22 @@ public async Task CreateAsync(Entity entity, CancellationToken cancellatio return resp.id; } - /// /// Create an entity and process any related entities /// /// entity to create /// Propagates notification that operations should be canceled. /// Returns the newly created record - public Task CreateAndReturnAsync(Entity entity, CancellationToken cancellationToken = default) + public Task CreateAndReturnAsync(Entity entity, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + /// + /// Create an entity and process any related entities + /// + /// entity to create + /// Returns the newly created record + public Task CreateAndReturnAsync(Entity entity) { throw new NotImplementedException(); } @@ -6227,7 +6331,7 @@ public Task CreateAndReturnAsync(Entity entity, CancellationToken cancel /// Logical name of entity /// Id of entity /// Propagates notification that operations should be canceled. - public async Task DeleteAsync(string entityName, Guid id, CancellationToken cancellationToken = default) + public async Task DeleteAsync(string entityName, Guid id, CancellationToken cancellationToken) { DeleteResponse resp = (DeleteResponse)await ExecuteOrganizationRequestAsyncImpl( new DeleteRequest() @@ -6249,7 +6353,7 @@ public async Task DeleteAsync(string entityName, Guid id, CancellationToken canc /// /// /// Propagates notification that operations should be canceled. - public async Task DisassociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken = default) + public async Task DisassociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken) { DisassociateResponse resp = (DisassociateResponse)await ExecuteOrganizationRequestAsyncImpl(new DisassociateRequest() { @@ -6269,7 +6373,7 @@ public async Task DisassociateAsync(string entityName, Guid entityId, Relationsh /// Refer to SDK documentation for list of messages that can be used. /// Propagates notification that operations should be canceled. /// Results from processing the request - public async Task ExecuteAsync(OrganizationRequest request, CancellationToken cancellationToken = default) + public async Task ExecuteAsync(OrganizationRequest request, CancellationToken cancellationToken) { OrganizationResponse resp = await ExecuteOrganizationRequestAsyncImpl(request , cancellationToken @@ -6280,7 +6384,6 @@ public async Task ExecuteAsync(OrganizationRequest request return resp; } - /// /// Retrieves instance of an entity /// @@ -6289,7 +6392,7 @@ public async Task ExecuteAsync(OrganizationRequest request /// Column Set collection to return with the request /// Propagates notification that operations should be canceled. /// Selected Entity - public async Task RetrieveAsync(string entityName, Guid id, ColumnSet columnSet, CancellationToken cancellationToken = default) + public async Task RetrieveAsync(string entityName, Guid id, ColumnSet columnSet, CancellationToken cancellationToken) { RetrieveResponse resp = (RetrieveResponse)await ExecuteOrganizationRequestAsyncImpl( new RetrieveRequest() @@ -6311,7 +6414,7 @@ public async Task RetrieveAsync(string entityName, Guid id, ColumnSet co /// /// Propagates notification that operations should be canceled. /// Returns an EntityCollection Object containing the results of the query - public async Task RetrieveMultipleAsync(QueryBase query, CancellationToken cancellationToken = default) + public async Task RetrieveMultipleAsync(QueryBase query, CancellationToken cancellationToken) { RetrieveMultipleResponse resp = (RetrieveMultipleResponse)await ExecuteOrganizationRequestAsyncImpl(new RetrieveMultipleRequest() { Query = query }, cancellationToken, "RetrieveMultiple to Dataverse via IOrganizationService").ConfigureAwait(false); if (resp == null) @@ -6325,7 +6428,7 @@ public async Task RetrieveMultipleAsync(QueryBase query, Cance /// /// entity to update /// Propagates notification that operations should be canceled. - public async Task UpdateAsync(Entity entity, CancellationToken cancellationToken = default) + public async Task UpdateAsync(Entity entity, CancellationToken cancellationToken) { // Relay to Update request. UpdateResponse resp = (UpdateResponse)await ExecuteOrganizationRequestAsyncImpl( diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs index 1102090..d129afe 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs +++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerPlatform.Dataverse.Client; using Microsoft.PowerPlatform.Dataverse.Client.Auth; +using Microsoft.PowerPlatform.Dataverse.Client.Utils; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Metadata; @@ -214,6 +215,21 @@ public void CreateRequestTests() respId = cli.Create(acctEntity); Assert.Equal(testSupport._DefaultId, respId); + // Test low level createAsync + respId = cli.CreateAsync(acctEntity).GetAwaiter().GetResult(); + Assert.Equal(testSupport._DefaultId, respId); + + try + { + // Test low level createAsyncwithCancelationToken + System.Threading.CancellationToken tok = new System.Threading.CancellationToken(true); + respId = cli.CreateAsync(acctEntity, tok).GetAwaiter().GetResult(); + } + catch(Exception ex) + { + Assert.IsType(ex); + } + // Test Helper create respId = cli.CreateNewRecord("account", newFields); Assert.Equal(testSupport._DefaultId, respId); diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt index e1fd230..2bf3bab 100644 --- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt +++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt @@ -8,6 +8,16 @@ Notice: Note: that only OAuth, Certificate, ClientSecret Authentication types are supported at this time. ++CURRENTRELEASEID++ +Fixed and issue with the client being non-castable to IOrganizationServiceAsync, Client will now cast again. (Internal Report) + Special note: + IOrganizationServiceAsync has async methods without cancellation support. this is required for service mapping as the dataverse server does not natively support this feature. + IOrganizationServiceAsync2 has async methods WITH cancellation support. + This surfaces in the client as 2 forms of each async method and is currently necessary to support internal auto generation of contracts for service communication. We will work to improve this in the future. + Cancellation events will currently raise an OperationCancelled Exception, this can either be the topmost exception, or embedded in a DataverseException. + Cancellation methods are 'preferred' if you need to support cancellation in your application. +Fixed an issue with External authentication type not being properly passed though to cloned clients. (git #162) + +0.5.2: ***** Breaking Changes ***** Removed the constructor: public ServiceClient(OrganizationWebProxyClient externalOrgWebProxyClient, ILogger logger = null)