Friday, December 18, 2015

Calling Custom Action using Javascript

One of the most powerful features introduced in CRM 2013 is Actions. It strongly supports the concept of XRM platform. Actions are the Custom messages which can be created for any entity or globally to perform business operations like create/update/Assign etc. (more than what a Workflow can do). Looking first time we get a feeling that it is very similar to Workflow but hold on it’s very different from WF’s or Plugins.

Below are the key points which make it different:

  • Can be associated with single entity or globally.
  • Actions support Arguments – Input and Output Arguments.
  • Can be only called through web service calls – either JavaScript/Plugins/Web through API calls.
  • Supports Rollback and executes in real time.
  • Always runs under the context of the calling user.


Arguments

Arguments are the parameters or the variables which can be used while creating the action. There are two kinds of arguments

 Input Arguments:

These are the parameters which need to be passed during action call. These can be Optional/Mandatory. For Mandatory argument it is necessary to provide some value while calling this action. These input arguments can be used as variables while performing any operation in the action.

Output Arguments:

These are the parameters which can be returned when the action is called. These can be Optional/Mandatory.

Custom Action can be call through Javascript, Plug-Ins, Custom Workflow, WebServices etc

Today I am going to share a very simple example in which I'll Call Custom Action using Javascript. In my next post i'll also share details of how it is call from Plugins.
Let's take a look the example below in which I am calling Custom Action using Javascript by passing a Input parameter to custom action. Have a look-

Requirement -
I have to update or put any message on 'Case Description' field of case Entity using Custom Action.

Step 1 -   Create a Custom Action (Setting > Process > Action) and Create one Input Parameter which I'll pass from Javascript while calling Action :
I am creating this Custom Action for Case Entity as shown below-
                      
Create Custom Action















Step 2 - Put this Input Parameter in Case Description field to Update the same using Javascript as shown below :

Add Step 'Update Record'















Pass Input Parameter to Case Description field






















Step 3 -  Activated the Custom Action then our Custom Action Creation Step has completed.

Activate the Custom Action


























Step 3 - Add below Javascript code on Case 'OnSave' Event -


function CallActionFromJavaScript() 
{
var actionInputValue = "Custom Action has been executed successfully using Javascript";
var entityId = Xrm.Page.data.entity.getId();
var entityName = "incident";
var requestName = "new_CallCustomAction_Javascript";
try
{
ExecuteActionCreateProject(actionInputValue, entityId, entityName, requestName);
Xrm.Page.data.refresh();
}
catch(e)
{
alert(e.message);
}
}

function ExecuteActionCreateProject(actionInputValue, entityId, entityName, requestName) 
{
try{
    // Creating the request XML for calling the Action
var requestXML = "";
requestXML += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
requestXML += "<s:Body>";
requestXML += "<Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
requestXML += "<request xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">";
requestXML += "<a:Parameters xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
requestXML += "<a:KeyValuePairOfstringanyType>";
requestXML += "<b:key>Target</b:key>";
requestXML += "<b:value i:type=\"a:EntityReference\">";
requestXML += "<a:Id>" + entityId + "</a:Id>";
requestXML += "<a:LogicalName>" + entityName + "</a:LogicalName>";
requestXML += "<a:Name i:nil=\"true\" />";
requestXML += "</b:value>";
requestXML += "</a:KeyValuePairOfstringanyType>";
requestXML += "<a:KeyValuePairOfstringanyType>";
requestXML += "<b:key>ActionInputValue</b:key>";
requestXML += "<b:value i:type=\"c:string\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">" + actionInputValue + "</b:value>";
requestXML += "</a:KeyValuePairOfstringanyType>";
requestXML += "</a:Parameters>";
requestXML += "<a:RequestId i:nil=\"true\" />";
requestXML += "<a:RequestName>" + requestName + "</a:RequestName>";
requestXML += "</request>";
requestXML += "</Execute>";
requestXML += "</s:Body>";
requestXML += "</s:Envelope>";
var req = new XMLHttpRequest();
req.open("POST",GetClientUrl(), false)
req.setRequestHeader("Accept", "application/xml, text/xml, */*");
req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
req.send(requestXML); 
//Get the Resonse from the CRM Execute method
}
catch(e)
{
alert(e.message);
}
}

function GetClientUrl() 
{
if (typeof Xrm.Page.context == "object") 
{
clientUrl = Xrm.Page.context.getClientUrl();
}
var ServicePath = "/XRMServices/2011/Organization.svc/web";
return clientUrl + ServicePath;
}

Step 4 - Test Custom Action by Creating New Case or Click Save on any existing case:
You will find Case Description field Contain message that i have passed to custom action through javscript (Highlighted above with Yellow color)




























As a CRM developer, introduction to Custom Actions in this CRM 2013 release is really a great improvement from CRM’s perspective which will help us to minimize the coding part and also achieve some complex requirement easily.

Invoke custom actions from a workflow or dialog

Create a custom action

  1. Go to Settings > Processes(How do I get there?)
  2. On the Nav bar, choose New. Give the process a name and choose the Action category.
To request an approval for the discount, we’re using a custom action called Approval Process. We added an input parameter, SpecialNotes, and a Send email step to create a new message and send a request for the manager’s approval, as shown here.
Add a step - send email
To configure the email message, choose Set Properties. When the form opens, use the Form Assistant to add special notes and other information to the email, as highlighted on the screenshot. To add the special notes, place the cursor where you want them to appear in the message, and then, in the Form Assistant, under Look for, chooseArguments in the first drop-down list and choose SpecialNotes in the second drop-down list, and then choose OK.
Set up email
Before you can invoke the action from a workflow or dialog, you have to activate it. After you have activated the action, you can view its properties by choosing View properties.
Activate custom action - approval process

Invoke a custom action from a workflow

  1. Go to Settings > Processes(How do I get there?)
  2. On the Nav bar, choose New. Give the process a name and choose the Workflow category.
We created a workflow that invokes the Approval Process custom action whenever the manager’s approval for a discount over 20% for an opportunity is required.
Set action properties from workflow
You can set the action’s input properties by choosing Set Properties. We added a name of the account related to the opportunity in the special notes. In the Form Assistant, under Look for, choose Account in the first drop-down list, choose Account Name in the second drop-down list, and then choose OK. The Target property is required and it is populated by the system. The {Opportunity(Opportunity)} in the Target property is the same opportunity that the calling workflow is running on. Alternatively, you can choose a specific opportunity for the target property by using lookup.
Set input parameters for ApprovalProcess action

Invoke a custom action from a dialog

  1. Go to Settings > Processes(How do I get there?)
  2. On the Nav bar, choose New. Give the process a name and choose the Dialog category.
You can implement a scenario that’s similar to calling the Approval Process from a dialog as shown in the following illustration.
Enable custom action from dialog
Set up input parameters, as shown here.
Set properties for ApprovalProcess action

Trigger Plugin from Ribbon Button Using Custom Actions in Dynamics CRM 2013

The plugin framework is an extremely powerful feature of CRM where we, as system customizers, can make the CRM do whatever we want.  Before Microsoft Dynamics CRM 2013 came out, we were using out of the box messages like Create, Update, Delete, SetState, Merge, etc. to accomplish a majority of our work.  But what about the times when there is no create, update, delete or any other out of the box event?  Sweat no more!
Custom Actions
Microsoft Dynamics CRM 2013 introduced an exciting feature called Custom Actions. Now we can create our own platform messages and register plugins on them.  These messages can be invoked from JavaScript that in turn can trigger our plugins registered on them.  What’s more is that these custom actions can also have custom input and output parameters. In future blogs, we will show some specifics examples of how we make use of this feature. But for now, here is an example of how we can create a custom action for Order entity that we can invoke from a ribbon button.
–          From the customizations go to Processes
–          Click New, provide value for the Process name, from Category select Action, and from Entity select Order and click OK
You can also specify that this custom action can accept an optional input parameter.  In this case, we are calling it “Data.”  The plugin handling this custom action can read that parameter using PluginContext.InputParameters[“Data”].  If your plugin needs a lot of data passed to it, you can also use XML as value for Data parameter.
–          Save and Activate this process. Activation is a very important step for any process to be used.
Now assuming that we have added a ribbon button to the Order form, we add the below JavaScript to the web resource.  The script creates a simple SOAP envelope to invoke the message, just like you would do for any other out of the box message like Create, Update, Delete, etc.
The first parameter of ExecuteAction is the custom action to be called when the ribbon button is clicked.  The third parameter of ExecuteAction is where we specify the input parameters for our custom action.
function ExecuteAction(requestName, refreshPage, stringParameter)
{
// Creating the request XML for calling the Action
var requestXML = ""
if (stringParameter == null)
{
requestXML += "<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">";
requestXML += "  <s:Body>";
requestXML += "
<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">";
requestXML += "
<request xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">";
requestXML += "        <a:Parameters
xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic">";
requestXML += "          <a:KeyValuePairOfstringanyType>";
requestXML += "            <b:key>Target</b:key>";
requestXML += "            <b:value i:type="a:EntityReference">";
requestXML += "              <a:Id>" + Xrm.Page.data.entity.getId() + "</a:Id>";
requestXML += "              <a:LogicalName>" + Xrm.Page.data.entity.getEntityName()
+ "</a:LogicalName>";
requestXML += "              <a:Name i:nil="true" />";
requestXML += "            </b:value>";
requestXML += "          </a:KeyValuePairOfstringanyType>";
requestXML += "        </a:Parameters>";
requestXML += "        <a:RequestId i:nil="true" />";
requestXML += "        <a:RequestName>" + requestName + "</a:RequestName>";
requestXML += "      </request>";
requestXML += "    </Execute>";
requestXML += "  </s:Body>";
requestXML += "</s:Envelope>";
}
else
{
requestXML += "<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">";
requestXML += "  <s:Body>";
requestXML += "    <Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">";
requestXML += "      <request xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">";
requestXML += "        <a:Parameters xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic">";
requestXML += "          <a:KeyValuePairOfstringanyType>";
requestXML += "            <b:key>Target</b:key>";
requestXML += "            <b:value i:type="a:EntityReference">";
requestXML += "              <a:Id>" + Xrm.Page.data.entity.getId() + "</a:Id>";
requestXML += "              <a:LogicalName>" + Xrm.Page.data.entity.getEntityName() + "</a:LogicalName>";
requestXML += "              <a:Name i:nil="true" />";
requestXML += "            </b:value>";
requestXML += "          </a:KeyValuePairOfstringanyType>";
requestXML += "          <a:KeyValuePairOfstringanyType>";
requestXML += "            <b:key>Data</b:key>";
requestXML += "            <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema">" + stringParameter + "</b:value>";
requestXML += "          </a:KeyValuePairOfstringanyType>";
requestXML += "        </a:Parameters>";
requestXML += "        <a:RequestId i:nil="true" />";
requestXML += "        <a:RequestName>" + requestName + "</a:RequestName>";
requestXML += "      </request>";
requestXML += "    </Execute>";
requestXML += "  </s:Body>";
requestXML += "</s:Envelope>";
}
var req = new XMLHttpRequest();
req.open("POST", GetServiceUrl(), false)
req.setRequestHeader("Accept", "application/xml, text/xml, */*");
req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
req.send(requestXML);
//refresh the page if the request was successful.
if (req.status == 200)
{
if (refreshPage)
{
RefreshForm()
}
}
else
{
Xrm.Utility.alertDialog(req.statusText + "n" + req.responseXML.getElementsByTagName("faultstring")[0].textContent);
}
}
function RefreshForm()
{
Xrm.Utility.openEntityForm(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId());
}
ExecuteAction("new_GenerateOrderReport", true, "");
Now to handle this invocation, we can register a plugin that will be triggered either in pre or post operation just like any other plugin registered on out of the box messages.

Introduction to Actions in CRM 2013

Until CRM 4 there were Workflows that could be used for asynchronous processing of business logic, generally used for setting up automated actions and defining sales process in Dynamics CRM.
Since CRM 2011, the workflows became a category under Processes and there was another category Dialogs introduced. Dialogs provided for execution of Dialog scripts to set up process flows for the Salesforce. These could be used to guide the sales people through the sales process using a question/answer format. The Dialog included execution of automated steps like the workflows.
With CRM 2013, the Processes have been extended to now include Business Process Flow and Actions in addition to the categories from the previous versions. In our earlier blog we have discussed the concept of Business Process Flow (hyperlink to earlier blog). This article will concentrate on Actions.
What are Actions?
Actions are messages that can defined for an entity. Existing examples messages include Create, Update, Set State, Assign etc. With Actions, the CRM platform has enabled the creation of custom actions for entities to extend the XRM platform. 
Where to use Actions?
An example of a custom action could be “Approve”. Often times we have to design an approval process for Dynamics CRM. Once the item is approved, certain actions need to be performed. In previous versions it would either be implemented as a Workflow that is manually executed from the Workflows Dialog or it involved added of a custom attribute that when checked would imply the item is approved and then the workflow would capture the update of this field to process any automated actions defined.
How to setup an Action?
Let us take an example of an approval process. When an item is “Approved”, we want to send email notifying the concerned parties of the approval.
Here is an action created for “Approve”

It accepts an input parameter for the ApprovedBy User. You can specify parameters of any of the following data types


These parameters could be defined as input/output parameter.

In the workflow actions, it sends a mail from the ApprovedBy user to the Owner of the Order notifying them of the order being approved. You can also call custom workflow assemblies here.

Note the schema name generated “new_Approve”. This is the name of the new message that will not be available for the Order entity.

These actions are available for further implementation of custom business logic through the use of plugins. In some scenarios, say we need to validate certain conditions are met before the item can be approved. In this case we can register a plugin in the Pre-Stage of the Approve Message. The plugin could validate the conditions and throw an exception to abort the processing of the Approve Message. 

How to use Actions?

Since Actions are implemented as custom messages/requests, they can be implemented similar to any other OOB messages like Create or Update. 

Using C# code, you can invoke the Approve request using the following code

                    //get current user details
                     WhoAmIRequest userReq = new WhoAmIRequest();
                     WhoAmIResponse resp = (WhoAmIResponse) proxy.Execute(userReq);
         OrganizationRequest req = new OrganizationRequest("new_Approve");
                    req["ApprovedBy"] = new EntityReference("systemuser", resp.UserId);
                    req["Target"] = new EntityReference("salesorder",orderid);
                     //execute the request
                    OrganizationResponse response = proxy.Execute(req);


Through jscript it can be invoked as follows.

if (typeof (SDK) == "undefined")
{ SDK = { __namespace: true }; }
SDK.Action = {
    _getClientUrlfunction () {     
       var ServicePath = "/XRMServices/2011/Organization.svc/web";
        var clientUrl = "";
        if (typeof GetGlobalContext == "function") {
            var context = GetGlobalContext();
            clientUrl = context.getClientUrl();
        }
        else {
            if (typeof Xrm.Page.context == "object") {
                clientUrl = Xrm.Page.context.getClientUrl();
            }
            else
            { throw new Error("Unable to access the server URL"); }
        }
        if (clientUrl.match(/\/$/)) {
            clientUrl = clientUrl.substring(0, clientUrl.length - 1);
        }
        return clientUrl + ServicePath;
    },
    ApproveRequest : function (salesOrderId, approvedById) {
        var requestMain = ""
        requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
        requestMain += "  <s:Body>";
        requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
        requestMain += "      <request xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">";
        requestMain += "        <a:Parameters xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
        requestMain += "          <a:KeyValuePairOfstringanyType>";
        requestMain += "            <b:key>Target</b:key>";
        requestMain += "            <b:value i:type=\"a:EntityReference\">";
        requestMain += "              <a:Id>" + salesOrderId + "</a:Id>";
        requestMain += "              <a:LogicalName>salesorder</a:LogicalName>";
        requestMain += "              <a:Name i:nil=\"true\" />";
        requestMain += "            </b:value>";
        requestMain += "          </a:KeyValuePairOfstringanyType>";
        requestMain += "          <a:KeyValuePairOfstringanyType>";
        requestMain += "            <b:key>ApprovedBy</b:key>";
        requestMain += "            <b:value i:type=\"a:EntityReference\">";
        requestMain += "              <a:Id>" + approvedById + "</a:Id>";
        requestMain += "              <a:LogicalName>systemuser</a:LogicalName>";
        requestMain += "              <a:Name i:nil=\"true\" />";
        requestMain += "            </b:value>";
        requestMain += "          </a:KeyValuePairOfstringanyType>";
        requestMain += "        </a:Parameters>";
        requestMain += "        <a:RequestId i:nil=\"true\" />";
        requestMain += "        <a:RequestName>new_Approve</a:RequestName>";
        requestMain += "      </request>";
        requestMain += "    </Execute>";
        requestMain += "  </s:Body>";
        requestMain += "</s:Envelope>";
        var req = new XMLHttpRequest();
        req.open("POST", SDK.Action._getClientUrl(), false)
        req.setRequestHeader("Accept""application/xml, text/xml, */*");
        req.setRequestHeader("Content-Type""text/xml; charset=utf-8");
        req.send(requestMain);
        //work with the response here
        //var strResponse = req.responseXML.xml;       
    },
    __namespace: true
};

Here in this above script RequestName indicates the Unique Name of Action and Parameters contains collection of key value pairs that can be passed to Action. In above script We can use below function using below syntax.
SDK.Action.ApproveRequest(salesOrderId, approvedById);
You can register plugin on the Approve message. The input parameters of the Approve message will receive the input parameters as defined in the Approve Action.
The plugin registration tool will start showing this message for the Order entity to register plugin against.
Once the plugin is registered, in the code you can access the input parameters just like you do for other messages as shown below
To get access to the image of the Order record
EntityReference entityRef = localContext.PluginExecutionContext.InputParameters["Target"] as EntityReference;
To read the ApprovedBy input parameter
EntityReference approvedBy = localContext.PluginExecutionContext.InputParameters["ApprovedBy"] as EntityReference;
In the pre-stage, you can throw an InvalidPluginExecution error to abort the Approve operation.
Conclusion:
Actions is a powerful tool in the hands of the developer to truly extend the CRM platform for XRM implementations.

How to Deploy your API as a web app or API app

  Before you can call your custom API from a logic app workflow, deploy your API as a web app or API app to Azure App Service. To make your ...