• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rwjdk / TrelloDotNet / 23755519059

30 Mar 2026 04:25PM UTC coverage: 78.106% (-1.0%) from 79.144%
23755519059

push

github

rwjdk
2.6.0 (CRUD of Custom Fields)

2632 of 3636 branches covered (72.39%)

Branch coverage included in aggregate %.

0 of 89 new or added lines in 5 files covered. (0.0%)

4 existing lines in 1 file now uncovered.

4667 of 5709 relevant lines covered (81.75%)

145.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

31.84
/src/TrelloDotNet/TrelloClient.CustomFields.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Globalization;
4
using System.Text.Json.Nodes;
5
using System.Threading;
6
using System.Threading.Tasks;
7
using System.Web;
8
using TrelloDotNet.Control;
9
using TrelloDotNet.Model;
10
using TrelloDotNet.Model.Options.AddCustomFieldOptions;
11
using TrelloDotNet.Model.Options.UpdateCustomFieldOptions;
12

13
// ReSharper disable UnusedMember.Global
14

15
namespace TrelloDotNet
16
{
17
    public partial class TrelloClient
18
    {
19
        /// <summary>
20
        /// Updates the value of a custom field of type 'Checkbox' on a card.
21
        /// </summary>
22
        /// <remarks>
23
        /// Tip: To remove a value from a custom field use .ClearCustomFieldValueOnCardAsync()
24
        /// </remarks>
25
        /// <param name="cardId">ID of the card to update the custom field on</param>
26
        /// <param name="customField">The custom field to update (must be of type 'Checkbox')</param>
27
        /// <param name="newValue">The new boolean value to set</param>
28
        /// <param name="cancellationToken">Cancellation Token</param>
29
        public async Task UpdateCustomFieldValueOnCardAsync(string cardId, CustomField customField, bool newValue, CancellationToken cancellationToken = default)
30
        {
31
            string payload;
32
            switch (customField.Type)
2!
33
            {
34
                case CustomFieldType.Checkbox:
35
                    string valueAsString = newValue ? "true" : "false";
2!
36
                    payload = $"{{\"value\": {{ \"checked\": \"{HttpUtility.JavaScriptStringEncode(valueAsString)}\" }}}}";
2✔
37
                    break;
2✔
38
                case CustomFieldType.Date:
39
                case CustomFieldType.List:
40
                case CustomFieldType.Number:
41
                case CustomFieldType.Text:
42
                default:
43
                    throw new ArgumentOutOfRangeException(nameof(customField), "Only a custom field of type 'Checkbox' can be set with a bool value");
×
44
            }
45

46
            await SendCustomFieldChangeRequestAsync(cardId, customField, payload, cancellationToken);
2✔
47
        }
2✔
48

49
        /// <summary>
50
        /// Updates the value of a custom field of type 'Date' on a card.
51
        /// </summary>
52
        /// <remarks>
53
        /// Tip: To remove a value from a custom field use .ClearCustomFieldValueOnCardAsync()
54
        /// </remarks>
55
        /// <param name="cardId">ID of the card to update the custom field on</param>
56
        /// <param name="customField">The custom field to update (must be of type 'Date')</param>
57
        /// <param name="newValue">The new date value to set</param>
58
        /// <param name="cancellationToken">Cancellation Token</param>
59
        public async Task UpdateCustomFieldValueOnCardAsync(string cardId, CustomField customField, DateTimeOffset newValue, CancellationToken cancellationToken = default)
60
        {
61
            string payload;
62
            switch (customField.Type)
2!
63
            {
64
                case CustomFieldType.Date:
65
                    string valueAsString = newValue.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture);
2✔
66
                    payload = $"{{\"value\": {{ \"date\": \"{HttpUtility.JavaScriptStringEncode(valueAsString)}\" }}}}";
2✔
67
                    break;
2✔
68
                case CustomFieldType.Checkbox:
69
                case CustomFieldType.List:
70
                case CustomFieldType.Number:
71
                case CustomFieldType.Text:
72
                default:
73
                    throw new ArgumentOutOfRangeException(nameof(customField), "Only a custom field of type 'Date' can be set with a DateTimeOffset value");
×
74
            }
75

76
            await SendCustomFieldChangeRequestAsync(cardId, customField, payload, cancellationToken);
2✔
77
        }
2✔
78

79
        /// <summary>
80
        /// Updates the value of a custom field of type 'Number' on a card using an integer value.
81
        /// </summary>
82
        /// <remarks>
83
        /// Tip: To remove a value from a custom field use .ClearCustomFieldValueOnCardAsync()
84
        /// </remarks>
85
        /// <param name="cardId">ID of the card to update the custom field on</param>
86
        /// <param name="customField">The custom field to update (must be of type 'Number')</param>
87
        /// <param name="newValue">The new integer value to set</param>
88
        /// <param name="cancellationToken">Cancellation Token</param>
89
        public async Task UpdateCustomFieldValueOnCardAsync(string cardId, CustomField customField, int newValue, CancellationToken cancellationToken = default)
90
        {
91
            string payload;
92
            switch (customField.Type)
2!
93
            {
94
                case CustomFieldType.Number:
95
                    string valueAsString = newValue.ToString(CultureInfo.InvariantCulture);
2✔
96
                    payload = $"{{\"value\": {{ \"number\": \"{HttpUtility.JavaScriptStringEncode(valueAsString)}\" }}}}";
2✔
97
                    break;
2✔
98
                case CustomFieldType.Checkbox:
99
                case CustomFieldType.Date:
100
                case CustomFieldType.List:
101
                case CustomFieldType.Text:
102
                default:
103
                    throw new ArgumentOutOfRangeException(nameof(customField), "Only a custom field of type 'Number' can be set with a integer value");
×
104
            }
105

106
            await SendCustomFieldChangeRequestAsync(cardId, customField, payload, cancellationToken);
2✔
107
        }
2✔
108

109
        /// <summary>
110
        /// Updates the value of a custom field of type 'Number' on a card using a decimal value.
111
        /// </summary>
112
        /// <remarks>
113
        /// Tip: To remove a value from a custom field use .ClearCustomFieldValueOnCardAsync()
114
        /// </remarks>
115
        /// <param name="cardId">ID of the card to update the custom field on</param>
116
        /// <param name="customField">The custom field to update (must be of type 'Number')</param>
117
        /// <param name="newValue">The new decimal value to set</param>
118
        /// <param name="cancellationToken">Cancellation Token</param>
119
        public async Task UpdateCustomFieldValueOnCardAsync(string cardId, CustomField customField, decimal newValue, CancellationToken cancellationToken = default)
120
        {
121
            string payload;
122
            switch (customField.Type)
2!
123
            {
124
                case CustomFieldType.Number:
125
                    string valueAsString = newValue.ToString(CultureInfo.InvariantCulture);
2✔
126
                    payload = $"{{\"value\": {{ \"number\": \"{HttpUtility.JavaScriptStringEncode(valueAsString)}\" }}}}";
2✔
127
                    break;
2✔
128
                case CustomFieldType.Checkbox:
129
                case CustomFieldType.Date:
130
                case CustomFieldType.List:
131
                case CustomFieldType.Text:
132
                default:
133
                    throw new ArgumentOutOfRangeException(nameof(customField), "Only a custom field of type 'Number' can be set with a decimal value");
×
134
            }
135

136
            await SendCustomFieldChangeRequestAsync(cardId, customField, payload, cancellationToken);
2✔
137
        }
2✔
138

139
        /// <summary>
140
        /// Updates the value of a custom field of type 'List' on a card using a custom field option.
141
        /// </summary>
142
        /// <remarks>
143
        /// Tip: To remove a value from a custom field use .ClearCustomFieldValueOnCardAsync()
144
        /// </remarks>
145
        /// <param name="cardId">ID of the card to update the custom field on</param>
146
        /// <param name="customField">The custom field to update (must be of type 'List')</param>
147
        /// <param name="newValue">The new custom field option to set</param>
148
        /// <param name="cancellationToken">Cancellation Token</param>
149
        public async Task UpdateCustomFieldValueOnCardAsync(string cardId, CustomField customField, CustomFieldOption newValue, CancellationToken cancellationToken = default)
150
        {
151
            string payload;
152
            switch (customField.Type)
2!
153
            {
154
                case CustomFieldType.List:
155
                    string valueAsString = string.Empty;
2✔
156
                    if (newValue != null)
2✔
157
                    {
158
                        valueAsString = newValue.Id;
2✔
159
                    }
160

161
                    payload = $"{{\"idValue\": \"{HttpUtility.JavaScriptStringEncode(valueAsString)}\" }}";
2✔
162
                    break;
2✔
163
                case CustomFieldType.Checkbox:
164
                case CustomFieldType.Date:
165
                case CustomFieldType.Number:
166
                case CustomFieldType.Text:
167
                default:
168
                    throw new ArgumentOutOfRangeException(nameof(customField), "Only a custom field of type 'List' can be set with a CustomFieldOption value");
×
169
            }
170

171
            await SendCustomFieldChangeRequestAsync(cardId, customField, payload, cancellationToken);
2✔
172
        }
2✔
173

174
        /// <summary>
175
        /// Updates the value of a custom field on a card using a string value. The type of the custom field determines how the value is interpreted.
176
        /// </summary>
177
        /// <remarks>
178
        /// Tip: To remove a value from a custom field use .ClearCustomFieldValueOnCardAsync()
179
        /// </remarks>
180
        /// <param name="cardId">ID of the card to update the custom field on</param>
181
        /// <param name="customField">The custom field to update</param>
182
        /// <param name="newValue">The new string value to set</param>
183
        /// <param name="cancellationToken">Cancellation Token</param>
184
        public async Task UpdateCustomFieldValueOnCardAsync(string cardId, CustomField customField, string newValue, CancellationToken cancellationToken = default)
185
        {
186
            string payload;
187
            switch (customField.Type)
2!
188
            {
189
                case CustomFieldType.Checkbox:
190
                    payload = $"{{\"value\": {{ \"checked\": \"{HttpUtility.JavaScriptStringEncode(newValue)}\" }}}}";
×
191
                    break;
×
192
                case CustomFieldType.Date:
193
                    payload = $"{{\"value\": {{ \"date\": \"{HttpUtility.JavaScriptStringEncode(newValue)}\" }}}}";
×
194
                    break;
×
195
                case CustomFieldType.List:
196
                    payload = $"{{\"idValue\": \"{HttpUtility.JavaScriptStringEncode(newValue)}\" }}";
×
197
                    break;
×
198
                case CustomFieldType.Number:
199
                    payload = $"{{\"value\": {{ \"number\": \"{HttpUtility.JavaScriptStringEncode(newValue)}\" }}}}";
×
200
                    break;
×
201
                case CustomFieldType.Text:
202
                    payload = $"{{\"value\": {{ \"text\": \"{HttpUtility.JavaScriptStringEncode(newValue)}\" }}}}";
2✔
203
                    break;
2✔
204
                default:
205
                    throw new ArgumentOutOfRangeException();
×
206
            }
207

208
            await SendCustomFieldChangeRequestAsync(cardId, customField, payload, cancellationToken);
2✔
209
        }
2✔
210

211
        /// <summary>
212
        /// Clears the value of a custom field on a card, removing any value that was previously set.
213
        /// </summary>
214
        /// <param name="cardId">ID of the card to clear the custom field on</param>
215
        /// <param name="customField">The custom field to clear</param>
216
        /// <param name="cancellationToken">Cancellation Token</param>
217
        public async Task ClearCustomFieldValueOnCardAsync(string cardId, CustomField customField, CancellationToken cancellationToken = default)
218
        {
219
            string payload;
220
            switch (customField.Type)
5!
221
            {
222
                case CustomFieldType.Checkbox:
223
                case CustomFieldType.Date:
224
                case CustomFieldType.Number:
225
                case CustomFieldType.Text:
226
                    payload = "{\"value\": \"\" }";
4✔
227
                    break;
4✔
228
                case CustomFieldType.List:
229
                    payload = "{\"idValue\": \"\" }";
1✔
230
                    break;
1✔
231
                default:
232
                    throw new ArgumentOutOfRangeException();
×
233
            }
234

235
            await SendCustomFieldChangeRequestAsync(cardId, customField, payload, cancellationToken);
5✔
236
        }
5✔
237

238
        private async Task SendCustomFieldChangeRequestAsync(string cardId, CustomField customField, string payload, CancellationToken cancellationToken)
239
        {
240
            await _apiRequestController.PutWithJsonPayload($"{UrlPaths.Cards}/{cardId}/customField/{customField.Id}/item", cancellationToken, payload, 0);
17✔
241
        }
17✔
242

243
        /// <summary>
244
        /// Retrieves all custom field item values for a specific card.
245
        /// </summary>
246
        /// <remarks>Tip: Use extension methods GetCustomFieldValueAsXYZ for a handy way to get values</remarks>
247
        /// <param name="cardId">ID of the card to retrieve custom field items for</param>
248
        /// <param name="cancellationToken">Cancellation Token</param>
249
        /// <returns>List of custom field items for the card</returns>
250
        public async Task<List<CustomFieldItem>> GetCustomFieldItemsForCardAsync(string cardId, CancellationToken cancellationToken = default)
251
        {
252
            return await _apiRequestController.Get<List<CustomFieldItem>>(GetUrlBuilder.GetCustomFieldItemsForCard(cardId), cancellationToken);
3✔
253
        }
3✔
254

255
        /// <summary>
256
        /// Retrieves all custom fields defined on a specific board.
257
        /// </summary>
258
        /// <param name="boardId">ID of the board (long version) to retrieve custom fields for</param>
259
        /// <param name="cancellationToken">Cancellation Token</param>
260
        /// <returns>List of custom fields on the board</returns>
261
        public async Task<List<CustomField>> GetCustomFieldsOnBoardAsync(string boardId, CancellationToken cancellationToken = default)
262
        {
263
            return await _apiRequestController.Get<List<CustomField>>(GetUrlBuilder.GetCustomFieldsOnBoard(boardId), cancellationToken);
1✔
264
        }
1✔
265

266
        /// <summary>
267
        /// Add a Custom Field
268
        /// </summary>
269
        /// <param name="boardId">ID of Board to add the Custom Field to</param>
270
        /// <param name="options">Options defining the Custom Field</param>
271
        /// <param name="cancellationToken">Cancellation Token</param>
272
        /// <returns>The new Custom Field</returns>
273
        public async Task<CustomField> AddCustomFieldAsync(string boardId, AddCustomFieldOptions options, CancellationToken cancellationToken = default)
274
        {
NEW
275
            JsonObject payload = new JsonObject
×
NEW
276
            {
×
NEW
277
                ["display_cardFront"] = options.ShowFieldOnFrontOfCard,
×
NEW
278
                ["idModel"] = boardId,
×
NEW
279
                ["modelType"] = "board",
×
NEW
280
                ["name"] = options.Name,
×
NEW
281
                ["type"] = GetCustomFieldType()
×
NEW
282
            };
×
283

NEW
284
            if (options.Type == CustomFieldType.List)
×
285
            {
NEW
286
                if (options.Options == null || options.Options.Count == 0)
×
287
                {
NEW
288
                    throw new TrelloApiException("No option items defined for the custom field of type list (need at least one)");
×
289
                }
290

NEW
291
                JsonArray payloadOptions = new JsonArray();
×
NEW
292
                foreach (AddCustomFieldOption option in options.Options)
×
293
                {
NEW
294
                    payloadOptions.Add(new JsonObject
×
NEW
295
                    {
×
NEW
296
                        ["value"] = new JsonObject
×
NEW
297
                        {
×
NEW
298
                            ["text"] = option.Text
×
NEW
299
                        },
×
NEW
300
                        ["color"] = option.Color.GetJsonPropertyName()
×
NEW
301
                    });
×
302
                }
303

NEW
304
                payload.Add("options", payloadOptions);
×
305
            }
306

307
            string GetCustomFieldType()
308
            {
NEW
309
                switch (options.Type)
×
310
                {
311
                    case CustomFieldType.Unknown:
NEW
312
                        throw new TrelloApiException("Custom Field Type have not been defined");
×
313
                    default:
NEW
314
                        return options.Type.GetJsonPropertyName();
×
315
                }
316
            }
317

318
            
NEW
319
            return await _apiRequestController.PostWithJsonPayload<CustomField>($"{UrlPaths.CustomFields}", cancellationToken, payload.ToString());
×
NEW
320
        }
×
321

322
        /// <summary>
323
        /// Update a Custom Field
324
        /// </summary>
325
        /// <param name="customFieldId">ID of Custom Field to update</param>
326
        /// <param name="options">Options to update</param>
327
        /// <param name="cancellationToken">Cancellation Token</param>
328
        /// <returns>The updated Custom Field</returns>
329
        public async Task<CustomField> UpdateCustomFieldAsync(string customFieldId, UpdateCustomFieldOptions options, CancellationToken cancellationToken = default)
330
        {
NEW
331
            JsonObject payload = new JsonObject();
×
NEW
332
            if (options.ShowFieldOnFrontOfCard.HasValue)
×
333
            {
NEW
334
                payload.Add("display/cardFront", options.ShowFieldOnFrontOfCard.Value);
×
335
            }
336

NEW
337
            if (!string.IsNullOrWhiteSpace(options.Name))
×
338
            {
NEW
339
                payload.Add(Constants.TrelloIds.CustomFieldFields.Name, options.Name);
×
340
            }
341

NEW
342
            if (options.NamedPosition.HasValue)
×
343
            {
NEW
344
                payload.Add(Constants.TrelloIds.CustomFieldFields.Position, options.NamedPosition.Value.GetJsonPropertyName());
×
345
            }
NEW
346
            else if(options.Position.HasValue)
×
347
            {
NEW
348
                payload.Add(Constants.TrelloIds.CustomFieldFields.Position, options.Position.Value);
×
349
            }
350

NEW
351
            return await _apiRequestController.PutWithJsonPayload<CustomField>($"{UrlPaths.CustomFields}/{customFieldId}", cancellationToken, payload.ToString());
×
NEW
352
        }
×
353

354
        /// <summary>
355
        /// Delete a custom field by its ID
356
        /// </summary>
357
        /// <param name="customFieldId">ID of the Custom Field</param>
358
        /// <param name="cancellationToken">CancellationToken</param>
359
        public async Task DeleteCustomFieldAsync(string customFieldId, CancellationToken cancellationToken = default)
360
        {
NEW
361
            await _apiRequestController.Delete($"{UrlPaths.CustomFields}/{customFieldId}", cancellationToken, 0);
×
NEW
362
        }
×
363

364
        /// <summary>
365
        /// Add a custom field option to a field ot type List
366
        /// </summary>
367
        /// <param name="customFieldId">the ID of the Custom Field</param>
368
        /// <param name="addCustomFieldOption">The new option</param>
369
        /// <param name="cancellationToken">CancellationToken</param>
370
        /// <exception cref="NotImplementedException"></exception>
371
        public async Task AddCustomFieldOptionAsync(string customFieldId, AddCustomFieldOption addCustomFieldOption, CancellationToken cancellationToken)
372
        {
NEW
373
            JsonObject payload = new JsonObject
×
NEW
374
            {
×
NEW
375
                ["value"] = new JsonObject
×
NEW
376
                {
×
NEW
377
                    ["text"] = addCustomFieldOption.Text
×
NEW
378
                },
×
NEW
379
                ["color"] = addCustomFieldOption.Color.GetJsonPropertyName()
×
NEW
380
            };
×
NEW
381
            await _apiRequestController.PostWithJsonPayload($"{UrlPaths.CustomFields}/{customFieldId}/options", cancellationToken, payload.ToString(), 0);
×
NEW
382
        }
×
383

384
        /// <summary>
385
        /// Delete a Custom Field Option
386
        /// </summary>
387
        /// <param name="customFieldId">ID of the Custom Field the option is on</param>
388
        /// <param name="customFieldOptionId">Id of the option to delete</param>
389
        /// <param name="cancellationToken">CancellationToken</param>
390
        public async Task DeleteCustomFieldOptionAsync(string customFieldId, string customFieldOptionId, CancellationToken cancellationToken)
391
        {
NEW
392
            await _apiRequestController.Delete($"{UrlPaths.CustomFields}/{customFieldId}/options/{customFieldOptionId}", cancellationToken, 0);
×
NEW
393
        }
×
394

395
        /// <summary>
396
        /// Update a custom Field option
397
        /// </summary>
398
        /// <param name="customFieldId">ID of the Custom Field the option is on</param>
399
        /// <param name="customFieldOptionId">Id of the option to delete</param>
400
        /// <param name="updateCustomFieldOption">This to update on the option</param>
401
        /// <param name="cancellationToken">CancellationToken</param>
402
        /// <exception cref="NotImplementedException"></exception>
403
        public async Task UpdateCustomFieldOptionAsync(string customFieldId, string customFieldOptionId, UpdateCustomFieldOption updateCustomFieldOption, CancellationToken cancellationToken = default)
404
        {
NEW
405
            JsonObject payload = new JsonObject();
×
NEW
406
            if (!string.IsNullOrWhiteSpace(updateCustomFieldOption.Text))
×
407
            {
NEW
408
                payload.Add("value", new JsonObject
×
NEW
409
                {
×
NEW
410
                    ["text"] = updateCustomFieldOption.Text
×
NEW
411
                });
×
412
            }
413

NEW
414
            if (updateCustomFieldOption.NamedPosition.HasValue)
×
415
            {
NEW
416
                payload.Add(Constants.TrelloIds.CustomFieldFields.Position, updateCustomFieldOption.NamedPosition.Value.GetJsonPropertyName());
×
417
            }
NEW
418
            else if(updateCustomFieldOption.Position.HasValue)
×
419
            {
NEW
420
                payload.Add(Constants.TrelloIds.CustomFieldFields.Position, updateCustomFieldOption.Position.Value);
×
421
            }
422

NEW
423
            if (updateCustomFieldOption.Color.HasValue)
×
424
            {
NEW
425
                payload.Add(Constants.TrelloIds.CustomFieldFields.Color, updateCustomFieldOption.Color.Value.GetJsonPropertyName());
×
426
            }
427

NEW
428
            await _apiRequestController.PutWithJsonPayload($"{UrlPaths.CustomFields}/{customFieldId}/options/{customFieldOptionId}", cancellationToken, payload.ToString(), 0);
×
NEW
429
        }
×
430
    }
431
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc