Create a basic module to hold custom settings
Custom modules series
This guide is a part of the Custom modules series and a prerequisite to the the following guide.
A custom module is a common requirement in digital experience projects. It allows developers to empower certain users to configure the site in new ways that are not available out-of-the-box.
This guide is the first in the series of four and shows how to create a basic custom settings module. The module will hold custom settings keys and values. Administrators and editors with sufficient permissions in the UI will be able to create and adjust the values of these settings, which you can access from your code.
The custom settings we’ll create in this guide will be global and not associated with any specific channel. If you want to create custom channel-specific settings, take a look at our later guide.
Before you start
This guide requires the following:
- Familiarity with C#, .NET Core, Dependency injection, and the MVC pattern.
- A running instance of Xperience by Kentico, preferably 29.6.1 or higher.Some features covered in the Training guides may not work in older versions.
Code samples
You can find a project with completed, working versions of code samples from this guide and others in the finished branch of the Training guides repository.
The main branch of the repository provides a starting point to code along with the guides.
The code samples in this guide are for .NET 8 only.
They come from a project that uses implicit using directives. You may need to add additional using
directives to your code if your project does not use this feature.
Define module and data class
The individual settings in our custom settings module (e.g., the Email notification sender from the video above) correspond to objects of a custom class.
Each class must belong to a Module – a container that groups related data classes. We recommend organizing your custom classes into separate modules for different features.
Create custom module
- Navigate to your Xperience administration.
- Create a new module, entering these values:
- Module name: Project settings
- Identifiers → Code name: TrainingGuides.ProjectSettings
Note, that in order to fill out the Code name manually, you need to uncheck the Pre-fill the code name automatically checkbox (checked by default).
We recommend including your customer’s namespace in the code name (e.g., TrainingGuides.). It will help you easily differentiate the custom modules and their purpose.
Create data class
Create new class within your new module. Use the following parameters:
- Class name: Global settings key
- Namespace: TrainingGuides
- Name: GlobalSettingsKey
Now if you check the CMS_Class table of the database, you’ll see a row representing your new class.
Specify Database columns for the class. Add columns with the following properties:
- The value of the setting, which you will access in code and used to influence functionality
- Field name: GlobalSettingsKeyValue
- Data type: Text
- Size: 1000
- Required: True (Enabled)
- The code name of the setting, which you will use to find the setting and its value
- Field name: GlobalSettingsKeyName
- Data type: Text
- Size: 200
- Required: True (Enabled)
- The display name of the setting, which provides a human-readable name for the listing page
- Field name: GlobalSettingsKeyDisplayName
- Data type: Text
- Size: 200
- Required: True (Enabled)
- A note field, which tells editors how to use the setting and provides additional context
- Field name: GlobalSettingsKeyNote
- Data type: Text
- Size: 2000
- Required: True (Enabled)
Column roles designations
Create fields whose names end in DisplayName, Name, and LastModified with appropriate data types to automatically map them to the Display name column, Code name column, and “Last modified” column roles, respectively.
Binary and Guid fields will automatically map to the GUID column and Binary column of the class.
These settings are important for code generation, which we will discuss later in this guide.
Switch to the Code tab to see that your new fields have been automatically mapped:
Note that you can always manually change the designations on the Code.
- The value of the setting, which you will access in code and used to influence functionality
Add a UI form for the class
To enable editors or administrators to create items of your class in the admin UI, you need a UI form.
- Create a new edit form for your class:
- UI form name: Global settings key edit
- Identifiers → Code name: GlobalSettingsKeyEdit
In this case, you can simply leave Pre-fill code name automatically checked instead of typing the code name manually. The system-generated code name will be identical.
- Switch to the Fields tab in the left menu.
- Add a new UI field for each of your class database fields:
- Click New field.
- Select a database column you want to map the field to. The available columns are sorted alphabetically.
- Fill out field settings as desired and hit Save.
- GlobalSettingsKeyDisplayName
- Field caption: Display name
- Enabled: True (Enabled)
- Form component: Text input
- GlobalSettingsKeyName
- Field caption: Settings key code name
- Enabled: True (Enabled)
- Form component: Text input
- GlobalSettingsKeyValue
- Field caption: Value
- Enabled: True (Enabled)
- Form component: Text area
- Minimum number of rows: 3
- Maximum number of rows: 5
- GlobalSettingsKeyNote
- Field caption: Notes
- Tooltip text: Explain the purpose of this settings key.
- Enabled: True (Enabled)
- Form component: Text area
- Minimum number of rows: 3
- Maximum number of rows: 15
Your custom class and edit form are now ready.
Generate code files
The next step toward accessing your class and its objects in your code in a strongly typed manner is to generate code files for it.
Dedicated info providers
We recommend using the --with-provider-class "false"
parameter to avoid generating a dedicated provider for your class.
You should use a generic provider, based on IInfoProvider<TInfo>
to access the objects of your class - see an example in our next guide .
This is the best practice unless you specifically need to retrieve your objects in a context where dependency injection is not yet available. An example is the CookieLevelConsentMapping
class in one of our Data protection guides.
Double-check
If you’ve previously generated classes that do require dedicated providers, use the --include
or --exclude
parameters to ensure that the new command does not overwrite them.
If you are working in the Training guides repository, you can use the following command. (Skip the --exclude
parameter if your code doesn’t include the CookieLevelConsentMapping
class.)
dotnet run --no-build -- --kxp-codegen --type "Classes" --location "../TrainingGuides.Entities/{type}/{name}/ " --exclude "trainingguides.cookielevelconsentmapping" --with-provider-class "false"
As a result, the system will generate a partial GlobalSettingsKeyInfo
class under the namespace matching the module name (TrainingGuides.ProjectSettings
) inside the TrainingGuides.Entities project.
Visit our documentation page for more information on the code generation parameters.
Enable CI for the new class
If your project uses Continuous integration, you need to enable the feature for the newly generated GlobalSettingsKeyInfo
class. You can achieve this by overriding the object type configuration of the class.
Rather than editing the generated files directly, we recommend performing the configuration in a new partial class file. This way, you won’t accidentally overwrite your changes the next time you generate code files.
In the TrainingGuides.Entities/Classes/Overrides folder create a partial GlobalSettingsKeyInfo
class and override its TYPEINFO
definition:
namespace TrainingGuides.ProjectSettings;
public partial class GlobalSettingsKeyInfo
{
static GlobalSettingsKeyInfo()
{
TYPEINFO.ContinuousIntegrationSettings.Enabled = true;
}
}
Build the module UI
You have created a custom module and class representing custom settings. However, at this point, administrators or editors still cannot manage the settings from the administration dashboard.
Let’s develop some UI pages for them.
The pages will have the following structure in the UI tree:
- Under the Configuration category, we will have a Project settings application.
- Under the application, there will be a listing of Global settings.
- From the Global settings page, the user can:
- edit a specific setting - through the Edit section holding an Edit global settings key page.
- create a new setting - through the Create global settings key page.
Create the application page
Store customization files separately
We recommend storing any files customizing the Xperience administration separate from the channel-specific files. In our Training guides repository and this guide, we use the TrainingGuides.Admin project for this purpose.
If you are using the main branch of our repository and your solution does not contain this project yet, you can follow the steps in the Set up the project section of this guide to define it.
If you are working with more of an enterprise-sized solution, our guide about utilizing Clean architecture in Xperience projects may be interesting to you.
In your source code, navigate to your administration customization project (e.g., TrainingGuides.Admin).
Create the following folder structure: Pages/ProjectSettings.
We recommend matching the folder name with the name of your custom module.
In the ProjectSettings folder, create a new class file, ProjectSettingsApplication.cs.
Implement your custom UI application page:
using Kentico.Xperience.Admin.Base;
using Kentico.Xperience.Admin.Base.UIPages;
using TrainingGuides.Admin.ProjectSettings;
// Register the UI application pages - see a links and more details in the info box below.
[assembly: UIApplication(
identifier: ProjectSettingsApplication.IDENTIFIER,
type: typeof(ProjectSettingsApplication),
slug: "project-settings",
name: "Project settings",
category: BaseApplicationCategories.CONFIGURATION,
icon: Icons.BoxCogwheel,
templateName: TemplateNames.SECTION_LAYOUT)]
namespace TrainingGuides.Admin.ProjectSettings;
// The class must inherit from the ApplicationPage base class.
public class ProjectSettingsApplication : ApplicationPage
{
public const string IDENTIFIER = "TrainingGuides.ProjectSettingsApplication";
}
Registration and category
Read more about registration parameters in our documentation.
Each UI application page has to be registered under a category. In this example we use Configuration - one of the pre-defined categories in Xperience.
Optionally, you can also create and register your own custom categories.
Permissions
Each application in the Xperience administration needs to declare the set of permissions it actively evaluates.
Our custom module will support viewing, creating, deleting and updating instances of the GlobalSettingsKey class. Let’s add the default system permissions, VIEW, CREATE, DELETE and UPDATE, by decorating the ProjectSettingsApplication
class with the UIPermission
attributes.
Later on you can further restrict actions (page commands) or UI pages access within the application, using any permission from this set.
The permissions correspond with those assigned to roles in the Role management application.
You can also use any custom permissions that exist in your system or have an application page without any permission requirements. Learn more about permissions.
...
using CMS.Membership;
...
[UIPermission(SystemPermissions.VIEW)]
[UIPermission(SystemPermissions.CREATE)]
[UIPermission(SystemPermissions.DELETE)]
[UIPermission(SystemPermissions.UPDATE)]
public class ProjectSettingsApplication : ApplicationPage
...
See the full ProjectSettingsApplication.cs file in our Training guides repository for your reference.
Now if you run your solution, you should be able to see your new Project settings application in the Xperience administration under Configuration.
Define the listing page
Your application is now accessible in the Xperience administration, but if you open it, you will see an empty page right now.
Instead, we want to show a list of available project settings, with an option to perform basic CRUD operations (for the users with sufficient permissions).
Let’s create a listing page and its underlying edit and create pages.
- Create a ProjectSettings/GlobalSettings folder - let’s keep all our GlobalSettingsKey-related pages in one place.
- In the GlobalSettings folder, create a new class file, GlobalSettingsListingPage.cs.
- Implement a UI page using the Listing UI page template.
Register the page as a child of
ProjectSettingsApplication
created in the previous section.Utilize the ASP .NET Core IStringLocalizer and Shared resources to localize the UI labels.
Read more about this recommended practice in our guide on multilingual in Xperience.
Note that you need to set up the Shared resources file for each project separately. See an example in our Training guides repository.Decorate the
Delete
method override with thePageCommand
attribute to restrict the action to users with permission to delete.Learn more about page commands and permissions in our documentation.
C#GlobalSettingsListingPage.csusing Kentico.Xperience.Admin.Base; using TrainingGuides.ProjectSettings; using TrainingGuides.Admin.ProjectSettings; using TrainingGuides.Admin.ProjectSettings.GlobalSettings; using CMS.Membership; using Microsoft.Extensions.Localization; [assembly: UIPage( parentType: typeof(ProjectSettingsApplication), slug: "global-settings", uiPageType: typeof(GlobalSettingsListingPage), name: "Global settings", templateName: TemplateNames.LISTING, order: 0)] namespace TrainingGuides.Admin.ProjectSettings.GlobalSettings; // The class has to inherit from ListingPage. public class GlobalSettingsListingPage : ListingPage { private readonly IStringLocalizer<SharedResources> stringLocalizer; protected override string ObjectType => GlobalSettingsKeyInfo.OBJECT_TYPE; public GlobalSettingsListingPage(IStringLocalizer<SharedResources> stringLocalizer) : base() { this.stringLocalizer = stringLocalizer; } public override async Task ConfigurePage() { // Determine which columns of your GlobalSettingsKeyInfo objects to show the user in the list view. PageConfiguration.ColumnConfigurations .AddColumn(nameof(GlobalSettingsKeyInfo.GlobalSettingsKeyDisplayName), stringLocalizer["Name"]) .AddColumn(nameof(GlobalSettingsKeyInfo.GlobalSettingsKeyValue), stringLocalizer["Value"]) .AddColumn(nameof(GlobalSettingsKeyInfo.GlobalSettingsKeyNote), stringLocalizer["Note"]) .AddColumn(nameof(GlobalSettingsKeyInfo.GlobalSettingsKeyName), stringLocalizer["Codename"]); // Add a "New settings" button at the top. This will navigate the user to a "create" page. // We will define it in the next section. PageConfiguration.HeaderActions.AddLink<GlobalSettingsCreatePage>(stringLocalizer["New setting"]); // Add an "Edit" row action. This will navigate the user to a "edit" section for a specific instance of GlobalSettingsKeyInfo object. // We will define it in the next section. PageConfiguration.AddEditRowAction<GlobalSettingsEditSection>(); // Define a "Delete" action for the user with sufficient permissions to remove an existing setting. PageConfiguration.TableActions .AddDeleteAction(nameof(Delete)); await base.ConfigurePage(); } // Ensure only a user with permissions to delete can perform the delete action. [PageCommand(Permission = SystemPermissions.DELETE)] public override Task<ICommandResponse<RowActionResult>> Delete(int id) => base.Delete(id); }
Read more about listing page template and it’s options in our documentation.
If you copy-pasted the code sample above, note that the GlobalSettingsListingPage
class references GlobalSettingsCreatePage
and GlobalSettingsEditSection
- classes that don’t exist yet. We will create them in the next section.
If you wish, comment out the two lines of code for now. When you visit the Project settings application in your administration, you should see an empty listing page, similar to this:
Define create and edit pages
Both create and edit pages for GlobalSettingsKeyInfo
objects will utilize the GlobalSettingsKeyEdit
UI form you created earlier in this guide.
Edit page
When the user goes to edit a specific instance of the GlobalSettingsKey class, their URL should contain the ID of the object that the user is currently editing, e.g., ~/admin/project-settings/global-settings/2/edit
We first need to create an edit section page to ensure a desired tree structure and correct routing. Subsequently, we will make the edit page a child of the edit section page.
Find more details in the first section of this documentation page.
In the GlobalSettings folder, create a new class file, GlobalSettingsEditSection.cs.
Implement the
GlobalSettingsEditSection
class, utilizing thePARAMETERIZED_SLUG
parameter.C#GlobalSettingsEditSection.csusing Kentico.Xperience.Admin.Base; using TrainingGuides.ProjectSettings; using TrainingGuides.Admin.ProjectSettings.GlobalSettings; // Register the UI page as child of GlobalSettingsListingPage. [assembly: UIPage( parentType: typeof(GlobalSettingsListingPage), slug: PageParameterConstants.PARAMETERIZED_SLUG, uiPageType: typeof(GlobalSettingsEditSection), name: "Edit", templateName: TemplateNames.SECTION_LAYOUT, order: 10)] namespace TrainingGuides.Admin.ProjectSettings.GlobalSettings; // Inherit from EditSectionPage and specify the type of object edited public class GlobalSettingsEditSection : EditSectionPage<GlobalSettingsKeyInfo> { }
In the GlobalSettings folder, create a new class file, GlobalSettingsEditPage.cs.
Implement your edit page:
C#GlobalSettingsEditPage.csusing Kentico.Xperience.Admin.Base; using TrainingGuides.ProjectSettings; using TrainingGuides.Admin.ProjectSettings.GlobalSettings; using Kentico.Xperience.Admin.Base.Forms; // Register page as a child of GlobalSettingsEditSection [assembly: UIPage( parentType: typeof(GlobalSettingsEditSection), slug: "edit", uiPageType: typeof(GlobalSettingsEditPage), name: "Edit global settings key", templateName: TemplateNames.EDIT, order: 0)] namespace TrainingGuides.Admin.ProjectSettings.GlobalSettings; public class GlobalSettingsEditPage : InfoEditPage<GlobalSettingsKeyInfo> { [PageParameter(typeof(IntPageModelBinder))] public override int ObjectId { get; set; } public GlobalSettingsEditPage(IFormComponentMapper formComponentMapper, IFormDataBinder formDataBinder) : base(formComponentMapper, formDataBinder) { } public override Task ConfigurePage() { // Assign your 'GlobalSettingsKeyEdit' UI form code name to the page. The assignment is case-insensitive. PageConfiguration.UIFormName = "globalsettingskeyedit"; return base.ConfigurePage(); } }
Create page
In the GlobalSettings folder, create a new class file, GlobalSettingsCreatePage.cs.
Implement the page using the
CreatePage
base class.C#GlobalSettingsCreatePage.csusing Kentico.Xperience.Admin.Base; using TrainingGuides.ProjectSettings; using TrainingGuides.Admin.ProjectSettings.GlobalSettings; using Kentico.Xperience.Admin.Base.Forms; // Register page as a child of GlobalSettingsListingPage created above. [assembly: UIPage( parentType: typeof(GlobalSettingsListingPage), slug: "create", uiPageType: typeof(GlobalSettingsCreatePage), name: "Create global settings key", templateName: TemplateNames.EDIT, order: 20)] namespace TrainingGuides.Admin.ProjectSettings.GlobalSettings; // Specify the entity which this page will create (GlobalSettingsKeyInfo) // and the page to redirect to after the entity has been cerated (GlobalSettingsEdit page) public class GlobalSettingsCreatePage : CreatePage<GlobalSettingsKeyInfo, GlobalSettingsEditPage> { public GlobalSettingsCreatePage(IFormComponentMapper formComponentMapper, IFormDataBinder formDataBinder, IPageUrlGenerator pageUrlGenerator) : base(formComponentMapper, formDataBinder, pageUrlGenerator) { } public override Task ConfigurePage() { // Assign your 'GlobalSettingsKeyEdit' UI form code name to the page. The assignment is case-insensitive. PageConfiguration.UIFormName = "globalsettingskeyedit"; return base.ConfigurePage(); } }
Learn more about the edit UI page template and it’s options here.
To see another edit page example implementation take a look at our Add a custom field to the Contact profile guide.
Build your solution and navigate to System → UI tree in the Xperience administration. If you have followed along with the example in this guide, you should be able to see your new pages in a UI tree structure:
When you navigate to your new Project settings application, you should now be able to create, view, edit, and delete custom settings, like in this video:
If you still only see an empty listing page with no New setting button, make sure you uncommented the lines in the GlobalSettingsListingPage
, referencing GlobalSettingsCreatePage
and GlobalSettingsEditSection
.
Next steps
You have learned how to create a custom key-value settings module in Xperience. The next guide in this series will demonstrate how you can access your custom settings instances through the ObjectQuery API and work with them in your code.