What is your developer-dependent CMS actually costing you? Calculate your costs and get a full report
What is your developer-dependent CMS actually costing you? Calculate your costs and get a full report
SDKs
Read, list, filter, save, publish and delete Agility content items with the .NET Management SDK 2.0, including bulk workflow, cascade publish, history and comments.
This page has moved — the Management SDK is now documented elsewhere. The JavaScript and .NET guides have been merged into a single set with per-language code tabs. Read the current guide.
The content client reads, lists, saves, deletes and runs workflow on content items. You reach it through client.Content on an AgilityManagementClient.
Note: This page covers version 2.0 of the .NET Management SDK. Upgrading from 1.x? See Migrating to 2.0.
The examples assume a client, an instance GUID and a locale. To set up the client, see the Intro.
using System.Net;
using System.Text.Json.Nodes;
using Agility.Management.Sdk;
using Agility.Management.Sdk.Clients;
using Agility.Management.Sdk.Models;
using var client = new AgilityManagementClient(new AgilityManagementOptions
{
AccessToken = Environment.GetEnvironmentVariable("AGILITY_TOKEN"),
});
var guid = "<<your-instance-guid>>";
var locale = "en-us";
Every method takes the instance GUID first, then the locale, then IDs, then optional settings. Every method also takes an optional CancellationToken cancellationToken as its last parameter. The signatures below leave it out.
BatchResult. See Batches.| Method | Description |
|---|---|
GetContentItemAsync | Get a content item by ID |
GetContentItemsByIdAsync | Get several content items in one request |
GetContentListAsync | List the items in a container, with paging, sorting and filters |
SaveContentItemAsync | Create or update one content item |
SaveContentItemsAsync | Create or update several content items in one batch |
PublishContentItemAsync | Publish a content item |
UnpublishContentItemAsync | Unpublish a content item |
RequestApprovalContentItemAsync | Request approval for a content item |
ApproveContentItemAsync | Approve a content item |
DeclineContentItemAsync | Decline a content item |
DeleteContentItemAsync | Delete a content item |
BatchWorkflowContentItemsAsync | Run one workflow action on many items in one batch |
GetCascadeItemsAsync | List what a cascade publish would include |
PublishContentItemCascadeAsync | Publish an item and its one-level nested content |
GetContentItemHistoryAsync | Get an item's version history |
GetContentItemCommentsAsync | Get an item's comments |
Get a content item by its content ID.
ContentItem item = await client.Content.GetContentItemAsync(guid, locale, 123);
string? title = item.Fields?["title"]?.GetValue<string>();
int? state = item.Properties?.State; // compare with the ItemState enum
Fields is a JsonObject keyed by field name, in camelCase. It holds whatever the model defines: text, numbers, linked content and image objects. Read a value with GetValue<T>(), or turn a field into your own type with Deserialize<T>().
Signature: Task<ContentItem> GetContentItemAsync(string instanceGuid, string locale, int contentId)
Get several content items in one request.
List<ContentItem> items = await client.Content.GetContentItemsByIdAsync(guid, locale, [123, 124, 125]);
Pass at least one ID. An empty list throws an ArgumentException.
Signature: Task<List<ContentItem>> GetContentItemsByIdAsync(string instanceGuid, string locale, IEnumerable<int> contentIds)
List the items in a container, one page at a time. Name the container by its reference name.
ContentList page = await client.Content.GetContentListAsync(guid, locale, "blogposts",
new ContentListOptions { Take = 50, Skip = 0, SortField = "title", SortDirection = "asc" });
Console.WriteLine($"Total: {page.TotalCount}");
foreach (JsonNode? row in page.Items ?? [])
{
Console.WriteLine(row?["contentID"]);
}
ContentListOptions controls paging, sorting and filtering. Leave out the options to get the first page of everything.
ContentListOptions | |
|---|---|
Take | Items per page. The API's default is 50. |
Skip | How many items to skip |
SortField | The field to sort by |
SortDirection | asc or desc |
Fields | Comma-separated field names to return, such as "title,category". This keeps large lists fast. |
ShowDeleted | Include deleted items |
Filter | A ContentListFilterModel (see below) |
To filter, set Filter to a ContentListFilterModel:
var filter = new ContentListFilterModel
{
GenericSearch = "launch", // free-text search
StateIds = [(int)ItemState.Published],
DateRange = new DateRangeFilter { StartDate = DateTime.UtcNow.AddDays(-30) },
FieldFilters =
[
new FieldFilter { Field = "category", Value = new FieldFilterValue { StringValue = "news" } },
],
};
ContentList recent = await client.Content.GetContentListAsync(guid, locale, "blogposts",
new ContentListOptions { Filter = filter, Take = 20 });
A FieldFilterValue holds one kind of value: StringValue, BoolValue, DateRangeValue (a DateRangeFilter) or NumRangeValue (a NumRangeFilter with FromNum and ToNum).
Note: In 1.x,
GetContentItemstook afilterstring that the API ignored. 2.0 uses the documented list route, so filters now apply.
Signature: Task<ContentList> GetContentListAsync(string instanceGuid, string locale, string referenceName, ContentListOptions? options = null)
Create or update one content item.
To update an item, read it first, change it, and send the whole item back:
var existing = await client.Content.GetContentItemAsync(guid, locale, 123);
existing.Fields!["title"] = "Updated Title";
BatchResult saved = await client.Content.SaveContentItemAsync(guid, locale, existing);
To create an item, use a ContentID of -1 and name the container (ReferenceName) and the model (DefinitionName):
var created = await client.Content.SaveContentItemAsync(guid, locale, new ContentItem
{
ContentID = -1,
Properties = new ContentItemProperties
{
ReferenceName = "blogposts",
DefinitionName = "BlogPost",
},
Fields = new JsonObject
{
["title"] = "New Article",
["slug"] = "new-article",
["content"] = "<p>Article content here...</p>",
},
});
int newContentId = created.ItemId!.Value;
Any ContentID of 0 or less creates a new item.
Signature: Task<BatchResult> SaveContentItemAsync(string instanceGuid, string locale, ContentItem item, bool waitForBatch = true)
Returns: A BatchResult. ItemId is the item's content ID.
Save several content items in one batch. You can mix new items and updates in the same call.
var items = new List<ContentItem>
{
new()
{
ContentID = -1,
Properties = new ContentItemProperties { ReferenceName = "blogposts", DefinitionName = "BlogPost" },
Fields = new JsonObject { ["title"] = "Article 1" },
},
new()
{
ContentID = -1,
Properties = new ContentItemProperties { ReferenceName = "blogposts", DefinitionName = "BlogPost" },
Fields = new JsonObject { ["title"] = "Article 2" },
},
};
BatchResult result = await client.Content.SaveContentItemsAsync(guid, locale, items);
Console.WriteLine($"Saved IDs: {string.Join(", ", result.ItemIds)}");
If any item fails, the call throws an AgilityBatchException. Its Batch.Items shows which items succeeded and which failed. (In 1.x, a failed item appeared as -1 in the returned list.)
Signature: Task<BatchResult> SaveContentItemsAsync(string instanceGuid, string locale, IReadOnlyList<ContentItem> items, bool waitForBatch = true)
Returns: A BatchResult. ItemIds lists the content IDs.
Each workflow method takes an optional comments string for the item's history, and returns a BatchResult.
Publish a content item.
await client.Content.PublishContentItemAsync(guid, locale, 123, comments: "Publishing update");
Signature: Task<BatchResult> PublishContentItemAsync(string instanceGuid, string locale, int contentId, string? comments = null, bool waitForBatch = true)
Unpublish a content item.
await client.Content.UnpublishContentItemAsync(guid, locale, 123, comments: "Temporarily unpublishing");
Signature: Task<BatchResult> UnpublishContentItemAsync(string instanceGuid, string locale, int contentId, string? comments = null, bool waitForBatch = true)
Submit a content item for approval.
await client.Content.RequestApprovalContentItemAsync(guid, locale, 123, comments: "Ready for review");
Signature: Task<BatchResult> RequestApprovalContentItemAsync(string instanceGuid, string locale, int contentId, string? comments = null, bool waitForBatch = true)
Approve a content item.
await client.Content.ApproveContentItemAsync(guid, locale, 123, comments: "Approved for publication");
Signature: Task<BatchResult> ApproveContentItemAsync(string instanceGuid, string locale, int contentId, string? comments = null, bool waitForBatch = true)
Decline a content item.
await client.Content.DeclineContentItemAsync(guid, locale, 123, comments: "Needs revision");
Signature: Task<BatchResult> DeclineContentItemAsync(string instanceGuid, string locale, int contentId, string? comments = null, bool waitForBatch = true)
Delete a content item.
await client.Content.DeleteContentItemAsync(guid, locale, 123, comments: "Removing outdated content");
Signature: Task<BatchResult> DeleteContentItemAsync(string instanceGuid, string locale, int contentId, string? comments = null, bool waitForBatch = true)
Run one workflow action on many content items in a single batch. The action is a WorkflowOperationType: Publish, Unpublish, Approve, Decline or RequestApproval.
await client.Content.BatchWorkflowContentItemsAsync(guid, locale, [123, 124, 125], WorkflowOperationType.Publish);
Pass at least one ID.
Signature: Task<BatchResult> BatchWorkflowContentItemsAsync(string instanceGuid, string locale, IEnumerable<int> contentIds, WorkflowOperationType operation, bool waitForBatch = true)
An item can link to other content and nested lists. A cascade publish publishes the item together with its one-level nested content: the non-shared containers its fields point at (dynamic page lists are not included).
List the items a cascade publish would include, as a tree.
CascadeItem tree = await client.Content.GetCascadeItemsAsync(guid, locale, 123);
foreach (var child in tree.Children ?? [])
{
Console.WriteLine($"{child.ItemType} {child.ItemID}: {child.Title}");
}
Signature: Task<CascadeItem> GetCascadeItemsAsync(string instanceGuid, string locale, int contentId)
Publish an item and its one-level nested content. The API can create more than one batch, and this method doesn't wait for them. Wait for each one with client.Batches.WaitForBatchAsync:
BatchCreateResult created = await client.Content.PublishContentItemCascadeAsync(guid, locale, 123);
foreach (var batchId in created.BatchIDs ?? [])
{
await client.Batches.WaitForBatchAsync(guid, batchId);
}
Signature: Task<BatchCreateResult> PublishContentItemCascadeAsync(string instanceGuid, string locale, int contentId, string? comments = null)
Get an item's version history, a page at a time.
ContentItemHistoryResponse history = await client.Content.GetContentItemHistoryAsync(guid, locale, 123, take: 20);
foreach (var version in history.Items ?? [])
{
Console.WriteLine($"v{version.VersionNumber} {version.State} by {version.CreatedBy} on {version.CreatedDate}");
}
Signature: Task<ContentItemHistoryResponse> GetContentItemHistoryAsync(string instanceGuid, string locale, int contentId, int? take = null, int? skip = null)
Get an item's comments, a page at a time.
ItemCommentsResponse comments = await client.Content.GetContentItemCommentsAsync(guid, locale, 123);
foreach (var comment in comments.Items ?? [])
{
Console.WriteLine($"{comment.CreatedBy}: {comment.Comment}");
}
Signature: Task<ItemCommentsResponse> GetContentItemCommentsAsync(string instanceGuid, string locale, int contentId, int? take = null, int? skip = null)
The batch client, client.Batches, checks on batches and lets you build your own.
Pass waitForBatch: false to return as soon as the batch is queued. Wait for it later with WaitForBatchAsync:
BatchResult queued = await client.Content.PublishContentItemAsync(guid, locale, 123, waitForBatch: false);
// ... later
Batch batch = await client.Batches.WaitForBatchAsync(guid, queued.BatchId);
GetBatchAsync(guid, batchId) gets a batch's current state without waiting.
Signature: Task<Batch> WaitForBatchAsync(string instanceGuid, int batchId)
Group workflow actions on content items and pages into one batch of your own.
BatchCreateResult batch = await client.Batches.CreateBatchAsync(guid, new CreateBatchWithItemsRequest
{
BatchName = "Spring launch",
Operation = WorkflowOperationType.Publish,
Items =
[
new AddBatchItemRequest { ItemType = BatchItemType.ContentItem, ItemID = 123, LanguageCode = "en-us" },
new AddBatchItemRequest { ItemType = BatchItemType.Page, ItemID = 7, LanguageCode = "en-us" },
],
}, processNow: true);
Set Operation, and give every item an ItemType. The SDK throws an ArgumentException if either is missing. With processNow: true the batch is queued for processing straight away. Otherwise it's left as a draft.
PublishBatchAsync, UnpublishBatchAsync, ApproveBatchAsync, DeclineBatchAsync and RequestApprovalBatchAsync run an action on every item in an existing batch. Each takes (guid, batchId) and returns a BatchResult.
Signature: Task<BatchCreateResult> CreateBatchAsync(string instanceGuid, CreateBatchWithItemsRequest request, bool? processNow = null)
To import or sync content, save items in chunks with SaveContentItemsAsync, then publish each chunk with one BatchWorkflowContentItemsAsync call:
const int ChunkSize = 50;
foreach (var chunk in contentItems.Chunk(ChunkSize))
{
BatchResult saved = await client.Content.SaveContentItemsAsync(guid, locale, chunk);
await client.Content.BatchWorkflowContentItemsAsync(guid, locale, saved.ItemIds, WorkflowOperationType.Publish);
Console.WriteLine($"Saved and published {saved.ItemIds.Count} items");
}
A processed publish batch means the Management API has published the item. The Fetch API, which your website reads from, syncs shortly after. To wait for that too:
await client.SyncStatus.WaitForFetchApiSyncAsync(guid, SyncMode.Fetch);
try
{
await client.Content.SaveContentItemAsync(guid, locale, item);
}
catch (AgilityBatchException ex)
{
// The batch ran, but some items failed.
foreach (var failed in ex.Batch?.Items?.Where(i => i.ErrorMessage is not null) ?? [])
{
Console.Error.WriteLine($"{failed.ItemID}: {failed.ErrorMessage}");
}
}
catch (AgilityManagementException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
Console.Error.WriteLine(ex.ApiMessage);
}
API and network errors throw an AgilityManagementException, and failed or timed-out batches throw an AgilityBatchException or AgilityBatchTimeoutException. See Errors.
The SDK retries reads that fail with a temporary error. It never retries saves, deletes or workflow actions, because repeating them would repeat the change.