What is the best way to add a post-save event that will always be called either success/failure? From the documentation, I see we actually can use addOnPostSave but when I try it, it didn’t work as expected (later we will go through this scenario). So, as an alternative for now we can make use of formContext.data.save where we can pass successCallback and errorCallback which make it the cleanest possible way to do it right now (shout out to Andrew Butenko where I get the most code from 🤣)!
The scenario that I’m talking about is we want to call an event if there is an error on the screen:

For this demonstration, I create a plugin on the Contact record with the below logic:
using System;
using System.Threading;
using Microsoft.Xrm.Sdk;
namespace DemoPlugin
{
public class Plugin1Demo : PluginBase
{
public Plugin1Demo() : base(nameof(Plugin1Demo))
{
}
protected override void ExecuteCdsPlugin(ILocalPluginContext localPluginContext)
{
var target = (Entity)localPluginContext.PluginExecutionContext.InputParameters["Target"];
if (target.Attributes.Contains("lastname") &&
target.GetAttributeValue<string>("lastname") != "error") return;
Thread.Sleep(TimeSpan.FromSeconds(5));
throw new InvalidPluginExecutionException("just random error...");
}
}
}
Once it is done, I register this event on the Update Contact:

For the logic that will be called when an error happened, I create a custom API tmy_demoapi that will return a string message (it is not really important to show the code for it).
addOnPostSave
The first test is using addOnPostSave using the below Javascript code:
var BlogJs = BlogJs || {};
var load;
(function() {
this.formOnLoad = function(executionContext){
addOnPostSave(executionContext);
};
// When OnSave is triggered, this function will be called (succed/failed)
var callDemoApi = async function(context) {
console.log("call here..");
var formContext = context.getFormContext();
var req = new demoApi("testing", formContext.data.entity.getId());
var result = await Xrm.WebApi.execute(req);
var message = await result.json();
formContext.ui.setFormNotification(message.output, "INFO", "demo");
window.setTimeout(function () {
formContext.ui.clearFormNotification("demo");
}, 10000);
};
// Register the OnPostSave event handler
var addOnPostSave = function (context) {
if (load) return;
var formContext = context.getFormContext();
formContext.data.entity.addOnPostSave(callDemoApi);
load = true;
};
}).apply(BlogJs);
class demoApi {
constructor(action, id) {
this.input = JSON.stringify({ action, id });
}
getMetadata() {
return {
boundParameter: null,
parameterTypes: {
"input": {
"typeName": "Edm.String",
structuralProperty: 1
}
},
operationType: 0,
operationName: "tmy_demoapi"
};
}
}
When I test the error scenario, from the debugger it is not triggering the callDemoApi method:

formContext.data.save
From the formContext.data.save, we have options to pass successCallback and errorCallback. I tested this using below code:
var BlogJs = BlogJs || {};
var load;
(function () {
this.formOnLoad = function (context) {
var formContext = context.getFormContext();
formContext.data.entity.addOnSave(formOnSave);
};
// call api only when error!
var callDemoApi = async function (context) {
console.log("callDemoApi");
var formContext = context.getFormContext();
var req = new demoApi("testing", formContext.data.entity.getId());
var result = await Xrm.WebApi.execute(req);
var message = await result.json();
formContext.ui.setFormNotification(message.output, "INFO", "demo");
window.setTimeout(function () {
formContext.ui.clearFormNotification("demo");
}, 10000);
};
// this code taken from https://butenko.pro/2018/11/15/cancelling-save-based-on-the-result-of-async-operation/
var SaveMode = {
Save: 1,
SaveAndClose: 2,
SaveAndNew: 59,
Autosave: 70,
};
var isSave = false;
var formOnSave = function (context) {
if (context.getEventArgs().isDefaultPrevented()) return;
if (isSave) return;
//getting save mode from event
var saveMode = context.getEventArgs().getSaveMode();
//if savemode is not one of listed - just quit the execution and let the record to be saved
if (
saveMode !== SaveMode.Save &&
saveMode !== SaveMode.SaveAndClose &&
saveMode !== SaveMode.SaveAndNew &&
saveMode !== SaveMode.Autosave
) {
return;
}
isSave = true;
//preventing standard save event because we will replace it with our own
context.getEventArgs().preventDefault();
var formContext = context.getFormContext();
// delay 1 second to avoid error `Please wait while saving is completed`..
setTimeout(() => {
formContext.data.save({ saveMode: saveMode }).then(
function () {
isSave = false;
},
function () {
isSave = false;
callDemoApi(context);
}
);
}, 1000);
};
}.apply(BlogJs));
class demoApi {
constructor(action, id) {
this.input = JSON.stringify({ action, id });
}
getMetadata() {
return {
boundParameter: null,
parameterTypes: {
input: {
typeName: "Edm.String",
structuralProperty: 1,
},
},
operationType: 0,
operationName: "tmy_demoapi",
};
}
}
Below is the result:

Happy CRM-ing!
One thought on “Model-Driven-Apps: Add on post-save event”