CRM 2013 – Actions Are My New Favorite Feature

So when originally previewing all the new stuff in CRM 2013 I kind of glossed over Actions.  A new type of workflow, meh, hopefully its not as worthless as Dialogs.  However, last week I had the opportunity to really dig into them and found out that they really have massive customization implications.  Including, duh, duh, dun: the ability to trigger plugin code directly with JavaScript.  “Who cares?” you ask.  We’ve always been able to do this, more or less, by creating a hidden attribute, and creating a plugin to run on Update and scoping it to that field.

Yeah, but with Actions, you can actually return a value (or values) back to the JavaScript!  What I did here last week was create a Clone function.  I haven’t really made it 100% configurable yet, in that you have to insert a button and call a JavaScript function, but its pretty neat in that it accepts any entity type.  I went with creating a custom Action because that way I can feed the Guid of the newly minted entity back to my JavaScript and them automatically open the new record using Xrm.Utility rather than making the user go hunt for the clone.

Architecture Overview:

– Button inserted on Entity record, set up to only display for an existing record, IE not on the Create form.

Clone_Meny

The button calls the cloneRecord() function, which will be detailed later.

– Action set up named “Clone” to accept a Target input parameter of type Entity (interestingly, from the documentation it looks like this should happen without needing to configure it, but as of 3/10/2014 this doesn’t happen) and outputting ClonedRecord output parameter of type EntityReference

Action

– Plugin written which is registered on the Clone event (the one we created above) of any entity.  The plugin is pretty simple, basically looping through all the attributes of the Target entity being fed to the Clone action and creating a new entity with the same values.  It then passes an EntityReference back to the OutputParameter.  Code below.

Details:

The cloneRecord() function creates an Execute request with an Entity key value pair being passed called “Target”.  I am using XrmServiceToolkit (which I really like, link) to perform the request.  I then take the response and parse out the guid and logicalname of the returned EntityReference.  I found that I had to use the .children() function in jQuery to properly parse out the value, I couldn’t get .find() to work, and I’m not sure why…  Anyway, I then use Xrm.Utility to open the newly created record.  The nice thing about this code is it is Entity agnostic, you don’t have to write a new function for each entity type.

//Call Clone message
function cloneRecord()
{
    try
    {
        var 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:Entity\">";
        requestMain += "              <a:Id>" + Xrm.Page.data.entity.getId() + "</a:Id>";
        requestMain += "              <a:LogicalName>"+ Xrm.Page.data.entity.getEntityName() +"</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><<insertprefixhere>>_Clone</a:RequestName>";
        requestMain += "      </request>";
        var cloneResp = XrmServiceToolkit.Soap.Execute(requestMain);

        var id = $(cloneResp).children(":first").children(":first").children(":first").children(":first").children("a\\:Results").children("a\\:KeyValuePairOfstringanyType").children("b\\:value").children("a\\:Id").text();
        var logicalName = $(cloneResp).children(":first").children(":first").children(":first").children(":first").children("a\\:Results").children("a\\:KeyValuePairOfstringanyType").children("b\\:value").children("a\\:LogicalName").text();

        alert("Opening Cloned Record");
        Xrm.Utility.openEntityForm(logicalName, id);

    }
    catch (e)
    {
        alert("cloneRecord() Error: " + e.message);
    }
}

The plugin is registered on the “Clone” event.  That is what really makes the Action concept powerful: you can basically create your own event handlers.  Note that the Message name will have the publisher prefix in front of it.  The plugin code is dirt simple compared to writing it all in Javascript.  Just loop through all the Attributes, handle the different types, and insert them into a newly created Entity and call Create.  In my code I don’t set the statuscode or statecode, and exclude any System.Guid types since they are the guid of the originating record.  This is still somewhat in process, so I’ll probably clean it up and make sure it handles all types at some point.


 public class CloneRecord : IPlugin
    {
        private IOrganizationService service;

        public void Execute(IServiceProvider serviceProvider)
        {
            try
            {
                ReportTypes ReportType = new ReportTypes();

                ReportStatusCodes statusCode = new ReportStatusCodes();
                service = XrmServiceProvider.GetService(serviceProvider);

                IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

                ITracingService tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
                if (tracing == null)
                {
                    throw new InvalidPluginExecutionException("Plugin tracing failed.");
                }

                if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
                {

                    Entity inputEntity = service.Retrieve(((Entity)context.InputParameters["Target"]).LogicalName, ((Entity)context.InputParameters["Target"]).Id, new ColumnSet(true));

                    Entity outputEntity = new Entity(inputEntity.LogicalName);

                    var keyString = string.Empty;
                    foreach (string key in inputEntity.Attributes.Keys)
                    {
                        //Don't want to set these
                        if (key != "statuscode" && key != "statecode")
                        {
                            keyString += " || key: " + key;

                            switch (inputEntity.Attributes[key].GetType().ToString())
                            {
                                case "Microsoft.Xrm.Sdk.EntityReference":
                                    outputEntity.Attributes[key] = inputEntity.GetAttributeValue<EntityReference>(key);
                                    keyString += " = " + inputEntity.Attributes[key].GetType().ToString() + " : " + inputEntity.GetAttributeValue<EntityReference>(key);
                                    break;
                                case "Microsoft.Xrm.Sdk.OptionSetValue":
                                    outputEntity.Attributes[key] = inputEntity.GetAttributeValue<OptionSetValue>(key);
                                    keyString += " = " + inputEntity.Attributes[key].GetType().ToString() + " : " + inputEntity.GetAttributeValue<OptionSetValue>(key);
                                    break;
                                case "Microsoft.Xrm.Sdk.Money":
                                    outputEntity.Attributes[key] = inputEntity.GetAttributeValue<Money>(key).Value;
                                    keyString += " = " + inputEntity.Attributes[key].GetType().ToString() + " : " + inputEntity.GetAttributeValue<Money>(key);
                                    break;
                                case "System.Guid":
                                    //Don't set this, only attribute of type System.Guid is the Id value
                                    break;
                                default:
                                    outputEntity.Attributes[key] = inputEntity.Attributes[key];
                                    keyString += " = " + inputEntity.Attributes[key].GetType().ToString() + " : " + inputEntity.Attributes[key];
                                    break;
                            }
                        }
                    }

                    Guid clonedEntGuid = service.Create(outputEntity);

                    EntityReference clonedEntRef = new EntityReference(outputEntity.LogicalName, clonedEntGuid);

                    context.OutputParameters["ClonedEntity"] = clonedEntRef;
                }
            }
            catch (Exception e)
            {
                throw;
            }
        }

    }

You can see the plugin passes an EntityReference back to the OutputValue, which gets returned to the Javascript.

Easy peasy, and way simpler and more robust than trying to do it in javascript, and more functional than just doing it server-side.  This made me a big fan of Actions!

Advertisements
Comments
8 Responses to “CRM 2013 – Actions Are My New Favorite Feature”
  1. Ben says:

    I had to change the code for the currency type to – ‘outputEntity.Attributes[key] = new Money(inputEntity.GetAttributeValue(key).Value);’ Before this, currency fields were not copying over.

    • yanbu0 says:

      Yep, this looks like a bug. Might not have tested Money fields before sticking the code out here. Thanks! I’ll have to take a look at this code again, might have just gone with outputEntity.Attributes[key] = inputEntity.GetAttributeValue<Money>(key);

  2. Ant says:

    Hi,

    Having a few problems trying to replicate this, the soap request is success get a 200 however, my plugin doesnt seem to fire.

    Plugin is registered on Clone event execution mode synchronous,.

    Any ideas?

    • yanbu0 says:

      Tough to say without being able to see whats going on. Are you sure the plugin doesn’t fire? Have you published, et cetera? What is getting returned to your javascript? I haven’t touched CRM in 6 months, so I’m a bit out of school at the moment.

  3. For CRM 2015 I had to change the following code:

    requestMain += ” “;

    to

    requestMain += ” “;

  4. For CRM 2015 I had to change to following code:

    requestMain += ” <b:value i:type=\”a:Entity\”>”;

    to

    requestMain += ” <b:value i:type=\”a:Entity\”>”;

Trackbacks
Check out what others are saying...


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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: