|
| 1 | +--- |
| 2 | +title: "Conditional Operation sample (C#) (Common Data Service)| Microsoft Docs" |
| 3 | +description: "This sample shows how to perform conditional message operations when accessing records of the Common Data Service." |
| 4 | +ms.custom: "" |
| 5 | +ms.date: 07/16/2020 |
| 6 | +ms.service: powerapps |
| 7 | +applies_to: |
| 8 | + - "Dynamics 365 (online)" |
| 9 | +author: "JimDaly" |
| 10 | +ms.author: "pehecke" |
| 11 | +ms.reviewer: "pehecke" |
| 12 | +search.audienceType: |
| 13 | + - developer |
| 14 | +search.app: |
| 15 | + - PowerApps |
| 16 | + - D365CE |
| 17 | +--- |
| 18 | +# Conditional Operations sample (C#) |
| 19 | + |
| 20 | +This sample shows how to perform conditional message operations when accessing entity instance records of the Common Data Service. The sample uses the Common Data Service Web API and the CDSWebApiService class. |
| 21 | + |
| 22 | +Messages using a conditional statement, such as "If-None-Match", in the message header are sent to Common Data Service. |
| 23 | + |
| 24 | +> [!NOTE] |
| 25 | +> This sample implements the Common Data Service operations and console output detailed in [Web API Conditional Operations Sample](../web-api-conditional-operations-sample.md) and uses the methods available in the [CDSWebApiService](cdswebapiservice.md) class for message processing, performance enhancements, and error management. |
| 26 | +
|
| 27 | +## Prerequisites |
| 28 | + |
| 29 | +The following is required to build and run the sample: |
| 30 | + |
| 31 | +- Microsoft Visual Studio 2019. |
| 32 | +- Access to Common Data Service with privileges to perform the operations described above. |
| 33 | + |
| 34 | +## How to run this sample |
| 35 | + |
| 36 | +1. Go to the [PowerApps-Samples](https://github.com/microsoft/PowerApps-Samples/) repository and either clone or download the compressed samples repository. If you downloaded the compressed file, extract its contents into a local folder. |
| 37 | + |
| 38 | +1. Navigate to the cds/webapi/C#/[ConditionalOperations](https://github.com/microsoft/PowerApps-Samples/tree/master/cds/webapi/C%23/ConditionalOperations) folder and load the solution file into Visual Studio. |
| 39 | + |
| 40 | +1. Edit the App.config file that is shared by several of the samples and set appropriate values for the Common Data Service environment you intend to use: connectionString `Url`, `UserPrincipalName`, and `Password`. You only need to perform this step once for all samples that share this file. |
| 41 | + |
| 42 | +1. Press F5 to build and run the program in debug mode. |
| 43 | + |
| 44 | +## Code listing |
| 45 | + |
| 46 | +This sample depends on the assembly built from in the CDSWebApiService project. For information on the methods this class provides see [CDSWebApiService class](cdswebapiservice.md). |
| 47 | + |
| 48 | +The following is the code from the Program.cs file: |
| 49 | + |
| 50 | +```csharp |
| 51 | +using Newtonsoft.Json; |
| 52 | +using Newtonsoft.Json.Linq; |
| 53 | +using System; |
| 54 | +using System.Collections.Generic; |
| 55 | +using System.Configuration; |
| 56 | +using System.Linq; |
| 57 | +using System.Net; |
| 58 | + |
| 59 | +namespace PowerApps.Samples |
| 60 | +{ |
| 61 | + /// <summary> |
| 62 | + /// This program demonstrates use of conditional operations with the |
| 63 | + /// Common Data Service Web API. |
| 64 | + /// </summary> |
| 65 | + class Program |
| 66 | + { |
| 67 | + //Get environment configuration data from the connection string in the App.config file. |
| 68 | + static readonly string connectionString = |
| 69 | + ConfigurationManager.ConnectionStrings["Connect"].ConnectionString; |
| 70 | + static readonly ServiceConfig config = new ServiceConfig(connectionString); |
| 71 | + |
| 72 | + static void Main() |
| 73 | + { |
| 74 | + // Save the URIs for entity records created in this sample. so they |
| 75 | + // can be deleted later. |
| 76 | + List<Uri> entityUris = new List<Uri>(); |
| 77 | + |
| 78 | + try |
| 79 | + { |
| 80 | + // Use the wrapper class that handles message processing, error handling, and more. |
| 81 | + using (CDSWebApiService svc = new CDSWebApiService(config)) |
| 82 | + { |
| 83 | + Console.WriteLine("--Starting conditional operations demonstration--\n"); |
| 84 | + |
| 85 | + #region Create required records |
| 86 | + // Create an account record |
| 87 | + var account1 = new JObject { |
| 88 | + { "name", "Contoso Ltd" }, |
| 89 | + { "telephone1", "555-0000" }, //Phone number value increments with each update attempt |
| 90 | + { "revenue", 5000000}, |
| 91 | + { "description", "Parent company of Contoso Pharmaceuticals, etc."} }; |
| 92 | + |
| 93 | + Uri account1Uri = svc.PostCreate("accounts", account1); |
| 94 | + entityUris.Add(account1Uri); // Track any created records |
| 95 | +
|
| 96 | + // Retrieve the account record that was just created. |
| 97 | + string queryOptions = "?$select=name,revenue,telephone1,description"; |
| 98 | + var retrievedaccount1 = svc.Get(account1Uri.ToString() + queryOptions); |
| 99 | + |
| 100 | + // Store the ETag value from the retrieved record |
| 101 | + string initialAcctETagVal = retrievedaccount1["@odata.etag"].ToString(); |
| 102 | + |
| 103 | + Console.WriteLine("Created and retrieved the initial account, shown below:"); |
| 104 | + Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented)); |
| 105 | + #endregion Create required records |
| 106 | + |
| 107 | + #region Conditional GET |
| 108 | + Console.WriteLine("\n** Conditional GET demonstration **"); |
| 109 | + |
| 110 | + // Attempt to retrieve the account record using a conditional GET defined by a message header with |
| 111 | + // the current ETag value. |
| 112 | + try |
| 113 | + { |
| 114 | + retrievedaccount1 = svc.Get( |
| 115 | + path: account1Uri.ToString() + queryOptions, |
| 116 | + headers: new Dictionary<string, List<string>> { |
| 117 | + { "If-None-Match", new List<string> {initialAcctETagVal}}} |
| 118 | + ); |
| 119 | + |
| 120 | + // Not expected; the returned response contains content. |
| 121 | + Console.WriteLine("Instance retrieved using ETag: {0}", initialAcctETagVal); |
| 122 | + Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented)); |
| 123 | + } |
| 124 | + catch (AggregateException ae) // Message was not successful |
| 125 | + { |
| 126 | + ae.Handle((x) => |
| 127 | + { |
| 128 | + if (x is ServiceException) // This we know how to handle. |
| 129 | + { |
| 130 | + var e = x as ServiceException; |
| 131 | + if (e.StatusCode == (int)HttpStatusCode.NotModified) // Expected result |
| 132 | + { |
| 133 | + Console.WriteLine("Account record retrieved using ETag: {0}", initialAcctETagVal); |
| 134 | + Console.WriteLine("Expected outcome: Entity was not modified so nothing was returned."); |
| 135 | + return true; |
| 136 | + } |
| 137 | + } |
| 138 | + return false; // Let anything else stop the application. |
| 139 | + }); |
| 140 | + } |
| 141 | + |
| 142 | + // Modify the account instance by updating the telephone1 attribute |
| 143 | + svc.Put(account1Uri, "telephone1", "555-0001"); |
| 144 | + Console.WriteLine("\n\bAccount telephone number updated to '555-0001'.\n"); |
| 145 | + |
| 146 | + // Re-attempt to retrieve using conditional GET defined by a message header with |
| 147 | + // the current ETag value. |
| 148 | + try |
| 149 | + { |
| 150 | + retrievedaccount1 = svc.Get( |
| 151 | + path: account1Uri.ToString() + queryOptions, |
| 152 | + headers: new Dictionary<string, List<string>> { |
| 153 | + { "If-None-Match", new List<string> {initialAcctETagVal}}} |
| 154 | + ); |
| 155 | + |
| 156 | + // Expected result |
| 157 | + Console.WriteLine("Modified account record retrieved using ETag: {0}", initialAcctETagVal); |
| 158 | + Console.WriteLine("Notice the update ETag value and telephone number"); |
| 159 | + } |
| 160 | + catch (ServiceException e) |
| 161 | + { |
| 162 | + if (e.StatusCode == (int)HttpStatusCode.NotModified) // Not expected |
| 163 | + { |
| 164 | + Console.WriteLine("Unexpected outcome: Entity was modified so something should be returned."); |
| 165 | + } |
| 166 | + else { throw e; } |
| 167 | + } |
| 168 | + |
| 169 | + // Save the updated ETag value |
| 170 | + var updatedAcctETagVal = retrievedaccount1["@odata.etag"].ToString(); |
| 171 | + |
| 172 | + // Display ty updated record |
| 173 | + Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented)); |
| 174 | + #endregion Conditional GET |
| 175 | + |
| 176 | + #region Optimistic concurrency on delete and update |
| 177 | + Console.WriteLine("\n** Optimistic concurrency demonstration **"); |
| 178 | + |
| 179 | + // Attempt to delete original account (if matches original ETag value). |
| 180 | + // If you replace "initialAcctETagVal" with "updatedAcctETagVal", the delete will |
| 181 | + // succeed. However, we want the delete to fail for now to demonstrate use of the ETag. |
| 182 | + Console.WriteLine("Attempting to delete the account using the original ETag value"); |
| 183 | + |
| 184 | + try |
| 185 | + { |
| 186 | + svc.Delete( |
| 187 | + uri: account1Uri, |
| 188 | + headers: new Dictionary<string, List<string>> { |
| 189 | + { "If-Match", new List<string> {initialAcctETagVal}}} |
| 190 | + ); |
| 191 | + |
| 192 | + // Not expected; this code should not execute. |
| 193 | + Console.WriteLine("Account deleted"); |
| 194 | + } |
| 195 | + catch (ServiceException e) |
| 196 | + { |
| 197 | + if (e.StatusCode == (int)HttpStatusCode.PreconditionFailed) // Expected result |
| 198 | + { |
| 199 | + Console.WriteLine("Expected Error: The version of the account record no" + |
| 200 | + " longer matches the original ETag."); |
| 201 | + Console.WriteLine("\tAccount not deleted using ETag '{0}', status code: '{1}'.", |
| 202 | + initialAcctETagVal, e.StatusCode); |
| 203 | + } |
| 204 | + else { throw e; } |
| 205 | + } |
| 206 | + |
| 207 | + Console.WriteLine("Attempting to update the account using the original ETag value"); |
| 208 | + JObject accountUpdate = new JObject() { |
| 209 | + { "telephone1", "555-0002" }, |
| 210 | + { "revenue", 6000000 } |
| 211 | + }; |
| 212 | + |
| 213 | + try |
| 214 | + { |
| 215 | + svc.Patch( |
| 216 | + uri: account1Uri, |
| 217 | + body: accountUpdate, |
| 218 | + headers: new Dictionary<string, List<string>> { |
| 219 | + { "If-Match", new List<string> {initialAcctETagVal}}} |
| 220 | + ); |
| 221 | + |
| 222 | + // Not expected; this code should not execute. |
| 223 | + Console.WriteLine("Account updated using original ETag {0}", initialAcctETagVal); |
| 224 | + } |
| 225 | + catch (ServiceException e) |
| 226 | + { |
| 227 | + if (e.StatusCode == (int)HttpStatusCode.PreconditionFailed) // Expected error |
| 228 | + { |
| 229 | + Console.WriteLine("Expected Error: The version of the existing record doesn't " |
| 230 | + + "match the ETag property provided."); |
| 231 | + Console.WriteLine("\tAccount not updated using ETag '{0}', status code: '{1}'.", |
| 232 | + initialAcctETagVal, (int)e.StatusCode); |
| 233 | + } |
| 234 | + else { throw e; } |
| 235 | + } |
| 236 | + |
| 237 | + // Reattempt update if matches current ETag value. |
| 238 | + accountUpdate["telephone1"] = "555-0003"; |
| 239 | + Console.WriteLine("Attempting to update the account using the current ETag value"); |
| 240 | + try |
| 241 | + { |
| 242 | + svc.Patch( |
| 243 | + uri: account1Uri, |
| 244 | + body: accountUpdate, |
| 245 | + headers: new Dictionary<string, List<string>> { |
| 246 | + { "If-Match", new List<string> { updatedAcctETagVal }} } |
| 247 | + ); |
| 248 | + |
| 249 | + // Expected program flow; this code should execute. |
| 250 | + Console.WriteLine("\nAccount successfully updated using ETag: {0}.", |
| 251 | + updatedAcctETagVal); |
| 252 | + } |
| 253 | + catch (ServiceException e) |
| 254 | + { |
| 255 | + if (e.StatusCode == (int)HttpStatusCode.PreconditionFailed) // Not expected |
| 256 | + { |
| 257 | + Console.WriteLine("Unexpected status code: '{0}'", (int)e.StatusCode); |
| 258 | + } |
| 259 | + else { throw e; } |
| 260 | + } |
| 261 | + |
| 262 | + // Retrieve and output current account state. |
| 263 | + retrievedaccount1 = svc.Get(account1Uri.ToString() + queryOptions); |
| 264 | + |
| 265 | + Console.WriteLine("\nBelow is the final state of the account"); |
| 266 | + Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented)); |
| 267 | + #endregion Optimistic concurrency on delete and update |
| 268 | + |
| 269 | + #region Delete created records |
| 270 | + |
| 271 | + // Delete (or keep) all the created entity records. |
| 272 | + Console.Write("\nDo you want these entity records deleted? (y/n) [y]: "); |
| 273 | + String answer = Console.ReadLine().Trim(); |
| 274 | + |
| 275 | + if (!(answer.StartsWith("y") || answer.StartsWith("Y") || answer == string.Empty)) |
| 276 | + entityUris.Clear(); |
| 277 | + |
| 278 | + foreach (Uri entityUrl in entityUris) svc.Delete(entityUrl); |
| 279 | + |
| 280 | + #endregion Delete created records |
| 281 | + } |
| 282 | + } |
| 283 | + catch (ServiceException e) |
| 284 | + { |
| 285 | + Console.WriteLine("Message send response: status code {0}, {1}", |
| 286 | + e.StatusCode, e.ReasonPhrase); |
| 287 | + } |
| 288 | + } |
| 289 | + } |
| 290 | +} |
| 291 | +``` |
| 292 | + |
| 293 | +### See also |
| 294 | + |
| 295 | +[Perform conditional operations using the Web API](../perform-conditional-operations-using-web-api.md) |
| 296 | +[Web API Conditional Operations Sample](../web-api-conditional-operations-sample.md) |
| 297 | +[Use the Common Data Service Web API](../overview.md) |
0 commit comments