In Xperience, consents are records used to inform visitors about the means of collecting and manipulating their personal data by the system, administrators, marketers and anyone else who has access to said data, including third parties to whom this data is forwarded.
You can use consents to comply with the requirements of the GDPR and other personal data regulations.
Whenever any personal data of a visitor is obtained, it is necessary to have a consent agreement from the visitor to legally process this data. This includes tracking of contacts and their activities. Every consent agreement of a visitor is directly connected to a corresponding contact. Deleting contacts from the system therefore also causes deletion of the contact's consent agreements.
Prerequisite
Since consent agreements are directly connected to contacts, you need to enable contact tracking in your Xperience application.
This page describes how to set up consent functionality on Xperience websites. To learn how to create consents in the Xperience administration, see Consent management.
When creating a consent for the tracking of contacts and activities, you need to provide a way for visitors to adjust their allowed cookie level. The system only performs the tracking for visitors whose cookie level is Visitoror higher.
To ensure that visitors are not tracked until they give the tracking consent, set the default cookie level in the Xperience administration:
To allow visitors to give agreements with the tracking consent on your site, you need to develop corresponding components. For example, you can implement tracking consent functionality as a view component (opens in a new tab).
Start by defining the tracking consent in the Xperience administration interface (in the Data protection application on the Consents tab).
We recommend creating a view model class in your project to represent data related to the tracking consent. Include the following properties:
string property for storing the consent's short textbool property to indicate whether an agreement with the consent was givenstring property for passing the current page URL as the return URLpublic class TrackingConsentViewModel
{
public string ShortText { get; set; }
public bool IsAgreed { get; set; }
public string ReturnUrl { get; set; }
}Create a new view component (opens in a new tab) class in your project:
Initialize instances of services, which are necessary to manage the tracking consent:
IInfoProvider<ConsentInfo> – to load consent data.IConsentAgreementService– to manage consent agreements.ICurrentCookieLevelProvider– to manage the current visitor's cookie level.IWebPageDataContextRetriever and IWebPageUrlRetriever – to obtain the URL of the current page.IPreferredLanguageRetriever – to get the currently selected content language.We recommend using dependency injection to initialize service instances.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using CMS.ContactManagement;
using CMS.DataEngine;
using CMS.DataProtection;
using CMS.Helpers;
using CMS.Websites;
using Kentico.Content.Web.Mvc;
using Kentico.Content.Web.Mvc.Routing;
public class TrackingConsentViewComponent : ViewComponent
{
private readonly IConsentAgreementService consentAgreementService;
private readonly ICurrentCookieLevelProvider currentCookieLevelProvider;
private readonly IInfoProvider<ConsentInfo> consentInfoProvider;
private readonly IWebPageDataContextRetriever webPageDataContextRetriever;
private readonly IWebPageUrlRetriever urlRetriever;
private readonly IPreferredLanguageRetriever preferredLanguageRetriever;
public TrackingConsentViewComponent(IConsentAgreementService consentAgreementService,
ICurrentCookieLevelProvider currentCookieLevelProvider,
IInfoProvider<ConsentInfo> consentInfoProvider,
IWebPageDataContextRetriever webPageDataContextRetriever,
IWebPageUrlRetriever urlRetriever,
IPreferredLanguageRetriever preferredLanguageRetriever)
{
this.consentAgreementService = consentAgreementService;
this.currentCookieLevelProvider = currentCookieLevelProvider;
this.consentInfoProvider = consentInfoProvider;
this.webPageDataContextRetriever = webPageDataContextRetriever;
this.urlRetriever = urlRetriever;
this.preferredLanguageRetriever = preferredLanguageRetriever;
}
}Implement the component's InvokeAsync method to display information about the tracking consent:
public async Task<IViewComponentResult> InvokeAsync()
{
// Gets the related tracking consent
// Fill in the code name of the appropriate consent object in Xperience
ConsentInfo consent = consentInfoProvider.Get("SampleTrackingConsent");
if (consent != null)
{
// Gets the current contact
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
// Gets the code name of the currently selected language
var preferredLanguage = preferredLanguageRetriever.Get();
// Sets the default cookie level for contacts who have revoked the tracking consent
// Required for scenarios where one contact uses multiple browsers
if ((currentContact != null) && !consentAgreementService.IsAgreed(currentContact, consent))
{
// Gets the default cookie level for the current channel and sets it for the contact
var defaultCookieLevel = currentCookieLevelProvider.GetDefaultCookieLevel();
currentCookieLevelProvider.SetCurrentCookieLevel(defaultCookieLevel);
}
var consentModel = new TrackingConsentViewModel
{
// Adds the consent's short text to the model
ShortText = (await consent.GetConsentTextAsync(preferredLanguage)).ShortText,
// Checks whether the current contact has given an agreement for the tracking consent
IsAgreed = (currentContact != null) && consentAgreementService.IsAgreed(currentContact, consent),
// Adds the current page URL to the model
ReturnUrl = webPageDataContextRetriever.TryRetrieve(out var currentWebPageContext)
? (await urlRetriever.Retrieve(currentWebPageContext.WebPage.WebPageItemID, preferredLanguage)).RelativePath
// Gets the URL from the request context for pages using custom routing
: (HttpContext.Request.PathBase + HttpContext.Request.Path).Value
};
// Displays a view with tracking consent information and actions
return View("~/Components/ViewComponents/TrackingConsent/_TrackingConsent.cshtml", consentModel);
}
return Content(string.Empty);
}Add the view referenced in your view component's invoke method. Display the text of the tracking consent and inputs for giving or revoking agreements. Depending on your preferences and legal requirements, you can hide the view's output for visitors who have given the tracking consent or display alternative content (for example the option to revoke the consent).
@using Kentico.Content.Web.Mvc;
@model TrackingConsentViewModel
@* Displays content only if the current contact has not yet given a tracking consent agreement *@
@if (!Model.IsAgreed)
{
var routeData = new Dictionary<string, string> { { "returnUrl", Model.ReturnUrl } };
<div>
@* Displays the consent text*@
@Html.Raw(@Model.ShortText)
</div>
@* Displays an input for giving a consent agreement *@
@* Replace "TrackingConsent" with the name of your controller that handles consent agreement requests *@
<form asp-action="Agree"
asp-controller="TrackingConsent"
asp-antiforgery="true"
asp-all-route-data="routeData"
method="post">
<input type="submit" value="I agree">
</form>
}Add a controller that handles requests for adding consent agreements:
Initialize instances of services required to manage the tracking consent (some of the same services described for the View component code).
Define a POST action for creating consent agreements.
Retrieve the visitor's contact using the ContactManagementContext API (see Contact configuration).
Modify the visitor's cookie level according to the performed action – call the SetCurrentCookieLevel method of the ICurrentCookieLevelProvider service.
Create consent agreements using the Agree method of the IConsentAgreementService instance.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Agree(string returnUrl)
{
// Gets the related tracking consent
ConsentInfo consent = consentInfoProvider.Get("SampleTrackingConsent");
if (consent != null)
{
// Sets the visitor's cookie level to 'All' (enables contact tracking)
currentCookieLevelProvider.SetCurrentCookieLevel(Kentico.Web.Mvc.CookieLevel.All.Level);
// Gets the current contact and creates a consent agreement
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
if (currentContact != null)
{
consentAgreementService.Agree(currentContact, consent);
}
return Redirect(returnUrl);
}
return new StatusCodeResult(StatusCodes.Status400BadRequest);
}Invoke the view component from a suitable location in your website's content. For example, use the main layout to ensure that the input to give the tracking consent is displayed on all pages.
...
<html>
<head >
...
</head>
<body>
<div>
@* Invokes 'TrackingConsentViewComponent' *@
<vc:tracking-consent />
...Visitors on your website now have an option to give agreements with your tracking consent. New visitors are not tracked as contacts until they positively confirm that they agree with the tracking.
If you have any functionality that stores or processes personal data, you can create conditions to ensure that it only runs for contacts who have given consent.
IConsentAgreementService interface and make it available in your code.IsAgreed method of the IConsentAgreementService instance.To ensure compliance of your website with the GDPR or other data protection regulations, you need to give visitors the option to view and revoke their previously given consent agreements. We recommend creating a privacy page, where visitors can view the consents for which they have given agreements and potentially revoke them.
"It shall be as easy to withdraw as to give consent."
(Source: GDPR Article 7 (opens in a new tab), Paragraph 3)
The following sections describe how to create a privacy page using MVC architecture. The privacy page lists accepted consents for visitors, and allows them to view the full consent details or revoke their agreements.
DataProtectionEvents.RevokeConsentAgreementglobal event to clear related cookies and personal data.Add a model class that represents the list of consents for which visitors have given agreements.
using System.Collections.Generic;
using System.Linq;
using CMS.DataProtection;
public class ConsentListingModel
{
public IEnumerable<Consent> Consents { get; set; } = Enumerable.Empty<Consent>();
}Consent and ConsentInfo objects
The Xperience API uses ConsentInfo objects to work with consents in general. However, when you load consents for which a contact has given an agreement, the ConsentAgreementService.GetAgreedConsents method returns a collection of Consent objects. Instances of the Consent class provide the GetConsentText method, which automatically retrieves either the current texts of the given consent or the texts of the archived consent version for which the agreement was given.
When displaying accepted consents to visitors, always work with Consent objects to ensure that the correct consent text version is used (for scenarios where the consent text was changed after agreements had been given).
For more information, see the View archived consents section of the Consent management page.
Tip: You can directly use an IEnumerable<Consent> collection as a model if you do not need to work with any other data in your controller or views. However, having a dedicated model class allows you to extend the model based on future requirements.
Add a model class that represents the full details of a consent, including properties for the short text, full text, and any other information about the consent you may need.
public class ConsentDetailsModel
{
public string ConsentShortText { get; set; }
public string ConsentFullText { get; set; }
public string ConsentDisplayName { get; set; }
}Create a new controller class or edit an existing one:
Initialize instances of the IConsentAgreementService and IInfoProvider<ConsentInfo> services (and any other required services), and make them available in the controller's code:
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using CMS.ContactManagement;
using CMS.DataEngine;
using CMS.DataProtection;
using Kentico.Content.Web.Mvc.Routing;
public class PrivacyPageController : Controller
{
private readonly IConsentAgreementService consentAgreementService;
private readonly IInfoProvider<ConsentInfo> consentInfoProvider;
private readonly IPreferredLanguageRetriever preferredLanguageRetriever;
public PrivacyPageController(IConsentAgreementService consentAgreementService,
IInfoProvider<ConsentInfo> consentInfoProvider,
IPreferredLanguageRetriever preferredLanguageRetriever)
{
this.consentAgreementService = consentAgreementService;
this.consentInfoProvider = consentInfoProvider;
this.preferredLanguageRetriever = preferredLanguageRetriever;
}
}We recommend using dependency injection to initialize service instances.
Implement a GET action which retrieves a list of all consents accepted by the visitor:
ContactManagementContext API (see Contact configuration).GetAgreedConsents method of the IConsentAgreementService instance./// <summary>
/// Loads and displays consents for which visitors have given agreements.
// / </summary>
public ActionResult Index()
{
// Gets the current visitor's contact
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
var consentListingModel = new ConsentListingModel();
// Does not attempt to load consent data if the current contact is not available
// This occurs if contact tracking is disabled, or for visitors who have not given an agreement with the tracking consent
if (currentContact != null)
{
// Gets all consents for which the current contact has given an agreement
consentListingModel.Consents = consentAgreementService.GetAgreedConsents(currentContact);
}
return View(consentListingModel);
}Implement a GET action which retrieves the full details of a specified consent:
/// <summary>
/// Display details of the specified consent.
/// </summary>
public ActionResult ConsentDetails(int consentId)
{
// Gets a list of consents for which the current contact has given an agreement
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
IEnumerable<Consent> consents = consentAgreementService.GetAgreedConsents(currentContact);
// Gets the consent matching the identifier for which the details were requested
// Using this approach to get objects of the 'Consent' class is necessary to ensure that the correct consent text
// is displayed, either from the current consent text or the archived consent version for which the agreement was given
Consent consent = consents.FirstOrDefault(c => c.Id == consentId);
// Displays the privacy page (consent list) if the specified consent identifier is not valid
if (consent == null)
{
return View("Index");
}
// Gets the consent text in the currently selected language
ConsentText consentText = consent.GetConsentText(preferredLanguageRetriever.Get());
// Sets the consent's details into the view model
var model = new ConsentDetailsModel
{
ConsentDisplayName = consent.DisplayName,
ConsentShortText = consentText.ShortText,
ConsentFullText = consentText.FullText
};
return View(model);
}Implement a POST action that revokes agreements for a specified consent:
IInfoProvider<ConsentInfo>().Get method).Revoke method of the IConsentAgreementService instance./// <summary>
/// Revokes the current contact's agreement with the specified consent.
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Revoke(int consentId)
{
// Gets the related consent object
ConsentInfo consent = consentInfoProvider.Get(consentId);
// Gets the current visitor's contact
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
if (consent != null && currentContact != null)
{
// Revokes the consent agreement
consentAgreementService.Revoke(currentContact, consent);
}
return RedirectToAction("Index");
}Websites and their related functionality often contain multiple locations where visitors can revoke consents. To ensure that revocations are handled correctly in all cases, we recommend assigning a custom handler to the system's DataProtectionEvents.RevokeConsentAgreement global event. The handler provides a central place where you can perform additional actions whenever a consent agreement is revoked.
The following are typical scenarios to handle in the RevokeConsentAgreementevent:
SetCurrentCookieLevel method of the ICurrentCookieLevelProvider service and set the default cookie level from the website channel's settings.using System;
using CMS;
using CMS.Core;
using CMS.DataEngine;
using CMS.DataProtection;
using CMS.Helpers;
// Registers the custom module into the system
[assembly: RegisterModule(typeof(CustomDataProtectionModule))]
internal class CustomDataProtectionModule : Module
{
private ICurrentCookieLevelProvider currentCookieLevelProvider;
// Module class constructor, the system registers the module under the name "CustomDataProtection"
public CustomDataProtectionModule()
: base("CustomDataProtection")
{
}
// Contains initialization code that is executed when the application starts
protected override void OnInit(ModuleInitParameters parameters)
{
// Resolves service dependencies from the IoC container
currentCookieLevelProvider = parameters.Services.GetRequiredService<ICurrentCookieLevelProvider>();
// Assigns a handler to the RevokeConsentAgreement event
DataProtectionEvents.RevokeConsentAgreement.Execute += RevokeConsentHandler;
}
private void RevokeConsentHandler(object? sender, RevokeConsentAgreementEventArgs e)
{
// For the tracking consent, lowers the cookie level to the website channel's default in order to disable tracking
if (e.Consent.ConsentName.Equals("SampleTrackingConsent", StringComparison.OrdinalIgnoreCase))
{
currentCookieLevelProvider.SetCurrentCookieLevel(currentCookieLevelProvider.GetDefaultCookieLevel());
}
}
}Add appropriate views to your project to display the consent list (privacy page), the consent details and revocation actions.
Consent text values may contain HTML tags added via the editor in the Xperience administration (e.g., for formatting). To ensure that the text is displayed correctly, disable the HTML encoding for the values using the Html.Raw method.
<h3>Short text</h3>
<p>@Html.Raw(Model.ConsentShortText)</p>
<h3>Full text</h3>
<p>@Html.Raw(Model.ConsentFullText)</p>Visitors on your site are now able to view a list of all consents for which they have given an agreement. They can also access the full details of the given consents and revoke their agreements if required.