Contents

How to Prevent Duplicate Event Registrations in Dynamics 365 Customer Insights (with Form Submission Validation)s

Some months ago, I got the question if there was a way to limit the Registrations for an event to one Registration per person in Dynamics 365 Customer Insights Journeys. I thought then, that there is no setting for this, so I decided to investigate, and after some research I stumbled into this documentation of Microsoft: Customize form submission validation

Then I thought that if there is a way of validating a form before the submission, we could avoid it altogether. So I set my mind on it and after some testing and validation, I developed a Plugin that will validate the event registration by running on form Submission and will prevent the submission to be sent in case there is already an event registration.

Yes, ok, but Martin, what does this mean?

There is a way to limit the registration process by checking if this person is already registered in the Event

HOW?

Step 1: data-form-validation in Event Registration Form

It all starts by setting the data-form-validation to true in the form where you want to integrate this:

/posts/custom-form-submission-validation/image1.png
Find the Form Tag in your Form HTML and simply add the data-validate-submission='true' part

Without this, your plugin won’t fire, because this will allow us to trigger the plugins registered under the “msdynmkt_validateformsubmission” Message.

Here an example in code:

    <form aria-label="{{EventName}}" class="marketingForm" data-validate-submission="true"
      data-successmessage="Thank you for submitting the form." data-errormessage="There was an error, please try again."
      data-waitlistmessage="You have been added to the waitlist.">

Step 2: Create a PlugIn

Now create a plugin that will first deserialize the form parameters, then iterate through them to find the email and the event id and retrieve with a fetchxml if there are registrations for the event with the email you got from form:

And for our low-code/no-code users, here’s what the plugin does in plain language: it reads the submission, checks if the email is already registered for that event, and if yes, it blocks the submission.

using System;
using Microsoft.Xrm.Sdk;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
using Microsoft.Xrm.Sdk.Query;

namespace UniqueEmailRegistrations
{

    public class CheckIfEmailRegistered : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            var tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            var service = factory.CreateOrganizationService(context.UserId);


            var requestString = context.InputParameters.Contains("msdynmkt_formsubmissionrequest")
                ? (string)context.InputParameters["msdynmkt_formsubmissionrequest"]
                : null;

            if (string.IsNullOrWhiteSpace(requestString))
            {
                tracing.Trace("No msdynmkt_formsubmissionrequest found; skipping validation.");
                context.OutputParameters["msdynmkt_validationresponse"] = Serialize(new ValidateFormSubmissionResponse
                {
                    IsValid = true,
                    ValidationOnlyFields = new List<string>()
                });
                return;
            }

            var submission = Deserialize<FormSubmissionRequest>(requestString);

            string GetField(string key)
            {
                if (submission?.Fields != null && submission.Fields.TryGetValue(key, out var value))
                    return value;
                return null;
            }

            var emailRaw = GetField("emailaddress1");
            var eventRaw = GetField("event");

            if (string.IsNullOrWhiteSpace(emailRaw))
            {
                tracing.Trace("Email field is empty or missing; skipping duplicate validation.");
                context.OutputParameters["msdynmkt_validationresponse"] = Serialize(new ValidateFormSubmissionResponse
                {
                    IsValid = true,
                    ValidationOnlyFields = new List<string>()
                });
                return;
            }


            if (!Guid.TryParse(eventRaw, out var eventId) || eventId == Guid.Empty)
            {
                tracing.Trace($"Event field missing or not a GUID: '{eventRaw}'. Skipping duplicate validation.");
                context.OutputParameters["msdynmkt_validationresponse"] = Serialize(new ValidateFormSubmissionResponse
                {
                    IsValid = true,
                    ValidationOnlyFields = new List<string>()
                });
                return;
            }

            var fetchXml = $@"
                <fetch>
                  <entity name='msevtmgt_eventregistration'>
                    <attribute name='msevtmgt_eventregistrationid' />
                    <filter>
                      <condition attribute='msevtmgt_eventid' operator='eq' value='{eventId}' />
                    </filter>
                    <link-entity name='contact' from='contactid' to='msevtmgt_contactid' link-type='inner'>
                      <filter>
                        <condition attribute='emailaddress1' operator='eq' value='{emailRaw}' />
                      </filter>
                    </link-entity>
                  </entity>
                </fetch>";

            tracing.Trace("Running duplicate check with FetchXML:");
            tracing.Trace(fetchXml);

            bool alreadyRegistered = false;
            try
            {
                var result = service.RetrieveMultiple(new FetchExpression(fetchXml));
                alreadyRegistered = (result != null && result.Entities != null && result.Entities.Count > 0);
                tracing.Trace($"Duplicate check result: alreadyRegistered={alreadyRegistered}");
            }
            catch (Exception ex)
            {
                tracing.Trace("Error during duplicate check: " + ex.ToString());
            }

            var response = new ValidateFormSubmissionResponse
            {
                IsValid = !alreadyRegistered,
                ValidationOnlyFields = alreadyRegistered ? new List<string> { "emailaddress1" } : new List<string>()
            };

            context.OutputParameters["msdynmkt_validationresponse"] = Serialize(response);
        }
        private T Deserialize<T>(string jsonString)
        {
            var serializer = new DataContractJsonSerializer(typeof(T));
            T result;
            using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
            {
                result = (T)serializer.ReadObject(stream);
            }
            return result;
        }

        private string Serialize<T>(T obj)
        {
            string result;
            var serializer = new DataContractJsonSerializer(typeof(T));
            using (MemoryStream memoryStream = new MemoryStream())
            {
                serializer.WriteObject(memoryStream, obj);
                result = Encoding.Default.GetString(memoryStream.ToArray());
            }
            return result;
        }
        public class FormSubmissionRequest { public Dictionary<string, string> Fields { get; set; } }
        public class ValidateFormSubmissionResponse { public bool IsValid { get; set; } public List<string> ValidationOnlyFields { get; set; } }

    }
}

Step 3: Register the plugIn

Make sure you follow the directives of Microsoft and you register it:

  • The step name is: “msdynmkt_validateformsubmission”
  • The Execution Mode is Synchronous
  • The Execution Order is 10 (This is to prevent conflict with plugins that microsoft have running)
  • The Event Pipeline Stage Of Execution is Post Operation

Step 4: Dynamically provide the event guid in the form

One has to add the Event guid to the form, and thanks to Real Time Marketing, we can do this now dynamically! So if we add a new custom Field and don’t forget to call it “event”. Then we change the Field Label for the dynamic text that is the Event (msevtmgt_eventid) of the Event from Form, we’ll have it:

/posts/custom-form-submission-validation/image2.png
Don't forget to call 'EventId', this will play a role in your plugin

Then we can copy this dynamic placeholder as the value of the input of the newly created field and then hide it, so the users don’t see it. The HTML should look similar to this one:

  <div class="textFormFieldBlock" data-editorblocktype="TextFormField" data-prefill="false" data-hide="hide">
    <label title="Field label" for="shorttext564-1758226821033">
      <span class="msdynmkt_personalization">{{EventId}}</span><br>
    </label>
    <input id="shorttext564-1758226821033" type="text" name="event" placeholder="Field label" title="Field label" maxlength="256" value="{{EventId}}">
  </div>

Step 5: Adjust your error Message

One should make sure that the Users get an error message that they can understand, that’s why it’s recommendable to adjust the HTML of the form to offer more information on the validation. Here is an HTML that you can add to the form, so that if the submission gives an error, it will show an explanatory message next to the submission failed:

<script>
document.addEventListener("d365mkt-afterformsubmit", function(event) {
  if (!event.detail.successful) {  
  const baseText =  "Error submitting the form";
  const oneTimeMsg = "You can only register once";

  const selector = ".onFormSubmittedFeedbackInternalContainer[data-submissionresponse='error'] .onFormSubmittedFeedbackMessage";

  function applyOnce() {
    const msgEl = document.querySelector(selector);
    if (!msgEl) return false;

    const targetHTML = baseText + "<br>" + oneTimeMsg;
    if (msgEl.innerHTML.trim() !== targetHTML) {
      msgEl.innerHTML = targetHTML;
    }
    return true;
  }

  if (applyOnce()) return;

  const observer = new MutationObserver((_, obs) => {
    if (applyOnce()) {
      obs.disconnect();
    }
  });

  observer.observe(document.body, { childList: true, subtree: true });

  setTimeout(() => observer.disconnect(), 5000);
  }
});
</script>

Tip: Don’t forget to add this between the Form Tags of your Form HTML, Real Time Marketing requires this to work

But I don’t know how to code, what do I do?

You can always download and install my sample Solution. This will check automatically if there is a contact registered with the email address that is trying to register now and it will give the users an error like the following one:

/posts/custom-form-submission-validation/image3.png
This is not very descriptive...

Just be sure to make the Step 1 for the plugin to trigger and if you’re feeling brave, I really encourage and recommend the step 5, as it will make the user experience way better 😉

/posts/custom-form-submission-validation/image4.png
Simple but effective

If you’ve tried this approach, let me know on LinkedIn — I’d love to hear how you’re handling event registrations!