Async OnSave handlers in DataVerse/ Dynamics 365 forms to cancel save operation, stop save till certain async events are executed. Check this out!

OnSave event handlers on an entity/table form in Dynamics 365/ DataVerse has been there for ages. And I have hardly come across implementations which does not have scripts written onsave event of an entity form.

With onsave event handlers, the following requirements frequently come up.

  • Cancel save event of form in based on custom validations from server side.
  • Cancel save event of form based on results of async event.
  • Wait for multiple asynchronous events inside your onSave event handler to complete before the save event is executed.
  • Show confirmation on click on save of an entity form in Model driven apps.

So you quite understandably there are innumerable blogs on how you can achieve this. You might be wondering – why another post on this mundane topic?

Among the approaches prescribed over the years on how to achieve these requirements, most of them started losing relevance from the time synchronous calls from Client side are deprecated by modern browsers. Even Microsoft’s Xrm.WebApi are asynchronous and based on promises.

Well, Microsoft have got your back. You shall be delightfully surprised to know that Microsoft have indeed enabled the support for asynchronous save events.

I hope I got your attention now. Lets’ get started then.

Step 1 – Enable async save event support for your model driven app.

This step is fairly easy and you can easily do this using the new Model-driven app designer. To enable Asynchronous load and save events support, open your model-driven app in the designer and navigate to Features tab.

Enable the asynchronous events as required.

As you can see, you can enable asynchronous support for both onload and save event handler.

Step 2: Write code for Async save.

With that, now it is time to write code. Below is the code

// JavaScript source code

function onAccountFormLoad(executionContext) {
   var formContext = executionContext.getFormContext();
   formContext.data.entity.addOnSave(
      (() => {

         return async (eContext) => {
            eContext.getEventArgs().preventDefaultOnError();
            await validateSaveAsync(eContext);
         }
      }
      )());
}
var validateSaveAsync = (eContext) => {
   return new Promise((resolve, reject) => {
      var recordId = eContext.getFormContext().data.entity.getId().replace("{", "").replace("}", "");
      Xrm.WebApi.retrieveRecord("account", recordId, "?$select=industrycode")
         .then(function (result) {
            if (result.industrycode === undefined || result.industrycode === null) {
               resolve(true);
            }
            else {
               reject("error");
            }
         });
   });
}

Let’s go through the code. I am registering the onSave of the form dynamically on load of the form. onAccountFormLoad is the event registered on load of the form.

The OnSave event can now accept promises. And the moment you return a promise inside your method registered on save of the form, it becomes async.

Observe how I am using the Xrm.WebApi.retrieveRecord async method. The entire save operation will wait until the retrieve is completed. And then depending on whether I want to save the record or not, I fulfill the promise by invoking resolve or reject.

In the above code, I cancel the save operation if the account have the Industry field populated.

But wait. How I am even cancelling the save. We all know that we can cancel the save event using the good old executionContext.getEventArgs().prevenDefault().

Check for eContext.getEventArgs().preventDefaultOnError(); in the code above. preventDefaultOnError() is a method recently introduced by Microsoft to support the async save.

But how does this method work? Right at the start I invoked this function to tell the framework that if my promise calls reject() then prevent the save. And that’s where it does the trick.

Whenever I invoke reject on the promise, the framework consider it as an error and stops the save event. Below is the code in action.

The only ugly thing to the entire thing is the script error dialog. This come up when we call preventDefaultOnError() to stop the save event.

Canceling the save event is just one of the benefits of async save. One of the biggest benefit of async save is that the save event shall wait till your promise is fulfilled.

Let’s take the below example.

function onAccountFormLoad(executionContext) {
   var formContext = executionContext.getFormContext();
   formContext.data.entity.addOnSave(
      (() => {

         return async (eContext) => {
            eContext.getEventArgs().preventDefaultOnError();
            await createChildRecords(eContext);
         }
      }
      )());
}
var createChildRecords = (eContext) => {
   return new Promise((resolve, reject) => {
      var recordId = eContext.getFormContext().data.entity.getId().replace("{", "").replace("}", "");
      var data = {};
      data.subject = "sample task";
      data["regardingobjectid@odata.bind"] = "/accounts(" + recordId + ")";

      Xrm.WebApi.createRecord("task", data)
         .then(function (result) {
            console.log("Task created with id: " + result.id);
         }, function (error) {
            console.log(error.message);
         });
   });
}

Here I am creating a task and associating it with the account record in the save event handler. The save event will wait till the task is created. In-fact it will even execute your successCallback and error callback.

Wondering how to wait for multiple async events to complete before the save event executes? Well all you need to do is execute them in Promise.all

Well that’s all in this blog. Hope this helped!

Debajit Dutta
Business Solutions MVP

1 thought on “Async OnSave handlers in DataVerse/ Dynamics 365 forms to cancel save operation, stop save till certain async events are executed. Check this out!”

  1. Thanks a lot for the info, Debajit. It is really interesting!

    Just a question, how can you reverse the appsetting in the solution? I have tested to add it and now I want to remove it. The problem? It is getting stuck with missing dependency in the solution file.

    Do you now how to remove the appsetting properly to avoid the dependency? Thanks in advance.

Comments are closed.