---
title: Understand the Identity collectors
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

Data protection laws such as the European Union’s GDPR often specify that visitors to a site must be able to see their personal data that has been collected, and they can request that it be removed. Identity collection is a key part of this process.

Let's create an identity collector for gathering visitor identities, such as contacts, members, and customers.

You can read more about GDPR compliance in the [Xperience by Kentico Documentation](https://docs.kentico.com/documentation/developers-and-admins/data-protection/gdpr-compliance.md).

## Understand the components

In the **Configuration → Data Protection** application in _Xperience by Kentico_, you'll notice tabs for **Data portability**, **Right to access**, and **Right to be forgotten**.

These correspond to aspects of the GDPR but are applicable to other regulations as well, and each is used for dealing with visitors' personal data.

The **Data portability** tab, once implemented, allows administrators to gather a visitor's data in a machine-readable format. In contrast, the **Right to access** tab collects data in a human-readable format. Finally, the **Right to be forgotten** tab allows a visitor's data to be wiped from the site.

However, the code that carries out these actions needs to be implemented, as there is no one-size-fits-all solution to the virtually limitless ways a site could collect and store personal information. The code samples from this series cover just one example site with its own specific requirements.

## Identify the first step

Data collection and erasure require an _Identity collector_ as the first step.

_Identity collectors_ are used to gather a collection of Xperience objects representing the visitor, called Identities. For example, real-world people could be represented by a _User_ object, one or more _Contact_ objects, or any custom objects, such as _Customer_, _Author_, _Contractor_, etc.

Once the identity collector gathers these identities, it passes them to other components. These components can use these identities to find objects that reference them, e.g. activities performed by a contact.

We will cover the creation of an identity collector, the first step of this process.

> **Note:** Note: The system uses identifiers, such as an email address, to find Identity objects. It is critical that any data you collect and store always contains one of the following, so that it can be found.
>
> - a reference to one of the identity objects
> - the identifier itself
>
> Consider an example where you use an email address as the identifier and create a form that collects the first and last name of a visitor but no email address. In such a case, there would be no way to find that information using a supplied email address, and GDPR compliance would be impossible when a visitor exercises their _right to be forgotten_.

## Implement the identity collector

1. Create a _Collectors_ folder in _\~/Features/DataProtection_ of the _TrainingGuides.Web_ project.
2. Add an _IdentityCollector.cs_ file to the folder, and add a namespace matching the folder structure (`TrainingGuides.Web.Features.DataProtection.Collectors`).
3. Implement the `IIdentityCollector` interface.
4. Take info providers for _contacts_, _members_, and _customers_ as parameters in the primary constructor.
5. Implement the `Collect(IDictionary<string, object> dataSubjectFilter, List<BaseInfo> identities)` method from the interface.
6. Use the `PersonalDataConstants.DATA_SUBJECT_IDENTIFIER_KEY` constant to get the data subject email.
7. Retrieve any contacts, members, and customers that have this email and add them to the `identities` parameter.
8. Define additional logic that creates a new contact and a new customer with the supplied email address if the queries find none.

   > **Info:** It is possible for form data containing an email address to exist even if there is no contact or customer in the database with that email.
   >
   > These dummy objects allow the _Data collector_, which will be covered in the next part of this series, to find form data and third-party addresses associated with the provided email address even when no contact or customer exists.

```csharp title="IdentityCollector.cs"
using CMS.Commerce;
using CMS.ContactManagement;
using CMS.DataEngine;
using CMS.DataProtection;
using CMS.Membership;

namespace TrainingGuides.Web.Features.DataProtection.Collectors;

public class IdentityCollector(IInfoProvider<ContactInfo> contactInfoProvider, IInfoProvider<MemberInfo> memberInfoProvider, IInfoProvider<CustomerInfo> customerInfoProvider) : IIdentityCollector
{
    public void Collect(IDictionary<string, object> dataSubjectFilter, List<BaseInfo> identities)
    {
        // Does nothing if the identifier input value is not available or empty
        if (!dataSubjectFilter.TryGetValue(PersonalDataConstants.DATA_SUBJECT_IDENTIFIER_KEY, out object? value))
        {
            return;
        }
        string? email = value as string;
        if (string.IsNullOrWhiteSpace(email))
        {
            return;
        }

        var contacts = contactInfoProvider
            .Get()
            .WhereEquals(nameof(ContactInfo.ContactEmail), email)
            .ToList();

        // If no contact exists with the provided email, create a new one.
        // This will allow us to retrieve form submissions that contain the email even if they are not currently tied to a contact.
        if (contacts.Count() == 0)
        {
            contacts.Add(new ContactInfo() { ContactEmail = email });
        }

        identities.AddRange(contacts);

        var members = memberInfoProvider
            .Get()
            .WhereEquals(nameof(MemberInfo.MemberEmail), email)
            .ToList();

        identities.AddRange(members);

        var customers = customerInfoProvider
            .Get()
            .WhereEquals(nameof(CustomerInfo.CustomerEmail), email)
            .ToList();

        // If no customer exists with the provided email, create a new one.
        // This will allow us to retrieve third-party customer and order addresses that contain the email even if they are associated with a different customer.
        if (customers.Count() == 0)
        {
            customers.Add(new CustomerInfo() { CustomerEmail = email });
        }

        identities.AddRange(customers);
    }
}
```

## Register the Identity collector

Now that the Identity collector is in place, we can register it.

1. Create a new `DataProtectionRegistrationModule` class in _\~/Features/DataProtection/Shared_ folder of the _TrainingGuides.Web_ project.
2. Inherit from Xperience’s `Module` class, and override `OnInit` to add `IdentityCollector` to the `IdentityCollectorRegister`.
3. Resolve an `IServiceProvider` and use it to create an instance of the `IdentityCollector`.
4. Register the module using an assembly attribute.

```csharp title="DataProtectionRegistrationModule.cs"
using CMS;
using CMS.Core;
using CMS.DataEngine;
using CMS.DataProtection;
using TrainingGuides.Web.Features.DataProtection.Collectors;
using TrainingGuides.Web.Features.DataProtection.Shared;

[assembly: RegisterModule(
    type: typeof(DataProtectionRegistrationModule))]

namespace TrainingGuides.Web.Features.DataProtection.Shared;

public class DataProtectionRegistrationModule : Module
{
    public DataProtectionRegistrationModule()
        : base("DataProtectionRegistration")
    {
    }

    // Contains initialization code that is executed when the application starts
    protected override void OnInit(ModuleInitParameters parameters)
    {
        var serviceProvider = parameters.Services.GetRequiredService<IServiceProvider>();

        base.OnInit(parameters);

        // Adds the IdentityCollector to the collection of registered identity collectors
        IdentityCollectorRegister.Instance.Add(ActivatorUtilities.CreateInstance<IdentityCollector>(serviceProvider));
    }
}
```

Now, Xperience will know how to utilize the `IdentityCollector` class for data portability, collection, and erasure requests. When you implement a _Data collector_, covered [later on in this series](https://docs.kentico.com/modules/data-protection/collect-personal-data.md#collect-data), Xperience will use the identity collector class to gather a list of identity objects and pass them to the data collector to retrieve associated data.

> **Note:** The `IdentityCollectorRegister`, like the other data protection registers, is a queue that can contain multiple collectors. If the same class is registered to this queue more than once, it will be called multiple times.
