diff --git a/src/GeneralTools/DataverseClient/Client/Builder/AbstractClientRequestBuilder.cs b/src/GeneralTools/DataverseClient/Client/Builder/AbstractClientRequestBuilder.cs
new file mode 100644
index 0000000..815bd44
--- /dev/null
+++ b/src/GeneralTools/DataverseClient/Client/Builder/AbstractClientRequestBuilder.cs
@@ -0,0 +1,482 @@
+// Ignore Spelling: Dataverse Crm
+
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Query;
+using Microsoft.Xrm.Sdk;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using static Microsoft.PowerPlatform.Dataverse.Client.Utilities;
+using Microsoft.PowerPlatform.Dataverse.Client.Utils;
+
+namespace Microsoft.PowerPlatform.Dataverse.Client.Builder
+{
+ ///
+ /// Internal use only. Request Builder base class.
+ ///
+ ///
+ public abstract class AbstractClientRequestBuilder : IOrganizationServiceAsync2
+ where T : AbstractClientRequestBuilder
+ {
+ private IOrganizationServiceAsync2 _client;
+ private Guid? _correlationId; // this is the correlation id of the request
+ private Guid? _requestId; // this is the request id of the request
+ private Dictionary _headers = new Dictionary();
+ private Guid? _aadOidId; // this ObjectID to use for the requesting user.
+ private Guid? _crmUserId; // this is the CRM user id to use for the requesting user.
+
+ ///
+ /// Internal use only, used to build a base class for request builders.
+ ///
+ ///
+ internal AbstractClientRequestBuilder(IOrganizationServiceAsync2 client)
+ {
+ _client = client;
+ }
+
+ ///
+ /// Adds a request id of your choosing to this request. This is used for tracing purposes.
+ ///
+ ///
+ ///
+ public T WithRequestId(Guid requestId)
+ {
+ _requestId = requestId;
+ return (T)this;
+ }
+
+ ///
+ /// Adds a correlation id of your choosing to this request. This is used for tracing purposes.
+ ///
+ ///
+ ///
+ public T WithCorrelationId(Guid correlationId)
+ {
+ _correlationId = correlationId;
+ return (T)this;
+ }
+
+ ///
+ /// Adds an individual header to the request. This works in conjunction with the custom headers request behavior.
+ ///
+ /// Header Key
+ /// Header Value
+ ///
+ public T WithHeader(string key, string value)
+ {
+ _headers.Add(key, value);
+ return (T)this;
+ }
+
+ ///
+ /// Adds an array of headers to the request. This works in conjunction with the custom headers request behavior.
+ ///
+ /// Dictionary of Headers to add to there request.
+ ///
+ public T WithHeaders(IDictionary headers)
+ {
+ foreach (var itm in headers)
+ _headers.Add(itm.Key, itm.Value);
+ return (T)this;
+ }
+
+ ///
+ /// Adds the AAD object ID to the request
+ ///
+ ///
+ ///
+ public T WithUserObjectId(Guid userObjectId)
+ {
+ _aadOidId = userObjectId;
+ return (T)this;
+ }
+
+ ///
+ /// Adds the CrmUserId to the request.
+ ///
+ ///
+ ///
+ public T WithCrmUserId(Guid crmUserId)
+ {
+ _crmUserId = crmUserId;
+ return (T)this;
+ }
+
+ ///
+ /// This configured the request to send to Dataverse.
+ ///
+ ///
+ ///
+ ///
+ internal OrganizationRequest BuildRequest(OrganizationRequest request)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException("Request is not set");
+ }
+
+ ParameterCollection parameters = new ParameterCollection();
+ Guid requestTracker = _requestId ?? Guid.NewGuid();
+ request.RequestId = requestTracker;
+
+
+ if (_correlationId != null)
+ {
+ parameters.Add(RequestHeaders.X_MS_CORRELATION_REQUEST_ID, _correlationId.Value);
+ }
+
+ if (_headers.Any())
+ {
+ parameters.Add(RequestBinderUtil.HEADERLIST, new Dictionary(_headers));
+ }
+
+ request.Parameters.AddRange(parameters);
+
+ // Clear in case this is reused.
+ ClearRequest();
+
+ return request;
+ }
+
+ ///
+ /// Clear request parameters when the request is executed.
+ ///
+ private void ClearRequest()
+ {
+ _requestId = null;
+ _correlationId = null;
+ _headers.Clear();
+ }
+
+ #region IOrganization Services Interface Implementations
+ ///
+ /// Associate an entity with a set of entities
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Propagates notification that operations should be canceled.
+ public Task AssociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken)
+ {
+ AssociateRequest request = new AssociateRequest()
+ {
+ Target = new EntityReference(entityName, entityId),
+ Relationship = relationship,
+ RelatedEntities = relatedEntities
+ };
+ request = (AssociateRequest)BuildRequest(request);
+ return _client.ExecuteAsync(request, 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
+ public Task CreateAsync(Entity entity, CancellationToken cancellationToken)
+ {
+ CreateRequest request = new CreateRequest()
+ {
+ Target = entity
+ };
+ request = (CreateRequest)BuildRequest(request);
+ return _client.ExecuteAsync(request, cancellationToken).ContinueWith((t) =>
+ {
+ return ((CreateResponse)t.Result).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)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Delete instance of an entity
+ ///
+ /// Logical name of entity
+ /// Id of entity
+ /// Propagates notification that operations should be canceled.
+ public Task DeleteAsync(string entityName, Guid id, CancellationToken cancellationToken)
+ {
+ DeleteRequest request = new DeleteRequest()
+ {
+ Target = new EntityReference(entityName, id)
+ };
+ request = (DeleteRequest)BuildRequest(request);
+ return _client.ExecuteAsync(request, cancellationToken);
+ }
+
+ ///
+ /// Disassociate an entity with a set of entities
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Propagates notification that operations should be canceled.
+ public Task DisassociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken)
+ {
+ DisassociateRequest request = new DisassociateRequest()
+ {
+ Target = new EntityReference(entityName, entityId),
+ Relationship = relationship,
+ RelatedEntities = relatedEntities
+ };
+ request = (DisassociateRequest)BuildRequest(request);
+ return _client.ExecuteAsync(request, 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
+ public Task ExecuteAsync(OrganizationRequest request, CancellationToken cancellationToken)
+ {
+ request = BuildRequest(request);
+ return _client.ExecuteAsync(request, 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
+
+ public Task RetrieveAsync(string entityName, Guid id, ColumnSet columnSet, CancellationToken cancellationToken)
+ {
+ RetrieveRequest request = new RetrieveRequest()
+ {
+ ColumnSet = columnSet,
+ Target = new EntityReference(entityName, id)
+ };
+ request = (RetrieveRequest)BuildRequest(request);
+ return _client.ExecuteAsync(request, cancellationToken).ContinueWith((t) =>
+ {
+ return ((RetrieveResponse)t.Result).Entity;
+ });
+ }
+
+ ///
+ /// Retrieves a collection of entities
+ ///
+ ///
+ /// Propagates notification that operations should be canceled.
+ /// Returns an EntityCollection Object containing the results of the query
+ public Task RetrieveMultipleAsync(QueryBase query, CancellationToken cancellationToken)
+ {
+ RetrieveMultipleRequest request = new RetrieveMultipleRequest()
+ {
+ Query = query
+ };
+ request = (RetrieveMultipleRequest)BuildRequest(request);
+ return _client.ExecuteAsync(request, cancellationToken).ContinueWith((t) =>
+ {
+ return ((RetrieveMultipleResponse)t.Result).EntityCollection;
+ });
+
+ }
+
+ ///
+ /// Updates an entity and process any related entities
+ ///
+ /// entity to update
+ /// Propagates notification that operations should be canceled.
+ public Task UpdateAsync(Entity entity, CancellationToken cancellationToken)
+ {
+ UpdateRequest request = new UpdateRequest()
+ {
+ Target = entity
+ };
+ request = (UpdateRequest)BuildRequest(request);
+ return _client.ExecuteAsync(request, cancellationToken);
+ }
+
+ ///
+ /// Create an entity and process any related entities
+ ///
+ /// entity to create
+ /// Returns the newly created record
+ public Task CreateAsync(Entity entity)
+ {
+ return CreateAsync(entity, CancellationToken.None);
+ }
+
+ ///
+ /// Retrieves instance of an entity
+ ///
+ /// Logical name of entity
+ /// Id of entity
+ /// Column Set collection to return with the request
+ /// Selected Entity
+ public Task RetrieveAsync(string entityName, Guid id, ColumnSet columnSet)
+ {
+ return RetrieveAsync(entityName, id, columnSet, CancellationToken.None);
+ }
+
+ ///
+ /// Updates an entity and process any related entities
+ ///
+ /// entity to update
+ public Task UpdateAsync(Entity entity)
+ {
+ return UpdateAsync(entity, CancellationToken.None);
+ }
+
+ ///
+ /// Delete instance of an entity
+ ///
+ /// Logical name of entity
+ /// Id of entity
+ public Task DeleteAsync(string entityName, Guid id)
+ {
+ return DeleteAsync(entityName, id, CancellationToken.None);
+ }
+
+ ///
+ /// 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 Task ExecuteAsync(OrganizationRequest request)
+ {
+ return ExecuteAsync(request, CancellationToken.None);
+ }
+
+ ///
+ /// Associate an entity with a set of entities
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task AssociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities)
+ {
+ return AssociateAsync(entityName, entityId, relationship, relatedEntities, CancellationToken.None);
+ }
+
+ ///
+ /// Disassociate an entity with a set of entities
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task DisassociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities)
+ {
+ return DisassociateAsync(entityName, entityId, relationship, relatedEntities, CancellationToken.None);
+ }
+
+ ///
+ /// Retrieves a collection of entities
+ ///
+ ///
+ /// Returns an EntityCollection Object containing the results of the query
+ public Task RetrieveMultipleAsync(QueryBase query)
+ {
+ return RetrieveMultipleAsync(query, CancellationToken.None);
+ }
+
+ ///
+ /// Create an entity and process any related entities
+ ///
+ /// entity to create
+ /// The ID of the created record
+ public Guid Create(Entity entity)
+ {
+ return CreateAsync(entity).Result;
+ }
+
+ ///
+ /// Retrieves instance of an entity
+ ///
+ /// Logical name of entity
+ /// Id of entity
+ /// Column Set collection to return with the request
+ /// Selected Entity
+ public Entity Retrieve(string entityName, Guid id, ColumnSet columnSet)
+ {
+ return RetrieveAsync(entityName, id, columnSet).Result;
+ }
+
+ ///
+ /// Updates an entity and process any related entities
+ ///
+ /// entity to update
+ public void Update(Entity entity)
+ {
+ UpdateAsync(entity).Wait();
+ }
+
+ ///
+ /// Delete instance of an entity
+ ///
+ /// Logical name of entity
+ /// Id of entity
+ public void Delete(string entityName, Guid id)
+ {
+ DeleteAsync(entityName, id).Wait();
+ }
+
+ ///
+ /// 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 OrganizationResponse Execute(OrganizationRequest request)
+ {
+ return ExecuteAsync(request).Result;
+ }
+
+ ///
+ /// Associate an entity with a set of entities
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Associate(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities)
+ {
+ AssociateAsync(entityName, entityId, relationship, relatedEntities).Wait();
+ }
+
+ ///
+ /// Disassociate an entity with a set of entities
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Disassociate(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities)
+ {
+ DisassociateAsync(entityName, entityId, relationship, relatedEntities).Wait();
+ }
+
+ ///
+ /// Retrieves a collection of entities
+ ///
+ ///
+ /// Returns an EntityCollection Object containing the results of the query
+ public EntityCollection RetrieveMultiple(QueryBase query)
+ {
+ return RetrieveMultipleAsync(query).Result;
+ }
+ #endregion
+ }
+}
diff --git a/src/GeneralTools/DataverseClient/Client/Builder/ServiceClientRequestBuilder.cs b/src/GeneralTools/DataverseClient/Client/Builder/ServiceClientRequestBuilder.cs
new file mode 100644
index 0000000..682ce45
--- /dev/null
+++ b/src/GeneralTools/DataverseClient/Client/Builder/ServiceClientRequestBuilder.cs
@@ -0,0 +1,20 @@
+// Ignore Spelling: Dataverse
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerPlatform.Dataverse.Client.Builder
+{
+ ///
+ /// Request builder class for submitting requests to Dataverse.
+ ///
+ public class ServiceClientRequestBuilder : AbstractClientRequestBuilder
+ {
+ internal ServiceClientRequestBuilder(IOrganizationServiceAsync2 client)
+ : base(client)
+ { }
+ }
+}
diff --git a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs
index 6dba71a..1b91da0 100644
--- a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs
+++ b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs
@@ -380,7 +380,7 @@ internal IEnumerable> GetAllLogs()
///
/// if set to true, the log provider is set locally
///
- public bool isLogEntryCreatedLocaly { get; set; }
+ public bool isLogEntryCreatedLocally { get; set; }
///
/// Get and Set of network credentials...
@@ -687,7 +687,7 @@ internal ConnectionService(IOrganizationService testIOrganziationSvc, string bas
_testSupportIOrg = testIOrganziationSvc;
WebApiHttpClient = mockClient;
logEntry = new DataverseTraceLogger(logger);
- isLogEntryCreatedLocaly = true;
+ isLogEntryCreatedLocally = true;
_OrgDetail = new OrganizationDetail();
_OrgDetail.Endpoints.Add(EndpointType.OrganizationDataService, baseConnectUrl);
@@ -708,12 +708,12 @@ internal ConnectionService(OrganizationWebProxyClientAsync externalOrgWebProxyCl
if (logSink == null)
{
logEntry = new DataverseTraceLogger();
- isLogEntryCreatedLocaly = true;
+ isLogEntryCreatedLocally = true;
}
else
{
logEntry = logSink;
- isLogEntryCreatedLocaly = false;
+ isLogEntryCreatedLocally = false;
}
// is this a clone request
@@ -765,12 +765,12 @@ internal ConnectionService(
if (logSink == null)
{
logEntry = new DataverseTraceLogger();
- isLogEntryCreatedLocaly = true;
+ isLogEntryCreatedLocally = true;
}
else
{
logEntry = logSink;
- isLogEntryCreatedLocaly = false;
+ isLogEntryCreatedLocally = false;
}
// is this a clone request
@@ -838,12 +838,12 @@ internal ConnectionService(
if (logSink == null)
{
logEntry = new DataverseTraceLogger();
- isLogEntryCreatedLocaly = true;
+ isLogEntryCreatedLocally = true;
}
else
{
logEntry = logSink;
- isLogEntryCreatedLocaly = false;
+ isLogEntryCreatedLocally = false;
}
// is this a clone request
@@ -908,12 +908,12 @@ internal ConnectionService(
if (logSink == null)
{
logEntry = new DataverseTraceLogger();
- isLogEntryCreatedLocaly = true;
+ isLogEntryCreatedLocally = true;
}
else
{
logEntry = logSink;
- isLogEntryCreatedLocaly = false;
+ isLogEntryCreatedLocally = false;
}
// is this a clone request
@@ -2086,6 +2086,8 @@ internal async Task Command_WebAPIProcess_ExecuteAsync(Org
userProvidedRequestId = req.RequestId.Value;
}
+ RequestBinderUtil.GetAdditionalHeaders(headers, req);
+
// Execute request
var sResp = await Command_WebExecuteAsync(postUri, bodyOfRequest, methodToExecute, headers, "application/json", logMessageTag, callerId, disableConnectionLocking, maxRetryCount, retryPauseTime, uriOfInstance, cancellationToken: cancellationToken, requestTrackingId: userProvidedRequestId).ConfigureAwait(false);
if (sResp != null && sResp.IsSuccessStatusCode)
@@ -3008,6 +3010,11 @@ private static async Task QueryGlobalDiscoveryAsyn
proInfo.SetValue(d, ep, null);
}
+ if (!Utilities.IsValidOrganizationUrl(d))
+ {
+ logSink.Log(string.Format(CultureInfo.InvariantCulture, "QueryGlobalDiscovery - Invalid Url returned from Discovery ({0})", d.Endpoints[EndpointType.OrganizationService]));
+ continue;
+ }
orgList.Add(d);
}
dtStartQuery.Stop();
@@ -3762,7 +3769,7 @@ void Dispose(bool disposing)
if (disposing)
{
disposedValue = true;
- if (isLogEntryCreatedLocaly)
+ if (isLogEntryCreatedLocally)
{
logEntry?.Dispose();
}
diff --git a/src/GeneralTools/DataverseClient/Client/Connector/OrganizationWebProxyClientAsync.cs b/src/GeneralTools/DataverseClient/Client/Connector/OrganizationWebProxyClientAsync.cs
index 19b1498..ce9fccf 100644
--- a/src/GeneralTools/DataverseClient/Client/Connector/OrganizationWebProxyClientAsync.cs
+++ b/src/GeneralTools/DataverseClient/Client/Connector/OrganizationWebProxyClientAsync.cs
@@ -5,11 +5,14 @@ namespace Microsoft.PowerPlatform.Dataverse.Client.Connector
using System.Net;
using System.Reflection;
using System.ServiceModel;
+ using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Threading.Tasks;
using Microsoft.PowerPlatform.Dataverse.Client;
+ using Microsoft.PowerPlatform.Dataverse.Client.Utils;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
+ using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
@@ -189,12 +192,20 @@ protected internal virtual Task DeleteAsyncCore(string entityName, Guid id)
protected internal virtual OrganizationResponse ExecuteCore(OrganizationRequest request)
{
- return ExecuteAction(() => Channel.Execute(request));
+ return ExecuteAction(() =>
+ {
+ ProcessRequestBinderProperties(request);
+ return Channel.Execute(request);
+ });
}
protected internal virtual Task ExecuteAsyncCore(OrganizationRequest request)
{
- return ExecuteOperation(() => Channel.ExecuteAsync(request));
+ return ExecuteOperation(() =>
+ {
+ ProcessRequestBinderProperties(request);
+ return Channel.ExecuteAsync(request);
+ });
}
protected internal virtual void AssociateCore(string entityName, Guid entityId, Relationship relationship,
@@ -241,6 +252,15 @@ protected override WebProxyClientContextAsyncInitializer(Func action)
}
#if NETCOREAPP
- protected async internal Task ExecuteOperation(Func> asyncAction)
+ protected internal Task ExecuteOperation(Func> asyncAction)
{
if (asyncAction == null)
{
@@ -92,7 +92,7 @@ protected async internal Task ExecuteOperation(Func> asyncAction)
using (CreateNewInitializer())
{
- return await asyncAction().ConfigureAwait(continueOnCapturedContext: true);
+ return asyncAction();
}
}
#else
diff --git a/src/GeneralTools/DataverseClient/Client/InternalExtensions/RequestResponseExtenstions.cs b/src/GeneralTools/DataverseClient/Client/InternalExtensions/RequestResponseExtenstions.cs
index 20c2e9d..9d067eb 100644
--- a/src/GeneralTools/DataverseClient/Client/InternalExtensions/RequestResponseExtenstions.cs
+++ b/src/GeneralTools/DataverseClient/Client/InternalExtensions/RequestResponseExtenstions.cs
@@ -11,7 +11,7 @@
namespace Microsoft.PowerPlatform.Dataverse.Client.InternalExtensions
{
///
- /// Organization request/response extenstions
+ /// Organization request/response extensions
///
internal static class RequestResponseExtenstions
{
diff --git a/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj b/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj
index c27794c..9203b93 100644
--- a/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj
+++ b/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj
@@ -35,15 +35,14 @@
-
-
+
+
-
+
-
+
diff --git a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs
index e33b48c..d2bd013 100644
--- a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs
+++ b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs
@@ -1,23 +1,19 @@
+// Ignore Spelling: Dataverse
+
#region using
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
using System.Globalization;
-using System.IO;
using System.Linq;
using System.Security;
using System.ServiceModel;
using System.ServiceModel.Description;
-using System.Xml;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Discovery;
using Microsoft.Xrm.Sdk.Messages;
-using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk.Query;
-using Microsoft.Xrm.Sdk.WebServiceClient;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using System.Net.Http;
@@ -28,10 +24,9 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.PowerPlatform.Dataverse.Client.Model;
-using System.Reflection;
-using Microsoft.Extensions.Caching.Memory;
using Microsoft.PowerPlatform.Dataverse.Client.Connector;
using Microsoft.PowerPlatform.Dataverse.Client.Connector.OnPremises;
+using Microsoft.PowerPlatform.Dataverse.Client.Builder;
#endregion
namespace Microsoft.PowerPlatform.Dataverse.Client
@@ -1444,6 +1439,15 @@ public ServiceClient Clone(System.Reflection.Assembly strongTypeAsm, ILogger log
}
}
+ ///
+ /// Creates a ServiceClient Request builder that allows you to customize a specific request sent to dataverse. This should be used only for a single request and then released.
+ ///
+ /// Service Request builder that is used to create and submit a single request.
+ public ServiceClientRequestBuilder CreateRequestBuilder()
+ {
+ return new ServiceClientRequestBuilder(this);
+ }
+
#region Dataverse DiscoveryServerMethods
///
diff --git a/src/GeneralTools/DataverseClient/Client/Utils/RequestBinderUtil.cs b/src/GeneralTools/DataverseClient/Client/Utils/RequestBinderUtil.cs
new file mode 100644
index 0000000..1f04730
--- /dev/null
+++ b/src/GeneralTools/DataverseClient/Client/Utils/RequestBinderUtil.cs
@@ -0,0 +1,128 @@
+// Ignore Spelling: Dataverse Utils
+
+using Microsoft.Xrm.Sdk;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.ServiceModel.Channels;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerPlatform.Dataverse.Client.Utils
+{
+ internal static class RequestBinderUtil
+ {
+ internal static readonly string HEADERLIST = "HEADERLIST";
+ ///
+ /// Populates the request builder Info into message headers.
+ ///
+ ///
+ ///
+ internal static void ProcessRequestBinderProperties(HttpRequestMessageProperty httpRequestMessageHeaders, OrganizationRequest request)
+ {
+ foreach (var itm in request.Parameters)
+ {
+ if (itm.Key == Utilities.RequestHeaders.X_MS_CORRELATION_REQUEST_ID)
+ {
+ AddorUpdateHeaderProperties(httpRequestMessageHeaders, Utilities.RequestHeaders.X_MS_CORRELATION_REQUEST_ID, itm.Value.ToString());
+ continue;
+ }
+ if (itm.Key == Utilities.RequestHeaders.X_MS_CLIENT_SESSION_ID)
+ {
+ AddorUpdateHeaderProperties(httpRequestMessageHeaders, Utilities.RequestHeaders.X_MS_CLIENT_SESSION_ID, itm.Value.ToString());
+ continue;
+ }
+ if (itm.Key == HEADERLIST)
+ {
+ if (itm.Value is Dictionary hrdList)
+ {
+ foreach (var hdr in hrdList)
+ {
+ AddorUpdateHeaderProperties(httpRequestMessageHeaders, hdr.Key, hdr.Value);
+ }
+ }
+ continue;
+ }
+ }
+ if ( request.Parameters.Count > 0 )
+ {
+ request.Parameters.Remove(Utilities.RequestHeaders.X_MS_CORRELATION_REQUEST_ID);
+ request.Parameters.Remove(Utilities.RequestHeaders.X_MS_CLIENT_SESSION_ID);
+ request.Parameters.Remove(HEADERLIST);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static void GetAdditionalHeaders(Dictionary> customHeaders, OrganizationRequest request)
+ {
+ foreach (var itm in request.Parameters)
+ {
+ if (itm.Key == Utilities.RequestHeaders.X_MS_CORRELATION_REQUEST_ID)
+ {
+ AddorUpdateHeaderProperties(customHeaders, Utilities.RequestHeaders.X_MS_CORRELATION_REQUEST_ID, itm.Value.ToString());
+ continue;
+ }
+ if (itm.Key == Utilities.RequestHeaders.X_MS_CLIENT_SESSION_ID)
+ {
+ AddorUpdateHeaderProperties(customHeaders, Utilities.RequestHeaders.X_MS_CLIENT_SESSION_ID, itm.Value.ToString());
+ continue;
+ }
+ if (itm.Key == HEADERLIST)
+ {
+ if (itm.Value is Dictionary hrdList)
+ {
+ foreach (var hdr in hrdList)
+ {
+ AddorUpdateHeaderProperties(customHeaders, hdr.Key, hdr.Value);
+ }
+ }
+ continue;
+ }
+ }
+ if (request.Parameters.Count > 0)
+ {
+ request.Parameters.Remove(Utilities.RequestHeaders.X_MS_CORRELATION_REQUEST_ID);
+ request.Parameters.Remove(Utilities.RequestHeaders.X_MS_CLIENT_SESSION_ID);
+ request.Parameters.Remove(HEADERLIST);
+ }
+ }
+
+ ///
+ /// Handle adding headers from request builder.
+ ///
+ ///
+ ///
+ ///
+ private static void AddorUpdateHeaderProperties(Dictionary> customHeaders, string hdrKey, string hrdValue)
+ {
+ if (customHeaders.Keys.Contains(hdrKey))
+ {
+ if (customHeaders[hdrKey] == null)
+ customHeaders[hdrKey] = new List();
+
+ customHeaders[hdrKey].Add(hrdValue);
+ }
+ else
+ {
+ customHeaders.Add(hdrKey, new List() { hrdValue });
+ }
+ }
+
+ private static void AddorUpdateHeaderProperties(HttpRequestMessageProperty httpRequestMessageHeaders, string key, string value)
+ {
+ if (httpRequestMessageHeaders.Headers.AllKeys.Contains(key))
+ {
+ httpRequestMessageHeaders.Headers.Add(key, value);
+ }
+ else
+ {
+ httpRequestMessageHeaders.Headers[key] = value;
+ }
+ }
+ }
+}
diff --git a/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs b/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs
index a129de9..2ec7fb7 100644
--- a/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs
+++ b/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs
@@ -20,6 +20,7 @@
using System.Net.Http;
using System.Reflection;
using System.Text;
+using System.Threading.Tasks;
#endregion
namespace Microsoft.PowerPlatform.Dataverse.Client
@@ -226,7 +227,28 @@ public static DiscoveryServer DeterminDiscoveryDataFromOrgDetail(Uri serviceUri,
return null;
}
return null;
+ }
+ public static bool IsValidOrganizationUrl(OrganizationDetail orgInfo)
+ {
+ if (orgInfo != null)
+ {
+ if (orgInfo.Endpoints != null)
+ {
+ foreach (var ep in orgInfo.Endpoints)
+ {
+ if (Uri.IsWellFormedUriString(ep.Value, UriKind.Absolute))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+ }
+ return false;
}
///
@@ -431,7 +453,7 @@ internal static void RetryRequest(OrganizationRequest req, Guid requestTrackingI
retryCount++;
logEntry.LogFailure(req, requestTrackingId, sessionTrackingId, disableConnectionLocking, LockWait, logDt, ex, errorStringCheck, webUriMessageReq: webUriReq);
logEntry.LogRetry(retryCount, req, retryPauseTimeRunning, isThrottled: isThrottled);
- System.Threading.Thread.Sleep(retryPauseTimeRunning);
+ Task.Delay(retryPauseTimeRunning);
}
///
@@ -907,6 +929,10 @@ internal static class RequestHeaders
///
public const string X_MS_CLIENT_REQUEST_ID = "x-ms-client-request-id";
///
+ /// PreRequest CorrlationId to trace request into Dataverse
+ ///
+ public const string X_MS_CORRELATION_REQUEST_ID = "x-ms-correlation-request-id";
+ ///
/// Content type of WebAPI request.
///
public const string CONTENT_TYPE = "Content-Type";
diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/DataverseClient_Core_UnitTests.csproj b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/DataverseClient_Core_UnitTests.csproj
index 95f5f47..1df524e 100644
--- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/DataverseClient_Core_UnitTests.csproj
+++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/DataverseClient_Core_UnitTests.csproj
@@ -12,9 +12,9 @@
-
-
-
+
+
+
diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs
index e5c9d27..d0517a4 100644
--- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs
+++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs
@@ -823,6 +823,7 @@ public void ConnectUsingServiceIdentity_ClientSecret_CtorV1()
// Validate connection
ValidateConnection(client);
+
}
[SkippableConnectionTest]
@@ -1698,6 +1699,50 @@ public void StageSolution_File_DeleteAndPromote_TestAsync()
}
}
+ [SkippableConnectionTest]
+ [Trait("Category", "Live Connect Required")]
+ public async void RequestBuilder_Execute()
+ {
+ // System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
+
+ var Conn_AppID = System.Environment.GetEnvironmentVariable("XUNITCONNTESTAPPID");
+ 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, Ilogger);
+ Assert.True(client.IsReady, "Failed to Create Connection via Constructor");
+
+ Entity a = new Entity("account");
+ a["name"] = "Test Account";
+ a.Id = Guid.NewGuid();
+
+ var trackingId = a.Id;
+
+ var rslt = await client.CreateRequestBuilder().WithCorrelationId(Guid.NewGuid()).WithHeader("User-Agent", "TEST").WithHeader("Foo", "TEST1").CreateAsync(a).ConfigureAwait(false);
+ Assert.IsType(rslt);
+
+ a["name"] = "Test Account - step 2";
+ await client.CreateRequestBuilder().WithCorrelationId(Guid.NewGuid()).WithHeader("User-Agent", "TEST").WithHeader("Foo", "TEST1").UpdateAsync(a).ConfigureAwait(false);
+
+ a["name"] = "Test Account - step 3";
+ UpsertRequest upsert = new UpsertRequest();
+ upsert.Target = a;
+ var upResp = (UpsertResponse) await client.CreateRequestBuilder().WithCorrelationId(Guid.NewGuid()).WithHeader("User-Agent", "TEST").WithHeader("Foo", "TEST1").ExecuteAsync(upsert).ConfigureAwait(false);
+ Assert.IsType(upResp);
+ Assert.False(upResp.RecordCreated);
+
+ // retrieve
+ var ret = (Entity)await client.CreateRequestBuilder().WithCorrelationId(Guid.NewGuid()).WithHeader("User-Agent", "TEST").WithHeader("Foo", "TEST1").RetrieveAsync("account", trackingId, new ColumnSet(true)).ConfigureAwait(false);
+ ret.Should().NotBeNull();
+ ret.Id.Should().Be(trackingId);
+ ret["name"].Should().Be("Test Account - step 3");
+
+ // delete
+ await client.CreateRequestBuilder().WithCorrelationId(Guid.NewGuid()).WithHeader("User-Agent", "TEST").WithHeader("Foo", "TEST1").DeleteAsync("account", trackingId).ConfigureAwait(false);
+
+ }
+
// Not yet implemented
//[SkippableConnectionTest]
//[Trait("Category", "Live Connect Required")]
diff --git a/src/GeneralTools/DataverseClient/UnitTests/LivePackageTestsConsole/LivePackageTestsConsole.csproj b/src/GeneralTools/DataverseClient/UnitTests/LivePackageTestsConsole/LivePackageTestsConsole.csproj
index dbf5dc7..6a0e4de 100644
--- a/src/GeneralTools/DataverseClient/UnitTests/LivePackageTestsConsole/LivePackageTestsConsole.csproj
+++ b/src/GeneralTools/DataverseClient/UnitTests/LivePackageTestsConsole/LivePackageTestsConsole.csproj
@@ -30,7 +30,7 @@
-
+
diff --git a/src/GeneralTools/DataverseClient/UnitTests/LiveTestsConsole/LiveTestsConsole.csproj b/src/GeneralTools/DataverseClient/UnitTests/LiveTestsConsole/LiveTestsConsole.csproj
index 31e247d..d7c7b2b 100644
--- a/src/GeneralTools/DataverseClient/UnitTests/LiveTestsConsole/LiveTestsConsole.csproj
+++ b/src/GeneralTools/DataverseClient/UnitTests/LiveTestsConsole/LiveTestsConsole.csproj
@@ -1,8 +1,9 @@
+ true
Exe
- net462;netcoreapp3.1
+ net462;net6.0
true
DataverseClient-Tests
false
diff --git a/src/SDK-IntelliSense/V9/PublishedXML/CE/Microsoft.PowerPlatform.Dataverse.Client.xml b/src/SDK-IntelliSense/V9/PublishedXML/CE/Microsoft.PowerPlatform.Dataverse.Client.xml
index 723162c..bc6845b 100644
--- a/src/SDK-IntelliSense/V9/PublishedXML/CE/Microsoft.PowerPlatform.Dataverse.Client.xml
+++ b/src/SDK-IntelliSense/V9/PublishedXML/CE/Microsoft.PowerPlatform.Dataverse.Client.xml
@@ -1472,10 +1472,7 @@
Defaults to True.
When true, this setting applies the default connection routing strategy to connections to Dataverse.This will 'prefer' a given node when interacting with Dataverse which improves overall connection performance.When set to false, each call to Dataverse will be routed to any given node supporting your organization.See https://docs.microsoft.com/en-us/powerapps/developer/data-platform/api-limits#remove-the-affinity-cookie for proper use.
-
-
- MaxBufferPoolSize override. - Use under Microsoft Direction only.
-
+
MaxFaultSize override. - Use under Microsoft Direction only.
diff --git a/src/nuspecs/Microsoft.Dynamics.Sdk.Messages.nuspec b/src/nuspecs/Microsoft.Dynamics.Sdk.Messages.nuspec
index 0f7ebf3..f90f158 100644
--- a/src/nuspecs/Microsoft.Dynamics.Sdk.Messages.nuspec
+++ b/src/nuspecs/Microsoft.Dynamics.Sdk.Messages.nuspec
@@ -15,19 +15,19 @@
Dynamics CommonDataService CDS PowerApps PowerPlatform
-
+
-
+
-
+
-
+
-
+
@@ -38,9 +38,6 @@
-
-
-
diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.nuspec b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.nuspec
index 7ff9d3b..fecdfab 100644
--- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.nuspec
+++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.nuspec
@@ -15,16 +15,16 @@
Dynamics CommonDataService CDS PowerApps PowerPlatform ServiceClient Dataverse
-
+
-
+
-
+
-
+
@@ -35,9 +35,6 @@
-
-
-
diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt
index 450d4bd..26058d0 100644
--- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt
+++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt
@@ -7,6 +7,17 @@ Notice:
Note: Only AD on FullFramework, OAuth, Certificate, ClientSecret Authentication types are supported at this time.
++CURRENTRELEASEID++
+Updated Core SDK to 9.2.24044.9795
+Added new ServiceClient method for creating requests called "RequestBuilder" - will allow you to create a request and customized header, user, and other properties as part of there request generation.
+Dependency Changes:
+ Removed
+ System.Security.Cryptography.Algorithms
+ System.Security.Cryptography.ProtectedData
+ System.Drawing.Common
+ Modified
+ System.Configuration.ConfigurationManager to 6.0.0
+
+1.1.17:
Fix for Request ID not reflecting correctly for some requests.
Fix for RequestAdditionalHeadersAsync interface not being forwarded to Cloned copies of DVSC. GitHub Reported - Fix #419
Fix for Clone being called concurrently causing unnecessary calls to dataverse. GitHub reported - Fix #422
diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec
index 99b2ca8..81de30c 100644
--- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec
+++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec
@@ -15,37 +15,37 @@
Dynamics CommonDataService CDS PowerApps PowerPlatform ServiceClient Dataverse
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -58,23 +58,20 @@
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
@@ -86,9 +83,6 @@
-
-
-