When customizing forms in Microsoft Dynamics 365, it’s common to need validation logic before saving a record — for example, checking for duplicates or ensuring data consistency.
This article shows how to manage the OnSave event and perform a custom validation call before allowing the record to be saved. The validation can invoke a custom action or Web API request to check for duplicates or other business rules.
Overview
We’ll use two key components:
OnSave handler — intercepts the save event and prevents it temporarily.
CheckEntity function — executes a validation call (e.g., to a custom action or an API endpoint).
If the validation finds a duplicate or issue, the user will be prompted with a confirmation dialog before proceeding.
The OnSave Function
Here’s a sample implementation of the OnSave event handler:
OnSave: function (ExecutionContext) {
formContext = ExecutionContext. getFormContext();
if (!SAVE) {
ExecutionContext.getEventArgs( ).preventDefault();
CustomNamespace.Entity. CheckEntity(formContext, function () {
var confirmStrings = {
subtitle: "Duplicate record found",
text: "A similar record already exists. Do you want to continue saving?",
title: "Duplicate Detected",
confirmButtonLabel: "CONFIRM",
cancelButtonLabel: "CANCEL"
};
var confirmOptions = { height: 350, width: 500 };
Xrm.Navigation. openConfirmDialog( confirmStrings, confirmOptions).then(
function (success) {
if (success.confirmed) {
SAVE = true;
formContext.data.save();
} else {
return; // cancel save
}
}
);
}).then((existsDuplicate) => {
if (existsDuplicate === false) {
SAVE = true;
formContext.data.save();
}
});
}
SAVE = false;
},
Key Notes:
The code uses
preventDefault()to stop the default save action until validation completes.It calls a validation function (
CheckEntity) to verify data before saving.If a potential duplicate is found, the user is shown a confirmation dialog.
If no duplicate exists, the record is saved automatically.
This ensures clean control over the save process and prevents unwanted record duplication.
The Validation Function
The CheckEntity function performs a validation call — for instance, invoking a custom action in Dynamics 365 that returns whether a duplicate exists.
CheckEntity: function (formContext, callback) {
return new Promise((resolve, reject) => {
var field1 = formContext.getAttribute(" field1")?.getValue() || "";
var field = formContext.getAttribute(" field")?.getValue() || "";
var field3 = formContext.getAttribute(" field3")?.getValue() || "";
var combinedField = (field1 + " " + field).trim();
var lookupField = formContext.getAttribute(" lookup_field")?.getValue();
var entityId = formContext.data.entity.getId( )?.replace(/[{}]/g, "") || "00000000-0000-0000-0000- 000000000000";
var entityObject = {
"@odata.type": "Microsoft.Dynamics.CRM. entityname",
"entityid": entityId,
"combinedfield": combinedField,
"field3": field3
};
if (lookupField && lookupField.length > 0) {
entityObject["lookup_field@ odata.bind"] = `/relatedentity(${lookupField[ 0].id.replace(/[{}]/g, "")})`;
}
var request = {
ValidateEntityPreOperation_ EntityObject: entityObject,
getMetadata: function () {
return {
boundParameter: null,
parameterTypes: {
ValidateEntityPreOperation_ EntityObject: { typeName: "mscrm.entityname", structuralProperty: 5 }
},
operationType: 0,
operationName: "custom_ ValidateEntityPreOperation"
};
}
};
Xrm.WebApi.execute(request). then(async response => {
const contentType = response.headers.get("content- type");
if (contentType && contentType.indexOf(" application/json") !== -1) {
var jsonResponse = await response.json();
if (jsonResponse && callback) {
callback();
resolve(true); // duplicate found
}
} else {
resolve(false); // no duplicate
}
}).catch(error => {
console.error("Error in CheckEntity:", error);
reject(null);
});
});
},