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
Manage Agility pages, the sitemap and page models with the .NET Management SDK 2.0, including how template saves treat zones and default components.
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 pages client works with pages, the sitemap and page models. You reach it through client.Pages 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 (guid) and a locale (locale). To set up the client, see the Intro.
using System.Net;
using Agility.Management.Sdk;
using Agility.Management.Sdk.Clients;
using Agility.Management.Sdk.Models;
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.
Page saves, deletes and workflow actions run as batches and return a BatchResult, just like content. See Batches. A page save always lands in Staging, so publish the page for the change to go live.
Page model saves are different: they aren't batches, and they return the saved template.
| Method | Description |
|---|---|
GetSitemapAsync | Get the sitemap for a locale |
GetPageAsync | Get a page by ID |
SavePageAsync | Create or update a page |
PublishPageAsync | Publish a page |
UnpublishPageAsync | Unpublish a page |
RequestApprovalPageAsync | Request approval for a page |
ApprovePageAsync | Approve a page |
DeclinePageAsync | Decline a page |
DeletePageAsync | Delete a page |
BatchWorkflowPagesAsync | Run one workflow action on many pages in one batch |
GetCascadeItemsAsync | List what a cascade publish of a page would include |
PublishPageCascadeAsync | Publish a page and the content its components use |
GetPageHistoryAsync | Get a page's version history |
GetPageCommentsAsync | Get a page's comments |
GetPageTemplatesAsync | List page models |
GetPageTemplateAsync | Get a page model by ID |
GetPageTemplateByNameAsync | Get a page model by name |
GetPageTemplateZonesAsync | Get a page model's zones |
SavePageTemplateAsync | Create or update a page model |
DeletePageTemplateAsync | Delete a page model |
Get the sitemap for a locale. The result has one entry per channel, and each channel has its pages.
List<Sitemap> sitemap = await client.Pages.GetSitemapAsync(guid, locale);
foreach (var channel in sitemap)
{
Console.WriteLine($"Channel {channel.Name}");
foreach (var node in channel.Pages ?? [])
{
Console.WriteLine($" {node.Url} - Page ID: {node.PageID}");
}
}
Each SitemapItem lists its children in ChildPages.
Signature: Task<List<Sitemap>> GetSitemapAsync(string instanceGuid, string locale)
Get a page by its page ID. Zones holds the components on the page, keyed by zone name.
PageItem page = await client.Pages.GetPageAsync(guid, locale, 7);
Console.WriteLine($"Page: {page.Name}");
foreach (var (zone, components) in page.Zones ?? [])
{
Console.WriteLine($"{zone}: {components.Count} components");
}
Signature: Task<PageItem> GetPageAsync(string instanceGuid, string locale, int pageId)
Create or update a page. A save replaces the whole page, so read it first, change it, and send it all back:
var page = await client.Pages.GetPageAsync(guid, locale, 7);
page.Title = "About us";
await client.Pages.SavePageAsync(guid, locale, page);
await client.Pages.PublishPageAsync(guid, locale, 7);
To create a page, use a PageID of -1 and fill in the page's properties (for example Name, Title, MenuText and TemplateName). Say where the page goes with SavePageOptions:
BatchResult created = await client.Pages.SavePageAsync(guid, locale, newPage,
new SavePageOptions { ParentPageId = 7 });
int newPageId = created.ItemId!.Value;
SavePageOptions | |
|---|---|
ParentPageId | The parent page. Leave it out for the root. |
PlaceBeforePageId | The sibling page to put this page before. Leave it out to put the page last. |
OtherLocale, PageIdInOtherLocale | Link the page to the same page in another locale |
LinkExistingComponents | Link the page's components to existing content instead of copying it |
Settings you leave out use the API's defaults.
Signature: Task<BatchResult> SavePageAsync(string instanceGuid, string locale, PageItem page, SavePageOptions? options = null, bool waitForBatch = true)
Returns: A BatchResult. ItemId is the page ID.
Each workflow method takes an optional comments string for the page's history, and returns a BatchResult.
await client.Pages.PublishPageAsync(guid, locale, pageId, comments: "Publishing page");
Signature: Task<BatchResult> PublishPageAsync(string instanceGuid, string locale, int pageId, string? comments = null, bool waitForBatch = true)
await client.Pages.UnpublishPageAsync(guid, locale, pageId, comments: "Taking down temporarily");
Signature: Task<BatchResult> UnpublishPageAsync(string instanceGuid, string locale, int pageId, string? comments = null, bool waitForBatch = true)
await client.Pages.RequestApprovalPageAsync(guid, locale, pageId, comments: "Ready for review");
Signature: Task<BatchResult> RequestApprovalPageAsync(string instanceGuid, string locale, int pageId, string? comments = null, bool waitForBatch = true)
await client.Pages.ApprovePageAsync(guid, locale, pageId, comments: "Approved for publication");
Signature: Task<BatchResult> ApprovePageAsync(string instanceGuid, string locale, int pageId, string? comments = null, bool waitForBatch = true)
await client.Pages.DeclinePageAsync(guid, locale, pageId, comments: "Needs revision");
Signature: Task<BatchResult> DeclinePageAsync(string instanceGuid, string locale, int pageId, string? comments = null, bool waitForBatch = true)
await client.Pages.DeletePageAsync(guid, locale, pageId, comments: "Removing page");
Signature: Task<BatchResult> DeletePageAsync(string instanceGuid, string locale, int pageId, string? comments = null, bool waitForBatch = true)
Run one workflow action on many pages in a single batch. The action is a WorkflowOperationType: Publish, Unpublish, Approve, Decline or RequestApproval.
await client.Pages.BatchWorkflowPagesAsync(guid, locale, [7, 8, 9], WorkflowOperationType.Publish);
Pass at least one page ID.
Signature: Task<BatchResult> BatchWorkflowPagesAsync(string instanceGuid, string locale, IEnumerable<int> pageIds, WorkflowOperationType operation, bool waitForBatch = true)
List what publishing a page would also publish: the content its components use, and nested lists.
CascadeItem tree = await client.Pages.GetCascadeItemsAsync(guid, locale, pageId);
Signature: Task<CascadeItem> GetCascadeItemsAsync(string instanceGuid, string locale, int pageId)
Publish a page and the content it depends on. 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.Pages.PublishPageCascadeAsync(guid, locale, pageId);
foreach (var batchId in created.BatchIDs ?? [])
{
await client.Batches.WaitForBatchAsync(guid, batchId);
}
Signature: Task<BatchCreateResult> PublishPageCascadeAsync(string instanceGuid, string locale, int pageId, string? comments = null)
Get a page's version history, a page of results at a time.
PageHistoryResponse history = await client.Pages.GetPageHistoryAsync(guid, locale, pageId, take: 20);
foreach (var version in history.Items ?? [])
{
Console.WriteLine($"v{version.VersionNumber} {version.State} by {version.CreatedBy}");
}
Signature: Task<PageHistoryResponse> GetPageHistoryAsync(string instanceGuid, string locale, int pageId, int? take = null, int? skip = null)
Get a page's comments.
ItemCommentsResponse comments = await client.Pages.GetPageCommentsAsync(guid, locale, pageId);
Signature: Task<ItemCommentsResponse> GetPageCommentsAsync(string instanceGuid, string locale, int pageId, int? take = null, int? skip = null)
A page model (called a page template in the SDK and API) defines a page's zones. In the SDK, a template is a PageModel, and its zones are its ContentSectionDefinitions. Each zone (ContentSectionDefinition) can have default components (DefaultModules) that new pages get automatically.
List page models. Set includeModuleZones: true to include each template's zones.
List<PageModel> templates = await client.Pages.GetPageTemplatesAsync(guid, locale, includeModuleZones: true);
foreach (var template in templates)
{
Console.WriteLine($"{template.PageTemplateName} (ID: {template.PageTemplateID})");
}
searchFilter returns only templates whose name contains that text.
Signature: Task<List<PageModel>> GetPageTemplatesAsync(string instanceGuid, string locale, bool includeModuleZones = false, string? searchFilter = null)
Get a page model by ID, with every zone and each zone's default components. Use this method to read a template you plan to save back.
PageModel template = await client.Pages.GetPageTemplateAsync(guid, locale, pageTemplateId);
Console.WriteLine($"Template: {template.PageTemplateName}");
Signature: Task<PageModel> GetPageTemplateAsync(string instanceGuid, string locale, int pageTemplateId)
PageModel template = await client.Pages.GetPageTemplateByNameAsync(guid, locale, "Main Template");
Console.WriteLine($"Template ID: {template.PageTemplateID}");
Signature: Task<PageModel> GetPageTemplateByNameAsync(string instanceGuid, string locale, string templateName)
Get just a template's zones.
List<ContentSectionDefinition> zones = await client.Pages.GetPageTemplateZonesAsync(guid, locale, pageTemplateId);
foreach (var zone in zones)
{
Console.WriteLine($"Zone: {zone.PageItemTemplateName} ({zone.PageItemTemplateReferenceName})");
}
⚠️ Don't save zones read with this method. It always returns
DefaultModulesas empty. If you put these zones in a template and save it, you clear every zone's default components. Read the template withGetPageTemplateAsyncinstead.
Signature: Task<List<ContentSectionDefinition>> GetPageTemplateZonesAsync(string instanceGuid, string locale, int pageTemplateId)
Create or update a page model. Read the save rules below before your first template save.
Signature: Task<PageModel> SavePageTemplateAsync(string instanceGuid, string locale, PageModel pageTemplate)
Returns: The saved template.
The SDK leaves any property you don't set (null) out of the request, and the API reads what you send like this:
| You send | The API |
|---|---|
ContentSectionDefinitions left null | Keeps the zones as they are |
A ContentSectionDefinitions list | Makes it the complete zone list. Stored zones you leave out are removed. |
A zone with DefaultModules left null | Keeps that zone's default components |
A zone with a DefaultModules list | Replaces that zone's default components with the list |
A zone with DefaultModules = [] | Clears that zone's default components |
How zones are matched. The API matches each zone you send to a stored zone by its ID (PageItemTemplateID). A zone sent with a PageItemTemplateID of -1 is matched by its reference name (PageItemTemplateReferenceName) instead, as long as no other zone in the same request already claims that stored zone by ID. If no stored zone matches, the API creates a new zone.
⚠️ Removing a zone removes its components from pages. When a save leaves a zone out, the components placed in that zone disappear from every page that uses the template. The content items themselves aren't deleted. Don't send a partial zone list by accident: start from
GetPageTemplateAsync, which returns every zone.
Restoring a removed zone. Send a zone with a PageItemTemplateID of -1 and the removed zone's reference name through the Management API, for example with SavePageTemplateAsync. The API restores the zone along with the components that were placed in it. Re-adding a zone with the same reference name in Web Studio doesn't do this: it creates a new, empty zone.
Channel. DigitalChannelTypeID is the template's channel. When you create a template without one, it's created for the Website channel. When you update a template, it keeps its channel.
Collections start as null. On a new object, create a collection before you add to it, for example zone.DefaultModules ??= [];. Leaving it null means "don't change it".
var template = await client.Pages.GetPageTemplateAsync(guid, locale, pageTemplateId);
template.PageTemplateName = "Landing Page";
template.ContentSectionDefinitions = null; // keep the zones as they are
await client.Pages.SavePageTemplateAsync(guid, locale, template);
var template = await client.Pages.GetPageTemplateAsync(guid, locale, pageTemplateId); // every zone and its defaults
template.ContentSectionDefinitions!.Add(new ContentSectionDefinition
{
PageItemTemplateID = -1,
PageItemTemplateName = "Sidebar",
PageItemTemplateReferenceName = "Sidebar",
});
await client.Pages.SavePageTemplateAsync(guid, locale, template);
var hero = (await client.Models.GetComponentModelsAsync(guid)).Single(m => m.ReferenceName == "Hero");
var template = await client.Pages.GetPageTemplateAsync(guid, locale, pageTemplateId);
var zone = template.ContentSectionDefinitions!.Single(z => z.PageItemTemplateReferenceName == "MainContentZone");
zone.DefaultModules ??= [];
zone.DefaultModules.Add(new ContentSectionDefaultModule { ContentDefinitionID = hero.Id, AutoCreate = true });
await client.Pages.SavePageTemplateAsync(guid, locale, template);
The list you send replaces the zone's default components, so it must include the existing ones. Starting from GetPageTemplateAsync keeps them. Only the component model (ContentDefinitionID) and AutoCreate are stored: a read returns the component model's own title, whatever Title you send.
var template = await client.Pages.GetPageTemplateAsync(guid, locale, pageTemplateId);
template.ContentSectionDefinitions!.Single(z => z.PageItemTemplateReferenceName == "Sidebar").DefaultModules = [];
await client.Pages.SavePageTemplateAsync(guid, locale, template);
PageModel created = await client.Pages.SavePageTemplateAsync(guid, locale, new PageModel
{
PageTemplateID = -1,
PageTemplateName = "Campaign Page",
ContentSectionDefinitions =
[
new ContentSectionDefinition
{
PageItemTemplateID = -1,
PageItemTemplateName = "Main",
PageItemTemplateReferenceName = "Main",
ItemOrder = 0,
},
],
});
Console.WriteLine($"Created template {created.PageTemplateID}");
Delete a page model. The method returns nothing. A failure throws an exception.
await client.Pages.DeletePageTemplateAsync(guid, locale, pageTemplateId);
Signature: Task DeletePageTemplateAsync(string instanceGuid, string locale, int pageTemplateId)
try
{
var page = await client.Pages.GetPageAsync(guid, locale, pageId);
}
catch (AgilityManagementException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
Console.Error.WriteLine($"Page not found: {ex.ApiMessage}");
}
Page saves and workflow actions throw an AgilityBatchException when their batch is processed with failures, and an AgilityBatchTimeoutException when it doesn't finish in time. See Errors for the full list of exceptions.