CD Exclusions

When using Xperience by Kentico's CI/CD functionality, we can specify object exclusions in our repository.config file - this works for both the CI and CD config files.

I generally don't recommend excluding content from CI since this is how you stay in sync with your development team.

But excluding content from CD is very common since the content we create in our local environments for testing functionality might not be appropriate for production 😅.

Exclude by Object Type

We can exclude full object types by specific their type name.

<ExcludedObjectTypes>
    <ObjectType>emaillibrary.emailchannelsender</ObjectType>
</ExcludedObjectTypes>

This works great for custom module Object Types where all objects are represented by a single type name. For example, we can exclude all "Office Location" records from CD with a single line.

However, this is less helpful when we want to exclude sets of Content items or Web pages - that's because these excludes target the Object Type, no the Content Type. We can't use <ObjectType>DancingGoat.CoffeePage</ObjectType> to exclude all Coffee Web pages - that's not the Object Type of Web pages - cms.webpageitem is 😔. We also want to exclude the "content" part of a Web page - the Content Item - which has an Object Type cms.contentitem.

So, what do we do if we want to exclude all Coffee Web pages from CD?

Exclude by Object Filter

Fortunately we can exclude more granularly using the <ObjectFilter> portion of our repository.config.

Unfortunately, we need to target an object code name, like this.

<ObjectFilters>
    <!-- Excludes the "articles" and "graphics" media libararies -->
    <ExcludedCodeNames ObjectType="media.library">articles;graphics</ExcludedCodeNames>
</ObjectFilters>

We can use wildcard % characters at the beginning or end of the code name value here, but the code names of Web pages are automatically generated by Xperience so it's going to be difficult to target something consistently.

But of course, Xperience by Kentico is known for its customization! We can use global events to hook into the saving process for Web pages and modify their code names to something we can target with the <ExcludedCodeNames> filter.

# Global Events

To hook into global events, we first need a Module.

[assembly: RegisterModule(typeof(ApplicationModule))]

namespace DancingGoat;

public class ApplicationModule : Module
{
    public ApplicationModule() : base(nameof(ApplicationModule)) { }

    // ...
}

Now, we need to add our event handler in the OnInit() method.

protected override void OnInit()
{
    base.OnInit();

    var env = Service.Resolve<IWebHostEnvironment>();

    /*
     * Ensure all pages that should be excluded from CD
     * deployments end in the correct code name
     */
    if (env.IsDevelopment())
    {
        WebPageEvents.Create.Before += EnsureLocalCodeNames;
    }
}

We make sure that we only execute this event in "Development" environments - we don't need to mess with code names in QA/UAT/Production.

Now let's create our EnsureLocalCodeNames event handler method.

private void EnsureLocalCodeNames(object sender, CreateWebPageEventArgs e)
{
    if (string.Equals(e.ContentTypeName, CoffeePage.CONTENT_TYPE_NAME))
    {
        if (e.Name.EndsWith("-localtest"))
        {
            return;
        }

        e.Name = e.Name[..Math.Min(90, e.Name.Length)] + "-localtest";
    }
}

Code names can be up to 100 characters for Web pages, so we limit the name to 90 characters max and then add -localtest to the end.

Xperience adds a random series of 8 characters to the end of every auto generated code name value. This is done to prevent conflicts in code names, which must be unique. We could update our logic to ensure we don't remove that uniqueness from the end of a code name - I'll leave this up to the reader 😉.

It's also worth noting that we don't handle update events. The code name is generated from the content item name but only on creation. After that it's not updated. And, we probably want to let developers set a fully custom code name if they want to.

# Modify Our Exclusions

Now that we have our custom module and global event handler in the project, we can update our CD repository.config file to exclude items that match our new code name pattern.

 <ObjectFilters>
      <ExcludedCodeNames ObjectType="cms.webpageitem">%-localtest</ExcludedCodeNames>
      <ExcludedCodeNames ObjectType="cms.contentitem">%-localtest</ExcludedCodeNames>
 </ObjectFilters>

And, with that, we're all set!

If we need to exclude other items in the future, we can update our global event handler and add some logic to account for new Content types or other contextual information.