Model-Driven-Apps: Add on post-save event

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:

The scenario

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:

Plugin Step for Contact Entity

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:

addOnPost is not called if there is an error from the plugin

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:

Add event on Post success 😎😎

Happy CRM-ing!

Advertisement

One thought on “Model-Driven-Apps: Add on post-save event

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.