Over the past few years, Microsoft Dynamics CRM has become a popular enterprise tool for solving complex tasks. To implement various scenarios developers often use plug-ins, dialogs and workflows, but sometimes these out-of-the-box tools can’t provide the functionality needed.

In this article we’ll consider using dialogs for multiple entities and implement a sample task where we’ll have customers with invoices. The task itself is to increase the invoice discount by some value for all selected customers.

We can’t use a workflow approach in a standard dialog as it will be hard to retrieve the references to customers’ invoices, and it has to be done for each individual customer. We could use custom workflow activities, but in this case it would be hard to enter some procedure parameters (in our case it will be the amount by which the invoice discount must be increased). Using only JavaScript will cause lots of problems connected to transactions and integrity of the procedure. So, we will need to use some combination of different Dynamics CRM coding approaches, namely, we’ll pair JavaScript with plug-ins functionality. Microsoft provides an opportunity for developers to extend and modify Dynamics CRM using .NET assemblies with code that can be triggered by some events (such as entitycreation, modification or deletion). These assemblies are called plug-ins.

We are going to solve this problem by using JavaScript to gather primary data (selected customers), dialogs to collect additional data (amount to increase/decrease the discount by), and plug-ins to process all the data. We will create .NET assembly and register it in CRM to enhance standard logic.

First of all, let’s look at the concept:

  1. For ribbon editing (creating button for action) we’ll be using Ribbon Workbench (http://www.develop1.net/public/page/Ribbon-Workbench-for-Dynamics-CRM-2011.aspx).
  2. To collect the selected customers IDs and pass them to the dialog and plug-in we will use custom entities, which will store IDs of customers and one parent entity for them to store data entered in the dialog. Entities with IDs will be connected to parent entity.
  3. Dialog will have these actions:
    • prompt user to enter required values;
    • update parent entity with data entered by the user via dialog (update will trigger plug-in execution).
  4. Plug-in will read the data from parent entities and associated entities with customer IDs and process customers’ invoices. Usage of C# language in plug-in will give us opportunity to create a complex logic and integrate it into CRM. CRM 2011 SDK provides .NET the assemblies needed to work with the data stored in any entity.

Preparing CRM entities

So, first things first: let’s create new entities for processing and add existing required entities to solution. To do this, go to solution components, add a new entity and fill the form as shown in В Picture 1 (other entity options are not important right now). For testing purposes we will display this entity in the Settings Area.

Picture 1 – Child Entity creation form

Add these fields to our new entity:

  • Contact
    • Name: ar_contact
    • Type: Lookup
    • Target Record Type: Contact
  • Parent Entity
    • Name: ar_parententity
    • Type: Lookup
    • Target Record Type: Parent Entity

Now do the same for the parent entity:

Picture 2 – Parent Entity creation form

Fields:

  • Amount:
    • Name: ar_amount
    • Type: Currency

Note that entity names can be different: prefix can differentiate in different solutions.

Also, to create button for customers, add Contact entity to solution.

Creating dialog for user interaction

The next step is to create a dialog for Parent entity for user interaction with our process. Go to solution components and add new Process. Fill form with data provided in Picture 3 and press OK.

Picture 3 – Dialog creation form

You should see the Dialog designer form:

Picture 4 – Dialog designer form

Click the Select this row and click Add Step field and add a new Page step:

Picture 5 – Adding new page

Select the row in the page step, add the new step Prompt and Response and click Set Properties:

Picture 6a – Creating Prompt and Response step

Picture 6b – Creating Prompt and Response step

Fill in the form as shown below, then save and close it:

Picture 7 – Prompt and Response designer form

In the Process creation window select Page step and add the new step — Update Record, then select Set Properties:

Picture 8 – Adding Update Record

Now we need to fill in the Amount field. To do this, place the cursor in the Amount field by clicking on the textbox and then select Prompt for amount in Look for dropdown and Response text in dropdown below, click the Add button and then OK in Form Assistant pane. You should see the form shown in the picture below with the filled Amount field. Press the Save and Close button.

Picture 9 – Update Record designer form

OK, dialog steps are prepared now. To publish the dialog press the Activate button at the top of the page and confirm activation in Confirmation dialog. The dialog is ready to use.

Creating JavaScript code for starting dialog

The next step will be to write a JavaScript library to create a Parent entity, list of Child entities, attach them to the Parent entity, and then open the dialog that we have created.

The first thing we need is to get dialog ID to start it over CRM API. This is how you can obtain it: go to Parent Entity view, select one record (create some with any data, if none exists), click Start Dialog button in ribbon panel and select the Process Customers dialog.

Picture 10 – Starting dialog manually

You should see the dialog running. Now copy URL of the dialog page, which may look something like this:

https://article0808.crm4.dynamics.com/cs/dialog/rundialog.aspx?DialogId=%7b38D394F3-0C1B-46C5-A682-53F373501CBD%7d&EntityName=ar_parententity&ObjectId=%7bFEA9E699-C700-E311-8A01-3C4A92DBCCDF%7d.

The underlined text is a unique dialog ID that we need. В Note that %7b characters are not parts of the ID, which in our case is 38D394F3-0C1B-46C5-A682-53F373501CBD.

We will use CRM REST Endpoint with JavaScript. More info on using REST Endpoint can be found here http://msdn.microsoft.com/en-us/library/gg334279.aspx (documentation) and here http://msdn.microsoft.com/en-us/library/gg309549.aspx (samples). SDK library (SDK.JQuery.js) can be found at http://msdn.microsoft.com/en-us/library/gg309549.aspx#BKMK_SDKJQueryJS or in the SDK installation folder, at SampleCode\JS\RESTEndpoint\JQueryRESTDataOperations.

Here’s the JavaScript code (process.js):

var dialogId = "38D394F3-0C1B-46C5-A682-53F373501CBD";
var parentEntityLogicalName = "ar_parententity";

function Process(items) {
    if (items.length != 0) {
        var parentEntity = {};
        var parentEntityId;
        var parentEntityName;

        // Creating parent entity with some name
        parentEntity.ar_name = (new Date()).toString();
        SDK.JQuery.createRecord(
            parentEntity,
            parentEntityLogicalName,
            // call back from parent entity creating
            function (entity) {
                // saving parent entity id and name
                parentEntityId = entity.ar_parententityId;
                parentEntityName = entity.ar_name;

                // creating child entities
                for (var i = 0; i < items.length; i++) {
                    var childEntity = {};
                    childEntity.ar_name = (new Date()).toString();
                    // setting ParentEntity field to assotiate child entity with parent
                    childEntity.ar_ParentEntity = { Id: parentEntityId, LogicalName: parentEntityLogicalName, Name: parentEntityName };
                    // storing customer in child entity using the reference to customer
                    childEntity.ar_Customer = { Id: items[i].toString(), LogicalName: parentEntityLogicalName };
                    // creating record
                    SDK.JQuery.createRecord(
                        childEntity,
                        "ar_childentity",
                        function(chEntity) {},
                        errorHandler
                    );
                }

                // we didn't wait until all children will be created so starting dialog immediately after parent entity is created
                var serverUri = Mscrm.CrmUri.create('/cs/dialog/rundialog.aspx');
                window.showModalDialog(serverUri + '?DialogId=' + "%7b" + dialogId + "%7d" + '&EntityName=' + parentEntityLogicalName + '&ObjectId=' + parentEntityId,
                                null, 'dialogWidth: 615px; dialogHeight: 480px; resizable: yes; status: yes, scrollbars:yes;');
            },
            errorHandler
        );
    } else {
        alert("No records selected");
    }
}

function errorHandler(error) {
    alert(error.message);
}

Publishing JavaScript

To make the provided JavaScript code sample work some additional libraries have to be uploaded to CRM and published:

  1. jQuery.js
  2. json2.js
  3. restSDK.js

All of them can be found in CRM 2011 SDK samples code or in the solution provided. Don’t forget to upload our library and name it process.js.

After libraries upload the Web Resources tab should look as in the picture 11 below:

Picture 11 – Web Resources view

Creating a button

Before creating a button ensure that Contact entity is included into your solution and install the Ribbon Workbench solution on you CRM system. Open application by clicking the Ribbon Workbench button in the Customizations menu. Select yours solution and click OK. Workbench will load the solution and show all the entities that belong to it with their ribbon panels.

In the Entities panel select contact from the Entities column, then in Solution elements right-click Commands and click Add New: a new command will be created.

Picture 12 – Adding new command to Contact entity

Now we need to add our JavaScript function to this command. Right click on command and select Edit Actions. In a dialog click Add, select Javascript Function Action and click OK. In the right panel type in the FunctionName, which in our case В will be Process. Select library, Process library in our case. Open the Parameters dialog by clicking the button located near Parameters textbox.

Picture 13 – Adding JavaScript function to command

Click Add, in a dialog that opens select Crm Parameter. Name it items (naming is optional). In the Value dropdown select SelectedControlSelectedItemIds and click OK to close the Parameters dialog.

Picture 14 – Adding parameters to function

Now let’s load other libraries into the Contact view. There are different ways to do that (see http://www.develop1.net/public/post/Asynchronous-loading-of-JavaScript-Web-Resources-after-U12POLARIS.aspx and http://social.microsoft.com/Forums/en-US/01772aa3-006c-4539-a368-bf78e1ae3561/crm-2011-javascript-load-scripts-onload-in-a-view-or-dashboard). В We’ll just add В the scripts to actions on button click. В In the Actions dialog add new Javascript Function Action with FunctionName $ and jQuery library without any parameters, then do the same for JSON library, and then once again for Rest SDK with FunctionName isNaN. Use Up and Down Arrows to move jQuery, JSON and SDK libraries higher in the list and load them before the execution of our script. After that your Actions window should look like this (Picture 15):

Picture 15 – Adding additional libraries

Click OK to save added actions.

Next step is to create a button: drag and drop the button item from Toolbox to any place you want (we placed it in the Process group after Start Dialog button), make sure you have selected the contact entity and Homepage from the top right dropdown menu. Fill the LabelText field in button properties and select the command that we have created earlier.

Picture 16 – Creating a new button

After that click Publish Solution and wait until the process completes. Now you can go to Customers->Contacts page, select contacts and click the new Process Invoices button (refresh the page if it doesn’t show up). This will create one Parent Entity with some Child Entities and start the Dialog.

Creating and publishing the plug-in

Now we can create a plug-in which will fire on Parent Entity Update and process the customers’ invoices.

Create a new plug-in project in Visual Studio. Generate the data model of your CRM organization using CrmSvcUtil.exe (see http://msdn.microsoft.com/en-us/library/gg327844.aspx). Use the following command line with your credentials and CRM URL:

CrmSvcUtil /url:https://article0808.crm4.dynamics.com/XRMServices/2011/Organization.svc /out:Xrm.cs /username:”administrator@article0808.onmicrosoft.com” /password:”adminadmin” /namespace:Xrm /serviceContextName:PluginServiceContext

Add the generated file to the project.

Create a new ProcessCustomersPlugin class and inherit it from Plugin class.

Here’s code from this class:

using System;
using System.Linq;
using System.Text;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;

using Xrm;

namespace ProcessMultipleRecords
{
    /// <summary>
    /// Process customers' invoices plugin
    /// </summary>
    public class ProcessCustomersPlugin : Plugin
    {
        /// <summary>
        /// Post operation pipeline stage.
        /// </summary>
        private const int PostOperationPipelineStage = 40;

        /// <summary>
        /// Create event name.
        /// </summary>
        private const string UpdateEventName = "Update";

        /// <summary>
        /// Assign customers to billing run trigger CRM name.
        /// </summary>
        private const string ParentEntityLogicalName = "ar_parententity";

        /// <summary>
        /// Initializes a new instance of the <see cref="ProcessCustomersPlugin"/> class.
        /// </summary>
        public ProcessCustomersPlugin()
            : base(typeof(ProcessCustomersPlugin))
        {
            this.RegisteredEvents.Add(
                new Tuple<int, string, string, Action<LocalPluginContext>>(
                    PostOperationPipelineStage, UpdateEventName, ParentEntityLogicalName, this.ExecuteProcessCustomers));
        }

        /// <summary>
        /// Executes customers processing.
        /// </summary>
        /// <param name="localPluginContext">
        /// The local plugin context.
        /// </param>
        private void ExecuteProcessCustomers(LocalPluginContext localPluginContext)
        {
            if (localPluginContext == null)
            {
                throw new ArgumentNullException("localPluginContext");
            }

            // Getting trigger entity
            ar_parententity trigger;
            if (localPluginContext.PluginExecutionContext.InputParameters != null
                && localPluginContext.PluginExecutionContext.InputParameters.Contains("Target")
                && localPluginContext.PluginExecutionContext.InputParameters["Target"] is Entity)
            {
                var entity = (Entity)localPluginContext.PluginExecutionContext.InputParameters["Target"];
                if (!entity.LogicalName.Equals(ar_parententity.EntityLogicalName))
                {
                    throw new ArgumentException("Plugin execution failure. Can not retrieve required reading.");
                }

                trigger = entity.ToEntity<ar_parententity>();
            }
            else
            {
                throw new ArgumentException("Plugin execution failure. Can not retrieve required reading.");
            }

            this.ProcessCustomers(trigger, localPluginContext);
        }

        /// <summary>
        /// Processes invoces connected to customers.
        /// </summary>
        /// <param name="trigger">
        /// The trigger with amount value.
        /// </param>
        /// <param name="localPluginContext">
        /// The local plugin context.
        /// </param>
        private void ProcessCustomers(ar_parententity trigger, LocalPluginContext localPluginContext)
        {
            using (var context = new PluginServiceContext(localPluginContext.OrganizationService))
            {
                // Getting Child Entities
                var triggerChildrens = context.ar_childentitySet.Where(x => x.ar_ParentEntity.Id == trigger.Id);
                foreach (var triggerChildren in triggerChildrens)
                {
                    // Getting customers from Child Entitites
                    var customer = context.ContactSet.FirstOrDefault(x => x.Id == triggerChildren.ar_Customer.Id);
                    if (customer != null)
                    {
                        // Getting invoices
                        var invoices =
                            context.InvoiceSet.Where(
                                x => x.CustomerId.Id == customer.Id && x.StateCode == InvoiceState.Active);
                        foreach (var invoice in invoices)
                        {
                            // Updating invoices
                            invoice.DiscountAmount =
                                new Money(
                                    (invoice.DiscountAmount ?? new Money(0)).Value
                                    + (trigger.ar_Amount ?? new Money(0)).Value);
                            context.UpdateObject(invoice);
                        }
                    }
                }

                // Saving all changes
                var results = context.SaveChanges(SaveChangesOptions.ContinueOnError);
                if (results.HasError)
                {
                    var sb = new StringBuilder();
                    foreach (var result in results)
                    {
                        if (result.Error != null)
                        {
                            sb.AppendFormat("{0}\n", result.Error.Message);
                        }
                    }

                    throw new InvalidOperationException(sb.ToString());
                }
            }
        }
    }
}

Sign the assembly with a key file and build it. Now we can register plug-in using Plug-in Registration Tool. Go to your Developer Tool Kit installation folder, then to sdk\bin and run pluginregistration executable. Click the Create New Connection button, fill in the form and click Connect. For more info about discovery URL see http://msdn.microsoft.com/en-us/library/gg328127.aspx.

Picture 17 - Connecting to CRM instance

Picture 17 – Connecting to CRM instance

After that enter the password and click Connect in Organization URLs panel:

Picture 18 – Connecting to CRM service

The system will load all present assemblies and messages. Click Register->Register New Assembly:

Picture 19 – Registering new assembly

In the Register New Plugin dialog specify the location of the assembly, select the required plugin and clickВ  Register Selected Plugins.

Picture 20 – New assembly form

Then in Organization tab expand the uploaded assembly, right click on the plug-in and select Register New Step in context menu.

Picture 21 – Registering new step

Fill in the dialog fields as shown in the picture below and click Register New Step

Picture 22 – New step creation form

Demonstration

OK, now we are ready to run processing over some customers, create invoices for them and add some items to these invoices. Invoices can be of two types: with applied discount, or without the discount, and our code will handle the empty values. Go to Sales->Customers->Contacts section, select some customers including the ones that we have created invoices for and hit the Process Invoices button.

Picture 23 – Selecting customers and processing invoices

In the dialog enter the value for the amount to add to customer’s discount,В click Next and then Finish. Now we can check our invoices. As you can see, the discount amount increased by the values we entered in dialogs.

Picture 24 – Processing Dialog

We’ve prepared a sample project with all the code and libraries mentioned in this article. Download the project files here: ProcessMultipleRecords.zip

You can found many other cases when we can apply this approach of implementing complex logic in Microsoft Dynamics CRM. Thanks for reading!

Insert math as
Block
Inline
Additional settings
Formula color
Text color
#333333
Type math using LaTeX
Preview
\({}\)
Nothing to preview
Insert