In Dynamics 365 model-driven apps, it's common to perform validations before saving a record. While synchronous validations are straightforward, asynchronous operations, such as server-side checks or API calls, introduce complexity. This article explores how to effectively cancel a save operation based on the outcome of an asynchronous process.(Microsoft Learn)
The Challenge with Asynchronous Validations
Traditionally, to prevent a save operation, developers use the preventDefault()
method within the OnSave
event handler:(Dreaming in CRM & Power Platform)
formContext.data.entity.addOnSave(function (e) {
var eventArgs = e.getEventArgs();
if (/* validation fails */) {
eventArgs.preventDefault();
}
});
However, this approach falls short when the validation involves asynchronous operations. For instance, consider a scenario where you need to check if a user with a specific phone number exists:(Dreaming in CRM & Power Platform)
formContext.data.entity.addOnSave(function (e) {
Xrm.WebApi.retrieveMultipleRecords("systemuser", "?$filter=homephone eq '12345'")
.then(function (result) {
if (result.entities.length > 0) {
e.getEventArgs().preventDefault();
}
});
});
In this case, the preventDefault()
method is called after the asynchronous operation completes. However, by that time, the save operation may have already proceeded, rendering the prevention ineffective.(Stack Overflow)
A Workaround: Preemptive Save Cancellation and Conditional Resave
To address this, Andrew Butenko proposed a strategy where the save operation is initially canceled, and then conditionally retriggered based on the asynchronous validation result. Here's how it works:(xrmtricks.com)
-
Cancel the Save Operation Immediately: Use
preventDefault()
at the beginning of theOnSave
handler to halt the save process.(Microsoft Learn) -
Perform Asynchronous Validation: Execute the necessary asynchronous operations, such as API calls or data retrievals.
-
Conditionally Resave: If the validation passes, programmatically trigger the save operation again.
Here's an implementation example:
formContext.data.entity.addOnSave(function (e) {
var eventArgs = e.getEventArgs();
eventArgs.preventDefault(); // Step 1: Cancel the save operation
Xrm.WebApi.retrieveMultipleRecords("systemuser", "?$filter=homephone eq '12345'")
.then(function (result) {
if (result.entities.length === 0) {
// Step 3: Resave if validation passes
formContext.data.save();
} else {
// Validation failed; do not resave
Xrm.Navigation.openAlertDialog({ text: "A user with this phone number already exists." });
}
});
});
Leveraging Asynchronous OnSave
Handlers
With the introduction of asynchronous OnSave
handlers in Dynamics 365, developers can now return a promise from the OnSave
event handler, allowing the platform to wait for the asynchronous operation to complete before proceeding with the save.(Microsoft Learn)
To utilize this feature:
-
Enable Async
OnSave
Handlers: In your app settings, navigate to Settings > Features and enable the Async OnSave handler option.(Microsoft Learn) -
Implement the Async Handler: Return a promise from your
OnSave
event handler. If the promise is resolved, the save proceeds; if rejected, the save is canceled.
Example:
formContext.data.entity.addOnSave(function (e) {
return Xrm.WebApi.retrieveMultipleRecords("systemuser", "?$filter=homephone eq '12345'")
.then(function (result) {
if (result.entities.length > 0) {
return Promise.reject(new Error("A user with this phone number already exists."));
}
return Promise.resolve();
});
});
In this setup, if the validation fails, the promise is rejected, and the save operation is canceled automatically.
Handling asynchronous validations during save operations in Dynamics 365 requires careful implementation. By either preemptively canceling the save and conditionally resaving or leveraging the asynchronous OnSave
handlers, developers can ensure data integrity and provide a seamless user experience.