Calling an Action using SAOP Request in CRM 2013

Execute Action using JavaScript web resource in CRM 2013/15

To invoke an action from JavaScript, Microsoft has created a tool to generate SOAP based request and response libraries. Here is the link – Sdk.Soap.js Action Message Generator.

But here we’ll call Action using simple SOAP request.

Here I have created an Action to create a copy of an existing Account record. This Action is triggered from a custom ribbon button – Copy Account. The business logic has been implemented using custom workflow.

The purpose of this Action is to create a copy of an existing Account record using a custom button on Account Form. When button is clicked, a SOAP request is generated to invoke Action. Inside the Action a custom activity has been added as a step. Account Id is provided as an input argument to Custom Activity. After creating a copy, custom assembly returns the Id of the newly created record, new Account record is then opened in a popup window.

Steps:

Step 1- Create an Action

Step 2- Create Custom Workflow Activity to execute within Action

Step 3- Add step to call custom activity and Add Assign Value step to map action and custom activity’s output params:

Step 4- Call Action from JavaScript using SOAP request and Parse SAOP Response

Step 1- Create an Action

To create an Action, go to settings -> Processes -> New

Action

Step 2- Create Custom Workflow Activity to execute within Action as step


public class CopyAccount : CodeActivity
    {

        [Input("Account Id")]
        [ReferenceTarget("account")]
        public InArgument<EntityReference> AccountId { get; set; }

        [Output("New Account Id")]
        public OutArgument<string> NewAccountId { get; set; }


        protected override void Execute(CodeActivityContext context)
        {
            var newAccountId = Guid.Empty;

            try
            {
                IWorkflowContext workflowContext = context.GetExtension<IWorkflowContext>();
                IOrganizationServiceFactory serviceFactory = context.GetExtension<IOrganizationServiceFactory>();
                IOrganizationService service = serviceFactory.CreateOrganizationService(workflowContext.UserId);

                EntityReference accountId = AccountId.Get<EntityReference>(context);

                var account = service.Retrieve(accountId.LogicalName, accountId.Id, new ColumnSet(true));

                //Create new Account
                Entity newAccount = account;

                //Remove existing primary key - accountid
                account.Attributes.Remove("accountid");

                //Assign new PK to new Account
                newAccount.Id = new Guid();

                //Remove owner - logged in user will be the owner of the new record
                account.Attributes.Remove("ownerid");

                //Create new account
                newAccountId = service.Create(newAccount);

                if (newAccountId != Guid.Empty)
                {
                    // retun to Action
                    NewAccountId.Set(context, newAccountId.ToString());
                }

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
    }

Step 3- Add step to call custom activity and Add Assign Value step to map action and custom activity’s output params: Where business logic is implemented

action-step-assembly

Map Action Input Properties:

input-params

Map Action’s Output parameters with the value returned by custom activity:

output param

Step 4- Call Action from JavaScript using SOAP request and Parse SAOP Response

function copyAccount() {
    var accountId = Xrm.Page.data != null ? Xrm.Page.data.entity.getId() : null;
    var accountLogicalName = "account";
    var actionName = "new_CopyAccount";

    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>" + accountId + "</a:Id>";
    requestMain += "              <a:LogicalName>" + accountLogicalName + "</a:LogicalName>";
    requestMain += "              <a:Name i:nil=\"true\" />";
    requestMain += "            </b:value>";
    requestMain += "          </a:KeyValuePairOfstringanyType>";
    requestMain += "          <a:KeyValuePairOfstringanyType>";
    requestMain += "            <b:key>AccountId</b:key>";
    requestMain += "            <b:value i:type=\"a:EntityReference\">";
    requestMain += "              <a:Id>" + accountId + "</a:Id>";
    requestMain += "              <a:LogicalName>" + accountLogicalName + "</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>" + actionName + "</a:RequestName>";
    requestMain += "      </request>";
    requestMain += "    </Execute>";
    requestMain += "  </s:Body>";
    requestMain += "</s:Envelope>";

    var url = getClientUrl() + "XRMServices/2011/Organization.svc/web";
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("POST", url, false);
    xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
    xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
    xmlhttp.onreadystatechange = function () { parseActionSOAPResponse(xmlhttp, successResponse, errorResponse); };
    xmlhttp.send(requestMain);
}

function parseActionSOAPResponse(req, successCallback, errorCallback) {
    if (req.readyState == 4) {
        req.onreadystatechange = null;
        if (req.status == 200){
            successCallback(req.responseXML);
        }
        else {
            errorCallback(req.responseXML);
        }
    }
}

function successResponse(responseXml) {
    var accountId = null;
    if (responseXml != null) {
        try {
            var x = responseXml.getElementsByTagName("a:KeyValuePairOfstringanyType");
            if (x.length == 0) {
                x = responseXml.getElementsByTagName("KeyValuePairOfstringanyType");
            }
            for (var i = 0; i < x.length; i++) {
                if (x[i].childNodes[0].textContent == "NewAccountId")/*Output Param set in Custom Activity - CopyAccount.cs*/ { 
                    accountId = x[i].childNodes[1].textContent;
                    //Open newly created account
                    if (accountId != null && accountId != "undefined" && accountId != "00000000-0000-0000-0000-000000000000" && accountId != "")
                        openNewAccount(accountId, "account");
                }
            }
            
        }
        catch (e) {
            alert("Erro Occured");
        }
    }
}

function openNewAccount(recordId, entityLogicalName) {
    var features = "titlebar=yes, resizable=yes";
    window.open("/main.aspx?etn=" + entityLogicalName + "&pagetype=entityrecord&id=" + encodeURIComponent(recordId), "_blank", features, false);
}

//In case of error
function errorResponse(responseXml) {
    if (responseXml != null &&
        responseXml.firstChild != null && responseXml.firstChild.firstChild != null) {
        try {
            var bodyNode = responseXml.firstChild.firstChild;
            
            for (var i = 0; i < bodyNode.childNodes.length; i++) {
                var node = bodyNode.childNodes[i];
                if ("s:Fault" == node.nodeName) {
                    for (var j = 0; j < node.childNodes.length; j++) {
                        var faultStringNode = node.childNodes[j];
                        if ("faultstring" == faultStringNode.nodeName) {
                            alert("Error: " + faultStringNode.textContent);
                            break;
                        }
                    }
                    break;
                }
            }
            hideProcessingBar();
        } catch (e) {
            hideProcessingBar();
            alert("Error while Copy account");
        };
    } else {
        hideProcessingBar();
        alert("Error while Copy account");
    }
}

// Return CRM path
function getClientUrl() {
    var strUrl = Xrm.Page.context.getClientUrl();
    if (strUrl.substr(strUrl.length - 1) != "/") {
        strUrl += "/";
    }
    return strUrl;
}

Privileges and Access Rights in CRM 2013

Privileges

Privileges define the action that a user can perform on a CRM System and at what level.

Privileges are added by system. Privileges can be modified but can’t be deleted. Privileges are assigned to users or teams using Security Roles. For security roles, privileges are assigned with different access levels i.e. User Level (basic), Business Unit (local), Parent (deep), and Organizational level.

There are 5 levels of access:

None – No Privileges

User (basic) – Privileges given at user level on records owned by/Shared with user or team.

Business Unit (local) – Privileges given on all the records in the Business Unit.

Parent: Child Business Unit (deep) – Privileges given on all the records in the user’s Business Unit and the Business Units subordinate to that Business Unit.

Organization (global): Privileges on all the records in the Organization.

Privileges

How to Check Privileges Programmatically using C# and JavaScript:

In the below code it’s checked if the given user has user(basic) level privileges on Account entity.

Using C#:

bool userHasCreatePrivilege = false;

QueryExpression query = new QueryExpression("privilege");

query.ColumnSet = new ColumnSet(new[] { "name", "canbebasic" }); //canbebasi == user level

LinkEntity lnkEnty1 = new LinkEntity("privilege", "roleprivileges", "privilegeid", "privilegeid", JoinOperator.Inner);

LinkEntity lnkEnty2 = new LinkEntity("roleprivileges", "role", "roleid", "roleid", JoinOperator.Inner);

LinkEntity lnkEnty3 = new LinkEntity("role", "systemuserroles", "roleid", "roleid", JoinOperator.Inner);

LinkEntity lnkEnty4 = new LinkEntity("systemuserroles", "systemuser", "systemuserid", "systemuserid", JoinOperator.Inner);

ConditionExpression condition = new ConditionExpression("systemuserid", ConditionOperator.Equal, userId);

lnkEnty4.LinkCriteria.AddCondition(condition);

FilterExpression filter = new FilterExpression(LogicalOperator.Or);

filter.AddCondition("name", ConditionOperator.Equal, "prvCreateAccount”);
query.Criteria = filter;

lnkEnty3.LinkEntities.Add(lnkEnty4);

lnkEnty2.LinkEntities.Add(lnkEnty3);

lnkEnty1.LinkEntities.Add(lnkEnty2);

query.LinkEntities.Add(lnkEnty1);

EntityCollection entities= service.RetrieveMultiple(query);

if (entities.Entities.Count > 0)

{

Entity entity = entities.Entities[0];

if (entity.Contains("canbebasic") && entity["canbebasic"] != null && (bool)entity["canbebasic"] == true)

userHasCreatePrivilege = true;

}

Using JavaScript:

Privileges can also be checked using SOAP request and Fetch Xml:


function checkUserPrivileges() {

    var fetchXml = generateFetchXml("prvCreatAccount", Xrm.Page.context.getUserId());   

    var request = generateFetchBasedSOAPRequest(fetchXml);
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("POST", getClientUrl() + "XRMServices/2011/Organization.svc/web", true);
    xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
    xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");

    xmlhttp.onreadystatechange = function () {
        if (this.readyState == 4) {
            xmlhttp.onreadystatechange = null; 
            parseSoapResponse(this);
        }
    };

    xmlhttp.send(request);
}

function generateFetchXml(privilageName, userId) {

    var fetchXml = "<fetch mapping='logical' version='1.0'>"
                    + "<entity name='privilege'>"
                    + "<attribute name='accessright' />"
                    + "<attribute name='name' />"
                    + "<filter>"
                    + "<condition attribute='name' operator='eq' value='" + privilageName + "' />"
                    + "</filter>"
                    + "<link-entity name='roleprivileges' from='privilegeid' to='privilegeid'>"
                    + "<link-entity name='role' from='parentrootroleid' to='roleid'>"
                    + "<link-entity name='systemuserroles' from='roleid' to='roleid'>"
                    + "<filter>"
                    + "<condition attribute='systemuserid' operator='eq' value='" + userId + "' />"
                    + "</filter>"
                    + "</link-entity>"
                    + "</link-entity>"
                    + "</link-entity>"
                    + "</entity>"
                    + "</fetch>";

    return fetchXml;
}

function generateFetchBasedSOAPRequest(fetchXml) {

    var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
    request += "<s:Body>";
    request += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' +
        '<request i:type="b:RetrieveMultipleRequest" ' +
        ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' +
        ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' +
        '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' +
        '<b:KeyValuePairOfstringanyType>' +
        '<c:key>Query</c:key>' +
        '<c:value i:type="b:FetchExpression">' +
        '<b:Query>';
    request += CrmEncodeDecode.CrmXmlEncode(fetchXml);
    request += '</b:Query>' +
'</c:value>' +
'</b:KeyValuePairOfstringanyType>' +
'</b:Parameters>' +
'<b:RequestId i:nil="true"/>' +
'<b:RequestName>RetrieveMultiple</b:RequestName>' +
'</request>' +
'</Execute>';

    request += '</s:Body></s:Envelope>';

    return request;
}



Access Rights

Access rights is basically record based security that applies to individual records. It’s provided by using Access Rights. Access rights are bit different than privileges. Privileges can be assigned through security roles while access rights can be given using Access Teams or Sharing. A user/team can you access rights on record level even without having privileges. Access rights apply after privileges have taken effect.

Access Rights Provided through Access Team (Access Team Template):

AccessTeam

Access Rights provided using Sharing:

Share

Create Access – In the above tables you’ll notice that there is no Create Access listed. The reason is Create is handled as a Privilege instead of an Access Right.

Retrieve Access Rights Programmatically using C# and JavaScript:

C# Code to retrieve Access Rights:


bool userHasAccessRight = false;

var principalAccessRequest = new RetrievePrincipalAccessRequest{
Principal = new EntityReference("systemuser", new Guid(".....")),

Target = new EntityReference("account", new Guid("....."))

};
var principalAccessResponse = (RetrievePrincipalAccessResponse)service.Execute(principalAccessRequest);

if ((principalAccessResponse.AccessRights &amp; AccessRights.ReadAccess) != AccessRights.None)

{

userHasAccessRight = true;

}

Using JavaScript:

Using RetrievePrincialAccess Request, it can be accessed Access Rights to a user on a particular record.


function checkUserAccessRights() {

var request= generateSoapRetrievePrincipalAccessRequest();

var xmlhttp = new XMLHttpRequest();

xmlhttp.open("POST", getClientUrl() + "XRMServices/2011/Organization.svc/web", true);

xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");

xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");

xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");


xmlhttp.onreadystatechange = function () {

if (this.readyState == 4) {

xmlhttp.onreadystatechange = null; 

parseResponse(this);

}

};

xmlhttp.send(request);

}

function generateSoapRetrievePrincipalAccessRequest() {


var userId = Xrm.Page.context.getUserId();

var accountId= Xrm.Page.data.entity.getId();

var accountLogicalName = "account";

var requestName = "RetrievePrincipalAccess";

var systemUserLogicalName = "systemuser";



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 i:type=\"b:RetrievePrincipalAccessRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";

requestMain += "       <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";

requestMain += "         <a:KeyValuePairOfstringanyType>";

requestMain += "           <c:key>Target</c:key>";

requestMain += "           <c:value i:type=\"a:EntityReference\">";

requestMain += "             <a:Id>" + accountId+ "</a:Id>";

requestMain += "             <a:LogicalName>" + accountLogicalName+ "</a:LogicalName>";

requestMain += "             <a:Name i:nil=\"true\" />";

requestMain += "           </c:value>";

requestMain += "         </a:KeyValuePairOfstringanyType>";

requestMain += "         <a:KeyValuePairOfstringanyType>";

requestMain += "           <c:key>Principal</c:key>";

requestMain += "           <c:value i:type=\"a:EntityReference\">";

requestMain += "             <a:Id>" + userId + "</a:Id>";

requestMain += "             <a:LogicalName>" + systemUserLogicalName + "</a:LogicalName>";

requestMain += "             <a:Name i:nil=\"true\" />";

requestMain += "           </c:value>";

requestMain += "         </a:KeyValuePairOfstringanyType>";

requestMain += "       </a:Parameters>";

requestMain += "        <a:RequestId i:nil=\"true\" />";

requestMain += "       <a:RequestName>" + requestName + "</a:RequestName>";

requestMain += "     </request>";

requestMain += "   </Execute>";

requestMain += " </s:Body>";

requestMain += "</s:Envelope>";



return requestMain;

}

Add/Remove User to/from Access Team in CRM 2013 using Plugin

Access Team – Overview

Access Team is a new feature added to CRM 2013. It allows to grant specific permissions to a team/user that don’t own a record. Which makes it easy to share a record with a list of users with certain rights.

If you want to Add/remove users to/from a record’s access team programmatically in plugin, please have a look that how it works.

Fields

  • Record – Record for which you want to add users to its Access Team
  • User id – User who you want to Add to Access Team
  • Access Team Template id – The template which you want to use, as there can be more than one templates created with different rights for the same Entity.
  • Name Space – Crm.Sdk.Messages

 

Add User to Access Team


AddUserToRecordTeamRequest adduser = new AddUserToRecordTeamRequest()
{
      Record = new EntityReference(entity.LogicalName, entity.Id),
      SystemUserId = userGuid,
     TeamTemplateId = teamTemplateGuid
};
service.Execute(adduser);</p>

Possible errors:

  • Insufficient Permissions to the User being added

This is possible due to the role of the user or user’s right, i.e. in the Access Team Template if it’s checked the delete rights while the User role has insufficient permissions to delete, this error may come.

 

Remove User from Access Team


RemoveUserFromRecordTeamRequest removeUser = new RemoveUserFromRecordTeamRequest()
{
     Record = new EntityReference(entity.LogicalName, entity.Id),
     SystemUserId = userGuid,
     TeamTemplateId = teamTemplateGuid
};
service.Execute(removeUser);