Webhooks, snapshots and roles with .NET

The Content Management API (CMA) is a restful API for managing content in your Contentful spaces. You can use it to create, update, delete and retrieve content using well-known HTTP verbs.

To make development easier for our users, we publish client libraries for various languages which make the task easier. This article details how to use the .NET client library to create, update and delete webhooks, snapshots and roles.

Pre-requisites

This tutorial assumes you understand the basic Contentful data model as described in the developer center and that you have already read and understand the getting started tutorial for the .NET client library and the using the management API with Contentful and .NET client library article.

Contentful.net is built on .NET Core and targets .NET Standard 2.0. The client library is cross-platform and runs on Linux, macOS, and Windows.

Working with webhooks

To learn more about what webhooks refer to the webhooks concepts article.

To create a webhook for a space use the CreateWebhook method.

1var webhook = new Webhook();
2webhook.Name = "New product"; // The name of the webhook
3webhook.Url = "https://www.example.com/"; // The url to call when the specified event occurrs
4webhook.HttpBasicPassword = "Pass"; // Optional basic auth password
5webhook.HttpBasicUsername = "User"; // Optional basic auth username
6webhook.Headers = new List<KeyValuePair<string, string>>(); // You can pass custom headers with every call
7webhook.Headers.Add(new KeyValuePair<string, string>("custom", "header")); // Add a header by the name of "custom" with a value of "header"
8webhook.Topics = new List<string>() // Topics are the events that trigger this webhook.
9{
10 "Entry.*"
11};
12
13await client.CreateWebhook(webhook);

The topics available are described in more detail in the webhooks concepts article, but the summary is that you specify a Type and an Action. Type can be any of Entry, Asset or ContentType and Action can be any of create, save, auto_save, archive, unarchive, publish, unpublish or delete. _ is a wildcard and _.\* is valid and would mean to call this webhook for all actions across all types.

Once you have created a webhook you can fetch them from the client library using the GetWebhooksCollection and GetWebhook methods.

1var allWebhooks = await client.GetWebhooksCollection();
2
3var webhook = await client.GetWebhook("<webhook_id>");

To retrieve more information about a specific webhook call use the GetWebhookCallDetailsCollection to retrieve a list of calls made to the webhook or the GetWebhookCallDetails to get details of a specific call.

1var calldetails = await client.GetWebhookCallDetailsCollection("<webhook_id>");
2
3var calldetail = await client.GetWebhookCallDetails("<call_details_id>", "webhook_id");

A method is available that gives a more general overview of the total number of webhook calls for a specific webhook and whether they returned a success code.

1var webhookHealth = await client.GetWebhookHealth("<webhook_id>");
2
3var total = webhookHealth.TotalCalls; // 27
4var healthy = webhookHealth.TotalHealthy; // 23

To delete a webhook you are no longer using.

1await client.DeleteWebhook("<webhook_id>");

Roles and memberships

The .NET client library allows for the creation of custom roles. It’s complex to understand the permissions and policies system, but the examples below should give you a good overview. For more detailed information, refer to the management API documentation.

Creating a role

Start by creating a new Role and adding the permissions and policies you need.

1var role = new Role();
2role.Name = "Name of the role";
3role.Description = "Describe the role";
4role.Permissions = new ContentfulPermissions(); // Add permissions to the role
5role.Permissions.ContentDelivery = new List<string>() { "read" }; // What permissions should the role have to the ContentDelivery API
6role.Permissions.ContentModel = new List<string>() { "read" }; // What permissions should the role have to the content model, i.e. creating content types and fields
7role.Permissions.Settings = new List<string>() { "manage" }; // What permissions should the role have to other type of settings
8
9role.Policies = new List<Policy>(); // Add more granular policies to the role.
10role.Policies.Add(new Policy() // Every policy consists of a number of actions and constraints.
11{
12 Effect = "allow",
13 Actions = new List<string>()
14 {
15 "read",
16 "create",
17 "update"
18 },
19 Constraint = new AndConstraint()
20 {
21 new EqualsConstraint()
22 {
23 Property = "sys.type",
24 ValueToEqual = "Entry"
25 }
26 }
27});

This example above would give the role permissions to read entries, assets, and content types, but not edit, create or delete them. It would also give permissions to manage settings for the space. The policies give this role-specific access to read, create and update entries.

Policies explained

The policies can look daunting and the framework behind them is complex. However, they do make it possible to create granular and flexible authorization rules.

The first property is the Effect which is how this policy is to be treated, will it allow or deny access.

Actions represents what actions this policy allows or denies. This is a list of strings but only a certain set of values are acceptable. These are read, create, update, delete, publish, unpublish, archive, unarchive or all.

Constraint is an IConstraint, which is explained in more detail below.

Constraints explained

Constraints represent which resources a specific policy applies to. There are five different kinds of constraints.

AndConstraint and OrConstraint are lists of other IConstraint. The OrConstraint ensures that at least one of the contained constraints is true and the AndConstraint requires all contained constraints to be true.

The NotConstraint contains another IConstraint and inverts the value of that constraint.

The EqualsConstraint contains a Property which is a field on the content type or asset and a ValueToEqual which contains the value that this field must equal for this constraint to evaluate to be fulfilled.

The PathConstraint contains a Fields property that gives a path that must exist on the content type. An example is "fields.heading" which would only match content types that have the heading field present.

Putting them all together looks something like the following.

1var constraint = new AndConstraint() // AndConstraint is a list of other IConstraints, ensuring all other contained constraints are met
2{
3 new EqualsConstraint() // This equals constraint constraints this policy to only affect resources with a sys.type of Entry
4 {
5 Property = "sys.type",
6 ValueToEqual = "Entry"
7 },
8 new PathConstraint(){ //This path constraint constraints this policy to only affect the name field of the resource
9 Fields = "fields.name.%"
10 }
11}

Once you’ve created a Role and added the appropriate permissions and policies, you can call the CreateRole method.

1var createdRole = await client.CreateRole(role);

You can update, delete and fetch roles with the appropriate methods.

1var role = await client.GetRole("<role_id>");
2
3var allRoles = await client.GetAllRoles();
4
5var updatedRole = await client.UpdateRole(role);
6
7await client.DeleteRole("<role_id>");

Working with snapshots

A snapshot of an entry is automatically created each time an entry is published and is the state of every field of the entry at that given time. You can use the snapshots of an entry to keep a full version history of the entry content. For more information read the snapshots documentation.

To get all snapshots for an entry use the GetAllSnapshotsForEntry. To get a specific snapshot use the SingleSnapshot method.

1var singleSnapshot = await client.SingleSnapshot("<snapshot_id>", "<entry_id>");
2
3var allSnapshots = await client.GetAllSnapshotsForEntry("<entry_id>");

Similarly, a snapshot of a content type is created each time a content type is published and can be fetched using the GetAllSnapshotsForContentType and GetSnapshotForContentType methods.

1var singleSnapshot = await client.GetSnapshotForContentType("<snapshot_id>", "<content_type_id>");
2
3var allSnapshots = await client.GetAllSnapshotsForContentType("<content_type_id>");

Working with space memberships

A space membership represents the membership type a user has in a space and you can assign additional roles to a user, flag a user as admin or remove a user from a space.

To get all memberships of a space call the GetSpaceMemberships method. To get a single membership call GetSpaceMembership method.

1var singleMembership = await client.GetSpaceMembership("<membership_id>");
2
3var roles = singleMembership.Roles; // A List<string> of all role ids this membership has
4var userId = singleMembership.User.SystemProperties.Id; // The id of the user tied to this membership
5var isAdmin = singleMembership.Admin; // Whether this membership is the administrator of the space
6
7var allMemberships = await client.GetSpaceMemberships();

To update an existing membership use the UpdateSpaceMembership method.

1var singleMembership = await client.GetSpaceMembership("<membership_id>");
2singleMembership.Admin = false; // This membership will no longer be administrator of the space
3
4await client.UpdateSpaceMembership(singleMembership);

To create a new membership use the CreateSpaceMembership method.

1var newMembership = new SpaceMembership();
2
3newMembership.Roles = new List<string>() {
4 "<role_id>"
5};
6
7newMembership.Admin = true;
8
9newMembership.User = new User() { // The user must already exist. It cannot be automatically created through the membership.
10 SystemProperties = new SystemProperties() {
11 Id = "<user_id>",
12 LinkType = "User",
13 Type = "Link"
14 }
15};
16
17await client.CreateSpaceMembership(newMembership);

To delete a membership use the DeleteSpaceMembership method.

It’s possible to delete every single membership of a space, leaving no administrator available. You can fix this by inviting a new user through the Contentful web app organization settings.
1await client.DeleteSpaceMembership("<membership_id>");

Working with API keys

The Contentful .NET client library allows you to get all API keys for the Content Delivery API ((not the management API) for a space. It also allows you to create new API keys.

1var allApiKeys = await client.GetAllApiKeys();
2
3var accessToken = allApiKeys.First().AccessToken;
4
5var newKey = await client.CreateApiKey("Name of key", "Description of key");
6
7var newAccessToken = newKey.AccessToken;

Working with environments

This is currently a beta feature and only available in the pre-release of the client library and for whitelisted organizations.

To create an environment call the CreateOrUpdateEnvironment method.

1var environment = await client.CreateOrUpdateEnvironment("<environment_id>", "<environment_name>");

This environment will be an identical copy to your current master environment. Note that it can take a while for the environment to be fully available depending on the size of the master environment.

To get a list of available environments use the GetEnvironments method.

1var environments = await client.GetEnvironments();

To get a specific environment use the GetEnvironment method.

1var environment = await client.GetEnvironment("<environment_id>");

To delete an environment use the DeleteEnvironment method.

1await client.DeleteEnvironment("<environment_id>");

Next steps