Skip to content

Commit

Permalink
Merge pull request #54 from microsoft/release/update/200915072858
Browse files Browse the repository at this point in the history
Adding additional features to CdsServiceClient over WebAPI
  • Loading branch information
MattB-msft authored Sep 15, 2020
2 parents bd5dee5 + 096d25e commit 3b961c7
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 31 deletions.
14 changes: 14 additions & 0 deletions src/Build.Common.StandardAndLegacy.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project>
<PropertyGroup Condition="'$(TargetFrameworks)' == ''">
<TargetFrameworks Condition="'$(OutputType)' == 'Exe'">netcoreapp3.1;net462</TargetFrameworks>
<TargetFrameworks Condition="'$(OutputType)' != 'Exe'">netstandard2.0</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

<PropertyGroup>
<Features>IOperation</Features>
</PropertyGroup>

<Import Project=".\Build.Shared.props" />

</Project>
6 changes: 6 additions & 0 deletions src/Build.Common.core.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
<Features>IOperation</Features>
</PropertyGroup>

<ItemGroup>
<Compile Remove="obj\**" />
<EmbeddedResource Remove="obj\**" />
<None Remove="obj\**" />
</ItemGroup>

<Import Project=".\Build.Shared.props" />

</Project>
88 changes: 88 additions & 0 deletions src/Build.Shared.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<Project>
<!-- msbuild properties shared for dotnetCore and .NET classic projects: -->
<PropertyGroup>
<PackageVersion_AppInsights>2.9.1</PackageVersion_AppInsights>
<PackageVersion_Adal>3.19.8</PackageVersion_Adal>
<PackageVersion_CdsSdk>4.5.2071</PackageVersion_CdsSdk>
<PackageVersion_CDSServerNuget>4.5.3122</PackageVersion_CDSServerNuget>
<PackageVersion_Newtonsoft>10.0.3</PackageVersion_Newtonsoft>
<PackageVersion_RestClientRuntime>2.3.20</PackageVersion_RestClientRuntime>
<PackageVersion_XrmSdk>9.0.2.25</PackageVersion_XrmSdk>
<!-- Test: -->
<PackageVersion_Moq>4.13.1</PackageVersion_Moq>
<PackageVersion_XUnit>2.4.1</PackageVersion_XUnit>
</PropertyGroup>

<PropertyGroup>
<!-- this property must be re-defined in individual .csprojs or a .props file per component area -->
<ComponentAreaName Condition="'$(ComponentAreaName)' == ''">FORGOT-To-Set-ComponentAreaName</ComponentAreaName>
</PropertyGroup>

<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<!-- These variables define the object and binary roots, respectively. -->
<RepoRoot>$([System.IO.Path]::GetFullPath($([System.IO.Path]::Combine($(MSBuildThisFileDirectory), ".."))))</RepoRoot>
<OutputRootDir Condition=" '$(OutputRootDir)' == '' ">$(RepoRoot)\bin\$(Configuration)\$(ComponentAreaName)\$(TargetFramework)</OutputRootDir>
<OutputRootDir>$(OutputRootDir.TrimEnd({'\\'}))</OutputRootDir>
<!-- These variables are the ones that the standard MSBuild targets recognize. -->
<OutDir Condition=" '$(OutDir)' =='' ">$([System.IO.Path]::Combine($(OutputRootDir), $(RelativeOutputPath)))</OutDir>
<!-- OutDir requires a trailing slash to prevent MSB8004 warning -->
<OutDir>$(OutDir.TrimEnd("\\"))\</OutDir>
<!-- Ensures OutputPath always matches OutDir -->
<OutputPath>$(OutDir)</OutputPath>
</PropertyGroup>

<PropertyGroup Condition="'$(SignAssembly)' == 'true'">
<AssemblyOriginatorKeyFile>$(RepoRoot)\build\crmKey\35MSSharedLib1024.snk</AssemblyOriginatorKeyFile>
<DelaySign>true</DelaySign>
</PropertyGroup>

<PropertyGroup>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineConstants>TRACE</DefineConstants>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<DefineConstants>TRACE</DefineConstants>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'CRMINTERNAL|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE;CRMINTERNAL</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>

<PropertyGroup Condition=" '$(Enable_Telemetry)' == 'true' " >
<DefineConstants>$(DefineConstants);PROD_CUSTOMER_TELEMETRY</DefineConstants>
</PropertyGroup>
</Project>
118 changes: 88 additions & 30 deletions src/GeneralTools/CDSClient/Client/CdsServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4982,33 +4982,7 @@ private OrganizationResponse CdsCommand_WebAPIProcess_Execute(OrganizationReques
{
if (cReq.KeyAttributes?.Any() == true)
{

string keycollection = string.Empty;
foreach (var itm in cReq.KeyAttributes)
{
if (itm.Value is EntityReference er)
{
keycollection += $"_{itm.Key}_value='{er.Id.ToString("P")}',";

//IEnumerable<string> keys = cReq.KeyAttributes.Select(s => $"_{s.Key}_value='{((EntityReference)s.Value).Id.ToString().Replace("'", "''")}'");
//keycollection += $"{EntityData.EntitySetName}({string.Join("&", keys)})";

//if (cReq.Id != Guid.Empty)
//{
// var s2 = EntityData.EntitySetName + cReq.Id.ToString("P");
//}


//// Add support for ER for KEY.
//keycollection += $"{itm.Key}@odata.bind='{$"/{metadataUtlity.GetEntityMetadata(Xrm.Sdk.Metadata.EntityFilters.Entity, er.LogicalName).EntitySetName}({er.Id})"}',";
}
else
{
keycollection += $"{itm.Key}='{itm.Value}',";
}
}
keycollection = keycollection.Remove(keycollection.Length - 1); // remove trailing ,
postUri = $"{EntityData.EntitySetName}({keycollection})";
postUri = $"{EntityData.EntitySetName}({Utilities.ParseAltKeyCollection(cReq.KeyAttributes)})";
}
else
{
Expand Down Expand Up @@ -5042,16 +5016,78 @@ private OrganizationResponse CdsCommand_WebAPIProcess_Execute(OrganizationReques
if (req.Parameters.ContainsKey(Utilities.CDSRequestHeaders.SOLUTIONUNIQUENAME))
{
if (req.Parameters[Utilities.CDSRequestHeaders.SOLUTIONUNIQUENAME].GetType() == typeof(string) &&
!String.IsNullOrEmpty((string)req.Parameters[Utilities.CDSRequestHeaders.BYPASSCUSTOMPLUGINEXECUTION]))
!String.IsNullOrEmpty((string)req.Parameters[Utilities.CDSRequestHeaders.SOLUTIONUNIQUENAME]))
{
solutionUniqueNameHeaderValue = req.Parameters[Utilities.CDSRequestHeaders.SOLUTIONUNIQUENAME].ToString();
}
}

// Execute request
bool? suppressDuplicateDetection = null;
if (req.Parameters.ContainsKey(Utilities.CDSRequestHeaders.SUPPRESSDUPLICATEDETECTION))
{
if (req.Parameters[Utilities.CDSRequestHeaders.SUPPRESSDUPLICATEDETECTION].GetType() == typeof(bool) &&
(bool)req.Parameters[Utilities.CDSRequestHeaders.SUPPRESSDUPLICATEDETECTION])
{
suppressDuplicateDetection = true;
}
}

string tagValue = string.Empty;
if (req.Parameters.ContainsKey(Utilities.CDSRequestHeaders.TAG))
{
if (req.Parameters[Utilities.CDSRequestHeaders.TAG].GetType() == typeof(string) &&
!String.IsNullOrEmpty((string)req.Parameters[Utilities.CDSRequestHeaders.TAG]))
{
tagValue = req.Parameters[Utilities.CDSRequestHeaders.TAG].ToString();
}
}

string rowVersion = string.Empty;
string IfMatchHeaderTag = string.Empty;
if ( req.Parameters.ContainsKey(Utilities.CDSRequestHeaders.CONCURRENCYBEHAVIOR) &&
(ConcurrencyBehavior)req.Parameters[Utilities.CDSRequestHeaders.CONCURRENCYBEHAVIOR] != ConcurrencyBehavior.Default)
{
// Found concurrency flag.
if (!string.IsNullOrEmpty(cReq.RowVersion))
{
rowVersion = cReq.RowVersion;
// Now manage behavior.
// if IfRowVersionMatches == Upsert/update/Delete should only work if record exists by rowRowVersion. == If-Match + RowVersion.
// If AlwaysOverwrite == Upsert/update/Delete should only work if record exists at all == No IF-MatchTag.
if ((ConcurrencyBehavior)req.Parameters[Utilities.CDSRequestHeaders.CONCURRENCYBEHAVIOR] == ConcurrencyBehavior.AlwaysOverwrite)
{
IfMatchHeaderTag = "If-Match";
rowVersion = "*";
}
if ((ConcurrencyBehavior)req.Parameters[Utilities.CDSRequestHeaders.CONCURRENCYBEHAVIOR] == ConcurrencyBehavior.IfRowVersionMatches)
{
IfMatchHeaderTag = "If-Match";
}
}
else
{
CdsClientOperationException opEx = new CdsClientOperationException("Request Failed, RowVersion is missing and is required when ConcurrencyBehavior is set to a value other then Default.");
logEntry.Log(opEx);
return null;
}
}

// Setup headers.
Dictionary<string, List<string>> headers = new Dictionary<string, List<string>>();
headers.Add("Prefer", new List<string>() { "odata.include-annotations=*" });

if (!string.IsNullOrEmpty(IfMatchHeaderTag))
{
if (rowVersion != "*")
{
headers.Add(IfMatchHeaderTag, new List<string>() { $"W/\"{rowVersion}\"" });
}
else
{
headers.Add(IfMatchHeaderTag, new List<string>() { $"*" });
}
}

if (bypassPluginExecution && ConnectedOrgVersion >= _minAllowBypassCustomPluginVersion)
{
headers.Add($"{Utilities.CDSRequestHeaders.CDSHEADERPROPERTYPREFIX}{Utilities.CDSRequestHeaders.BYPASSCUSTOMPLUGINEXECUTION}", new List<string>() { "true" });
Expand All @@ -5062,6 +5098,28 @@ private OrganizationResponse CdsCommand_WebAPIProcess_Execute(OrganizationReques
headers.Add($"{Utilities.CDSRequestHeaders.CDSHEADERPROPERTYPREFIX}{Utilities.CDSRequestHeaders.SOLUTIONUNIQUENAME}", new List<string>() { solutionUniqueNameHeaderValue });
}

if (suppressDuplicateDetection.HasValue)
{
headers.Add($"{Utilities.CDSRequestHeaders.CDSHEADERPROPERTYPREFIX}{Utilities.CDSRequestHeaders.SUPPRESSDUPLICATEDETECTION}", new List<string>() { "true" });
}

string addedQueryParams = "";
// modify post URI
if (!string.IsNullOrEmpty(tagValue))
{
//UriBuilder uriBuilder = new UriBuilder(postUri);
var paramValues = System.Web.HttpUtility.ParseQueryString(addedQueryParams);
paramValues.Add($"{Utilities.CDSRequestHeaders.TAG}", tagValue);
addedQueryParams = paramValues.ToString();
}

// add queryParms to the PostUri.
if (!string.IsNullOrEmpty(addedQueryParams))
{
postUri = $"{postUri}?{addedQueryParams}";
}

// Execute request
var sResp = CdsCommand_WebExecute(postUri, bodyOfRequest, methodToExecute, headers, "application/json", logMessageTag).Result;
if (sResp != null && sResp.IsSuccessStatusCode)
{
Expand Down Expand Up @@ -5606,7 +5664,7 @@ private void LogException(OrganizationRequest req, Exception ex, string errorStr
defaultODataHeaders.Add("Accept", "application/json");
defaultODataHeaders.Add("OData-MaxVersion", "4.0");
defaultODataHeaders.Add("OData-Version", "4.0");
defaultODataHeaders.Add("If-None-Match", "");
//defaultODataHeaders.Add("If-None-Match", "");

// Supported Version Check.
if (!(ConnectedOrgVersion > _minWebAPISupportedVersion))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.7.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'net472' or '$(TargetFramework)' == 'net48'">
<Reference Include="System.Web" />
</ItemGroup>

</Project>
53 changes: 52 additions & 1 deletion src/GeneralTools/CDSClient/Client/Utils/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,19 @@ internal static ExpandoObject ToExpandoObject(string entityName, AttributeCollec
throw new CdsClientOperationException($"Entity Reference {key.ToLower()} was not found for entity {entityName}.", null);
}

value = $"/{mUtil.GetEntityMetadata(Xrm.Sdk.Metadata.EntityFilters.Entity, entityReference.LogicalName).EntitySetName}({entityReference.Id})";
string entityReferanceValue = string.Empty;
// process ER Value
if (entityReference.KeyAttributes?.Any() == true)
{
entityReferanceValue = ParseAltKeyCollection(entityReference.KeyAttributes);
}
else
{
entityReferanceValue = entityReference.Id.ToString();
}


value = $"/{mUtil.GetEntityMetadata(Xrm.Sdk.Metadata.EntityFilters.Entity, entityReference.LogicalName).EntitySetName}({entityReferanceValue})";
}
else
{
Expand Down Expand Up @@ -395,6 +407,27 @@ internal static ExpandoObject ToExpandoObject(string entityName, AttributeCollec
return ((ExpandoObject)(expandoObject));
}

/// <summary>
/// Parses Key attribute collection for alt key support.
/// </summary>
/// <param name="keyValues">alt key's for object</param>
/// <returns>webAPI compliant key string</returns>
internal static string ParseAltKeyCollection (KeyAttributeCollection keyValues)
{
string keycollection = string.Empty;
foreach (var itm in keyValues)
{
if (itm.Value is EntityReference er)
{
keycollection += $"_{itm.Key}_value='{er.Id.ToString("P")}',";
}
else
{
keycollection += $"{itm.Key}='{itm.Value}',";
}
}
return keycollection.Remove(keycollection.Length - 1); // remove trailing ,
}
/// <summary>
/// List of entities to retry retrieves on.
/// </summary>
Expand Down Expand Up @@ -470,9 +503,27 @@ internal static class CDSRequestHeaders

/// <summary>
/// key used to apply the operation to a given solution.
/// See: https://docs.microsoft.com/powerapps/developer/common-data-service/org-service/use-messages#passing-optional-parameters-with-a-request
/// </summary>
public const string SOLUTIONUNIQUENAME = "SolutionUniqueName";

/// <summary>
/// used to apply duplicate detection behavior to a given request.
/// See: https://docs.microsoft.com/powerapps/developer/common-data-service/org-service/use-messages#passing-optional-parameters-with-a-request
/// </summary>
public const string SUPPRESSDUPLICATEDETECTION = "SuppressDuplicateDetection";

/// <summary>
/// used to pass data though CDS to a plugin or downstream system on a request.
/// See: https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/org-service/use-messages#add-a-shared-variable-from-the-organization-service
/// </summary>
public const string TAG = "tag";

/// <summary>
/// used to identify concurrencybehavior property in an organization request.
/// </summary>
public const string CONCURRENCYBEHAVIOR = "ConcurrencyBehavior";

/// <summary>
/// CDS Platform Property Prefix
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/nuspecs/Microsoft.Dynamics.Sdk.Messages.ReleaseNotes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Notice:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.crm.sdk.messages?view=dynamics-general-ce-9

++CURRENTRELEASEID++
No updates here.

0.2.17-Alpha:
No Updates here.

0.2.16-Alpha:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ 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.

0.2.17-Alpha:
Added IntelliSense Doc Support
Added support for bypassing custom Plug-in Execution during SDK Operation.
This is a special use capability that requires a specialized permission in the CDS infrastructure to use.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Notice:
Note: that only OAuth, Certificate, ClientSecret Authentication types are supported at this time.

++CURRENTRELEASEID++
Added support for Alternate key use on entity references as part of Create Update Delete Operations running over the webAPI.
Added concurrency support to Create Update Delete Operations running over the webAPI.

0.2.17-Alpha:
***BREAKING CHANGE***
Renamed Exception CdsConnectionException to CdsClientConnectionException. No other change to the Exception
***
Expand Down

0 comments on commit 3b961c7

Please sign in to comment.