When working with applications day in, day out, you sometimes overlook something that is sitting there, staring at you in the face. It may be an important feature or an inconsequential piece of functionality, but you never really take the time to fully understand either way just what it is and whether it can offer any distinct benefits or assistance. I realised a great example of this when recently deploying some new Plug-ins into Dynamics CRM/Dynamics 365 for Enterprise (CRM/D365E). When you are setting up a new Step for your Plug-in, you are given the option of specifying an Unsecure Configuration and Secure Configuration via a multi-line text box to the right of the window:

1

I was curious about just what these are and why it is not something that you ever really come across when you are first learning about Plug-in development with the application. I took a closer look at what these text boxes do and, as part of this week’s blog post, I wanted to share my findings and provide a demonstration of how they work in practice.

The Theoretical Bit: Unsecure/Secure Configuration Overview

Typically, when we want to get some juicy information relating to a piece of CRM/D365E functionality, we would turn to our good friends TechNet or MSDN. In this instance, however, there is no dedicated page that covers this topic in-depth. We must instead navigate to the Write a Plug-in Constructor page to find dedicated information about how these work:

The Microsoft Dynamics 365 platform supports an optional plug-in constructor that accepts either one or two string parameters. If you write a constructor like this, you can pass any strings of information to the plug-in at run time.

These “one or two” parameters are the multi-line text boxes indicated above. Information is exposed as string objects within you C# code and you enable this feature within your code by specifying the following, SDK adapted constructor within your Plug-in class:

public MyPlugin(string unsecureString, string secureString)
    {
        if (String.IsNullOrWhiteSpace(unsecureString) ||
            String.IsNullOrWhiteSpace(secureString))
            {
                throw new InvalidPluginExecutionException("Unsecure and secure strings are required for this plugin to execute.");
            }

            _unsecureString = unsecureString;
            _secureString = secureString;
    }

As with anything, there are a number of important caveats to bear in mind with this feature. These can be gleaned via additional online sources:

In terms of use cases, the above articles highlight some potential scenarios that they are best utilised within. Perhaps the best example is for an ISV solution that requires integration with external web services to retrieve data that is then consumed by CRM/D365E. Credentials for these web services can be stored securely when the Plug-in is deployed via the use of Secure configuration parameters. Other than that, if you are developing a Plug-in for internal use, that is unlikely to be deployed/managed across multiple environments, then it is probably not worthwhile to look at utilising configuration parameters when you can just as easily specify these within your code.

Practice Makes (for) Perfect (Understanding)!

The best way to see how something works is by getting hands-on and seeing how it works in action. Let’s assume you wish to deploy a plugin that executes whenever a record is opened/viewed by any user across the platform. The plugin should update the First Name (firstname) and Last Name (lastname) fields to match the value(s) in the Unsecure and Secure Configuration properties accordingly. The below plugin code will achieve these requirements:

using System;
using Microsoft.Xrm.Sdk;

namespace D365.BlogDemoAssets.Plugins
{
    public class PostContactRetrieve_PluginConfigurationTest : IPlugin
    {
        private readonly string _unsecureString;
        private readonly string _secureString;
        public PostContactRetrieve_PluginConfigurationTest(string unsecureString, string secureString)
        {
            if (String.IsNullOrWhiteSpace(unsecureString) ||
                String.IsNullOrWhiteSpace(secureString))
            {
                throw new InvalidPluginExecutionException("Unsecure and secure strings are required for this plugin to execute.");
            }

            _unsecureString = unsecureString;
            _secureString = secureString;
        }
        public void Execute(IServiceProvider serviceProvider)
        {
            // Obtain the execution context from the service provider.

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

            // Obtain the organization service reference.
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            // The InputParameters collection contains all the data passed in the message request.
            if (context.InputParameters.Contains("Target") &&
                context.InputParameters["Target"] is EntityReference)
            {
                Entity contact = new Entity("contact", ((EntityReference)context.InputParameters["Target"]).Id);
                
                contact["firstname"] = _unsecureString;
                contact["lastname"] = _secureString;
                service.Update(contact);

            }
        }
    }
}

When deploying the plugin using the Plugin Registration Tool, we specify the step to execute on the Retrieve message and to execute in the Pre-Operation Stage (otherwise the form will need to be refreshed to see the updated values!). We also need to specify our desired values for the First Name and Last Name fields in the appropriate Configuration fields. The Register New Step window should look similar to the below if configured correctly:

When we navigate into the Jim Glynn (sample) Contact record within CRM/D365E, we can see that the Plug-in has triggered successfully and updated the fields to match against the values specified on Step above:

We can also confirm that the appropriate error is thrown when one of the configuration properties is missing a value, by modifying our Plug-in step and attempting to reload our sample Contact record:

Can you spot what’s missing? 🙂

By clicking Download Log File, we can view the error message specified as part of the InvalidPluginExecutionException call. Below is a modified excerpt of the ErrorDetails XML that is generated:

  <InnerFault>
    <ActivityId>ed4a2021-9c87-4f06-a493-6d804676bf96</ActivityId>
    <ErrorCode>-2147220891</ErrorCode>
    <ErrorDetails xmlns:d3p1="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
    <Message>Unsecure and secure strings are required for this plugin to execute.</Message>
    <ExceptionSource i:nil="true" />
    <InnerFault i:nil="true" />
    <OriginalException i:nil="true" />
    <TraceText i:nil="true" />
  </InnerFault>

Conclusions or Wot I Think

It is impossible to become what I would like to term a “pub quiz champion” in CRM/D365E; what I mean by this is that I would defy anyone to rattle off every little detail and fact about the entire platform. As with any pub-quiz, those that do may more than likely end up cheating by having their phone out. With this metaphor in mind, I think Plug-in configuration properties would be an excellent topic for a quiz of this nature. As mentioned previously, it is not something that I was ever made aware of when starting to learn about Plug-in development and is not a feature touted regularly within the online community. Perhaps this is because of its very specific and limited application – although it is handy to have at our disposal, I think its usage is really only targeted towards those who are developing solutions that are deployed across multiple environments AND require the need to store configuration properties for external URL’s/web services in a compact and secure manner. Therefore, if you are currently having to use a custom entity within the application to store this type of information, it would make sense to reduce the footprint of your solution within the application itself and make the appropriate changes to use Secure configuration parameters instead. Using a bit of ingenuity (such as XML configuration parameters), you can achieve the same requirements without the need to customise the application unnecessarily.

Generally, when you are looking at adopting Dynamics CRM/Dynamics 365 for Enterprise (D365E) within your business, you can be reasonably satisfied that the majority of what is already configured within the system can be very quickly adapted to suit your business needs. Whether it’s the Lead to Opportunity sales process or the entire Case management module, the functionality at your disposal is suitable for many organisations across the globe. The great thing as well is that, should you wish to fine-tune things further, you have a broad range of options at your disposal that can help you achieve your objectives – sometimes in very specific and highly unique ways. I have previously looked at a good example of this on the blog – namely, how to override the systems built-in pricing engine in favour of your own – and, assuming you have a good understanding of C# and how to deploy plugins to the application, you can spin an important aspect of the systems functionality on its head to match how your business operates.

Having this ability is, undoubtedly, a real boon, but can present some odd behaviours. For example, you may start to notice that suddenly the Extended Amount field is no longer being populated with data after implementing your custom pricing engine. The example pictures below demonstrate a before and after example of adding a Product line item to the Quote entity, using the exact same sample Product:

Before…

…and after.

The odd thing about this is that, as soon as you click into the record, you will suddenly see a value appear in this field. Very strange!

It is difficult to pinpoint exactly what is causing the problem, but I can do a “stab in the dark”. CRM/D365E uses the CalculatePrice message to determine the points when either a) the default price engine or b) a custom one is triggered to perform all necessary calculations. Although there is no official documentation to back this up, I suspect that this message is only triggered when you Update or Retrieve an existing Product line item record (regardless of whether it is an Opportunity Product, Quote Product etc.). This is proven by the fact that, as soon as we click into our Product record, the Extended Amount field is suddenly populated – the platform has triggered the Retrieve message as a result of you opening the record and then, as a next step, forces the CalculatePrice message to also fire. The important thing to clarify with this point is that you must have a custom pricing implemented successfully within the application for this to work. Otherwise, don’t be too surprised if the Extended Amount value remains at 0.

Whilst the workaround for this is somewhat tolerable if you are working with a small subset of records and do not rely on the Extended Amount as part of any existing reporting within the application, this could really start to cause problems for your end users in the long term and give an impression that the application does not “work” as it should do. Fortunately, there is a solution that we can look at implementing that will hopefully lead to some happy fingers from not needing to click into records anymore 🙂 Be sure to have the CRM/D365E SDK handy before you begin the below!

  1. Open up the Plugin Registration Tool from within the SDK, and log into your CRM/D365E instance.
  2. Scroll down to your Assembly and Plugin that contains your custom pricing engine. If already configured correctly, it should have a step configured for the CalculatePrice message on any entity, as a Synchronous, Post-Operation step.
  3. Right click your plugin and click on Register New Step to open the window that lets you specify the required settings for your step. Populate the form as follows:
    • Message: Create
    • Primary Entity: Select one of the Product line item entities that your custom pricing engine uses. The list of accepted entities are invoicedetailopportunityproduct, salesorderdetail or quotedetail.
    • Event Pipeline Stage of Execution: Post-Operation
    • Execution Mode: Synchronous

All other settings can be left as default. Your window should look similar to the below if configured correctly for the quotedetail entity:

  1. Click on Register New Step to add the step to the application.
  2. Repeat steps 3-4 for any additional Product line item entities that are using with your custom pricing engine

Now, when you go back into CRM/D36E, the Extended Amount values will start to be populated automatically as soon as you add a new Product onto the Product line item subgrid.

Conclusions or Wot I Think

Whilst the ability to override an important piece of CRM’s/D365E’s functionality is welcome, you do need to bear in mind the additional overhead and responsibility this leaves your organisation in ensuring that your custom pricing engine is correct and that you have adequately tested the solution to properly identify actions which are out of the ordinary, such as the one discussed in this post. What is slightly frustrating about this quirk, in particular, is the lack of clear documentation regarding the CalculatePrice message from Microsoft. Granted, the message is only exposed for minimal interaction from an SDK point of view and is, for all intents and purposes, an internal application message that we shouldn’t really mess with or care about. Having said this, even just a brief summary of when the message is triggered on the platform would have made it instantly more understandable why any custom pricing calculation engine will fail to provide you with an instant amount within your Extended Amount field. In the end, however, I am pleased that there is a straightforward workaround that can be put into place to ensure that things work as expected; hopefully to the extent that it becomes virtually impossible to determine easily whether your organisation is using the default or a custom pricing engine in the first place.

For those who are well versed in rolling out solution updates within Dynamics CRM/365 for Enterprise (CRM/D365E), the process will always have a certain familiarity to it, with a few surprises rolled in now and again. Often, the update will proceed as anticipated; sometimes, you may encounter bizarre issues. I can remember a particularly strange incident I had last year where a solution import would get to about 90-95% completion…and then the green progress bar would suddenly start rolling back to nothing. The import progress window would then hang with no further guidance or error message. To try and determine the root cause, we had to interrogate the importjob entity within the system, which ended up showing the import job progress stuck at 0.15846281% : / In the end, we had to escalate the issue to Microsoft for further investigation, but rest assured that if you have not yet witnessed your own curious solution import experience, it’s definitely in the post 🙂

Thankfully, if you are new to the whole “rolling out solution update” thing, you can be assured that the process is relatively straightforward, and mostly without issue. If you have been handed a set of solution import instructions for the first time, though, you may be wondering why something similar to the following step is included:

Go into the Data Management -> Duplicate Detection Rules page and click Publish on all Duplicate Detection Rules that have a Status Reason of Unpublished

Unfortunately, after importing a solution update, CRM/D365E will automatically unpublish all of your Duplicate Detection Rules automatically. You are therefore required to explicitly publish them again, lest you start to encounter a sudden increase in duplicate records and database storage within your system. The reason why this happens is both understandable and frustrating in equal measure. As outlined in the following MSDN article on the subject:

A duplicate rule condition specifies the name of a base attribute and the name of a matching attribute. For example, specify an account as a base entity and a contact as a matching entity to compare last names and addresses

As part of the above, explicit matchcodes are created for every record that the Duplicate Detection Rule is targeting, based on the current metadata of your CRM/D365E entities and attributes. Because your solution update can potentially alter significant aspects of this metadata, the system automatically unpublishes all Duplicate Detection Rules as a precaution.

The above is perhaps trivial in nature, as the actual process of re-publishing all Duplicate Detection Rules is somewhat negligible in effort terms. Where difficulties can arise is if someone innocently overlooks this part of the process or if your system has many different Duplicate Detection Rules, in a mixture of Unpublished/Published state. You would have to specifically make a note of which rules were Published before beginning your solution import so that you can ensure that the correct rules are published after the fact. I would have thought that after so many versions of the product, that something would be added to address this – for example, perhaps a checkbox at the start of the Solution Import Wizard that lets you specify whether all currently published rules should be reactivated after the import completes successfully.

If you find that the above is an annoyance that you can do without no longer, like with many things on the platform, there is a solution that can be deployed in code. The SDK exposes the PublishDuplicateRuleRequest class, which does exactly what it says on the tin – meaning that you can write a plugin that applies this functionality accordingly. The tricky bit comes in determining which Message (i.e. action) on the platform that you wish to run this against. CRM/D365E does not expose a SolutionUpdate or SolutionImport message that we can piggy-back onto, so we have to look at the PublishAll message instead – the action that is triggered when you press Publish All Customizations in the system. This is because this is generally the action you will always need to take when importing an (unmanaged) solution. As a result, we can write a plugin class that is triggered on the Post-Operation event of this entity to automatically publish all Unpublished Duplicate Detection Rules in the system!

The snippet below is adapted from the sample code provided by Microsoft, but has been tweaked as follows:

  • A QueryExpression is used as opposed to QueryByAttribute, since we need to query on two separate attributes and their values – statecode and statuscode. You also cannot return an easily accessible count on all results returned with QueryByAttribute. We will see why is useful in a few moments.
  • The code explicitly checks for if there are any Unpublished rules first before attempting to proceed further – no point in running code unnecessarily!
  • Instead of activating each rule one-by-one using an Execute request, all of the requests are collected together as part of an ExecuteMultipleRequest, given that we now know the performance benefits that this can have.
  • Tracing has been implemented in liberal amounts, to provide remote debugging from within CRM/D365E.

Here’s the code – just copy into an empty class file on your plugin project, modify the namespace to reflect the name of your project and you will be good to go!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Crm.Sdk.Messages;

namespace MyPlugin.Plugins
{
    public class PostPublishAll_PublishDuplicateDetectionRules : IPlugin 
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            //Obtain the execution context from the service provider.

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

            //Get a reference to the Organization service.

            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = factory.CreateOrganizationService(context.UserId);

            //Extract the tracing service for use in debugging sandboxed plug-ins

            ITracingService tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            tracing.Trace("Tracing implemented successfully!");

            if (context.MessageName == "PublishAll")

            {
                PublishRules(service, tracing);
            }
        }

        private void PublishRules(IOrganizationService service, ITracingService tracing)

        {
            EntityCollection rules = GetDuplicateDetectionRules(service);

            tracing.Trace("Obtained " + rules.TotalRecordCount.ToString() + " duplicate detection rules.");

            if (rules.TotalRecordCount >= 1)

            {
                // Create an ExecuteMultipleRequest object.
                ExecuteMultipleRequest request = new ExecuteMultipleRequest()
                {
                    // Assign settings that define execution behavior: don't continue on error, don't return responses. 
                    Settings = new ExecuteMultipleSettings()
                    {
                        ContinueOnError = false,
                        ReturnResponses = false
                    },
                    // Create an empty organization request collection.
                    Requests = new OrganizationRequestCollection()
                };

                //Create a collection of PublishDuplicateRuleRequests, and execute them in one batch

                foreach(Entity entity in rules.Entities)

                {

                    PublishDuplicateRuleRequest publishReq = new PublishDuplicateRuleRequest { DuplicateRuleId = entity.Id };
                    request.Requests.Add(publishReq);
                    
                }

                service.Execute(request);

            }

            else

            {
                tracing.Trace("Plugin execution cancelled, as there are no duplicate detection rules to publish.");
                return;
            }
        }

        private EntityCollection GetDuplicateDetectionRules(IOrganizationService service)

        {
            QueryExpression qe = new QueryExpression("duplicaterule");

            qe.ColumnSet = new ColumnSet("duplicateruleid");

            ConditionExpression condition1 = new ConditionExpression();
            condition1.AttributeName = "statecode";
            condition1.Operator = ConditionOperator.Equal;
            condition1.Values.Add(0);

            ConditionExpression condition2 = new ConditionExpression();
            condition2.AttributeName = "statuscode";
            condition2.Operator = ConditionOperator.Equal;
            condition2.Values.Add(0);

            FilterExpression filter = new FilterExpression();
            filter.FilterOperator = LogicalOperator.And;
            filter.Conditions.Add(condition1);
            filter.Conditions.Add(condition2);

            qe.Criteria.AddFilter(filter);

            //Have to add this, otherwise the record count won't be returned correctly

            qe.PageInfo.ReturnTotalRecordCount = true;

            return service.RetrieveMultiple(qe);

        } 
    }
}

The only caveat with the above is that it is arguably only useful for if you are regularly importing Unmanaged, as opposed to Managed solutions, as the Publish All Customizations option is not displayed on the import wizard for unmanaged solutions. Nevertheless, by rolling out the above into your environment, you no longer need to scrabble around for the mental note you have to make when performing a solution update 🙂

It is often the case, as part of any application or database system, that certain record types will be well-suited towards duplication. Whilst this is generally a big no-no for individual customer records or invoice details, for example, there are other situations where the ability to duplicate and slightly modify an existing record becomes incredibly desirable. This is then expanded further to the point where end-users are given the ability to perform such duplication themselves.

A good example of this can be found within Dynamics CRM/Dynamics 365 for Enterprise (CRM/D365E). Email Templates are, in essence, a record type that is duplicated whenever a user selects the Template and creates a new Email record from within the application. Whilst there will always be details that need to be modified once the duplication is performed, having the ability to essentially “copy + paste” an existing record can generate the following benefits for a business:

  • Streamlining and adherence to business processes
  • Efficiency savings
  • Brand consistency

CRM/D365E does a pretty good job of making available a number of record types designed solely for this purpose, but a recent real-life example demonstrated a potential gap. A business I was working with was implementing the full sales process within the application – Lead to Opportunity to Quote etc. At the Quote stage, the businesses existing process would generally involve having a number of predefined “templates” for Quotes. This was due to the fact that the business would very regularly quote for the same kind of work, often with little or no variation. Out of the box with CRM/D365E, the sales team would have to create a new Quote record and add on every Quote Product line item each time a Quote was required – leading to little or no efficiency benefit of using the application.

To get around the issue, I was tasked with creating a means of setting up a number of “template” Quote records and then have the ability to quickly copy these template records, along with all of their associated Quote Product records, with some minor details changed in the process (for example, the Name value of the Quote). A workflow is immediately the best candidate for addressing the second requirement of this task but would require some additional development work to bring to fruition. I decided then to look, rather nervously, at creating a custom workflow assembly.

Why nervously? To be frank, although I have had plenty experience to date with writing plugins for CRM/D365E, I had not previously developed a custom workflow assembly. So I was a little concerned that the learning curve involved would be steep and take much longer than first anticipated. Fortunately, my fears were unfounded, and I was able to grasp the differences between a plugin and a custom workflow assembly very quickly:

  • Instead of inheriting from the IPlugin interface, your class instead needs to be set to the CodeActivity interface. As with plugins and, depending on Visual Studio version, you can then use CTRL + . to implement your Execute method.
  • Context (i.e. the information regarding the who, what and why of the execution; the User, the Entity and the action) is derived from the IWorkflowContext as opposed to the IPluginExecutionContext
  • Input/Output Parameters are specified within your Execute method and can be given a label, a target entity and then information regarding the data type that will be passed in/out. For example, to specify an Input Parameter for a Quote EntityReference, with the label Quote Record to Copy, you can use the following snippet:
[Input("Quote Record to Copy")]
[ReferenceTarget("quote")]
public InArgument<EntityReference> QuoteReference { get; set; }

The rest is as you would expect when writing a C# plugin. It is good to know that the jump across from plugins to custom workflow assemblies is not too large, so I would encourage anyone to try writing one if they haven’t done so already.

Back to the task at hand…

I implemented the appropriate logic within the custom workflow assembly to first create the Quote, using a Retrieve request to populate the quote variable with the Entity details and fields to copy over:

Entity newQuote = quote;
newQuote.Id = Guid.Empty;
newQuote.Attributes.Remove("quoteid");
newQuote.Attributes["name"] = "Copy of " + newQuote.GetAttributeValue<string>("quotenumber");
Guid newQuoteID = service.Create(newQuote);

The important thing to remember with this is that you must set the ID of the record to blank and then remove it from the newQuote – otherwise, your code will attempt to create the new record with the existing GUID of the copied record, resulting in an error.

Next, I performed a RetrieveMultiple request based off a QueryExpression to return all Quote Product records related to the existing records. Once I had my results in my qp EntityCollection, I then implemented my logic as follows:

foreach (Entity product in qp.Entities)

    {
        Entity newProduct = product;
        newProduct.Id = Guid.Empty;
        newProduct.Attributes.Remove("quotedetailid");
        newProduct.Attributes["quoteid"] = new EntityReference("quote", newQuoteID);
        service.Create(newProduct);
    }

After deploying to CRM and setting up the corresponding Workflow that referenced the assembly, I began testing. I noticed that the Workflow would occasionally fail on certain Quote records, with the following error message:

The plug-in execution failed because the operation has timed-out at the Sandbox Client.System.TimeoutException

The word Sandbox immediately made me think back to some of the key differences between CRM/D365E Online and On-Premise version, precisely the following detail pertaining to custom code deployed to Online versions of the application – it must always be deployed in Sandbox mode which, by default, only allows your code to process for 2 minutes maximum. If it exceeds this, the plugin/workflow will immediately fail and throw back the error message above. Upon closer investigation, the error was only being thrown for Quote records that had a lot of Quote Products assigned to them. I made the assumption that the reason why the workflow was taking longer than 2 minutes is because my code was performing a Create request into CRM for every Quote Product record and, as part of this, only proceeding to the next record once a success/failure response was returned from the application.

The challenge was therefore to find an alternative means of creating the Quote Product records without leading the Workflow to fail. After doing some research, I came across a useful MSDN article and code example that utilised the ExecuteMultipleRequest message:

You can use the ExecuteMultipleRequest message to support higher throughput bulk message passing scenarios in Microsoft Dynamics 365 (online & on-premises), particularly in the case of Microsoft Dynamics 365 (online) where Internet latency can be the largest limiting factor. ExecuteMultipleRequest accepts an input collection of message Requests, executes each of the message requests in the order they appear in the input collection, and optionally returns a collection of Responses containing each message’s response or the error that occurred.

Source: https://msdn.microsoft.com/en-us/library/jj863631.aspx

Throwing caution to the wind, I repurposed my code as follows, in this instance choosing not to return a response for each request:

// Create an ExecuteMultipleRequest object.

ExecuteMultipleRequest request = new ExecuteMultipleRequest()

{
    // Assign settings that define execution behavior: continue on error, return responses. 
    Settings = new ExecuteMultipleSettings()
    {
        ContinueOnError = false,
        ReturnResponses = false
    },
    // Create an empty organization request collection.
    Requests = new OrganizationRequestCollection()
};

    foreach (Entity product in qp.Entities)

    {
        Entity newProduct = product;
        newProduct.Id = Guid.Empty;
        newProduct.Attributes.Remove("quotedetailid");
        newProduct.Attributes["quoteid"] = new EntityReference("quote", newQuoteID);
        CreateRequest cr = new CreateRequest { Target = newProduct };
        request.Requests.Add(cr);

    }

service.Execute(request);

Thankfully, after re-testing, we no longer encountered the same errors on our particularly large Quote test records.

As a learning experience, the above has been very useful in showcasing how straightforward custom workflow assemblies are when coming from a primarily plugin development background. In addition, the above has also presented an alternative method for creating batch records within CRM/D365E, in a way that will not cause severe performance detriment. I was surprised, however, that there is no out of the box means of quickly copying existing records, thereby requiring an approach using code to resolve. Quotes are an excellent example of an Entity that could benefit from Template-isation in the near future, in order to expedite common order scenarios and help prevent carpel tunnel syndrome from CRM users the world over 🙂

Organisations that deploy Dynamics CRM/Dynamics 365 for Enterprise (CRM/D365E) can immediately take advantage of a number of inbuilt functionality, processes and data models that can be re-purposed with minimal effort. Whilst this approach can often lead to more streamlined deployment of your CRM/D365E solution, individuals customising the system should take care not to make the system fit around a business too much; rather, the opposite must be achieved where ever possible and careful analysis should be carried out in the outset to ensure that this balance is maintained. Sacrificing sensible business processes to accommodate for the quirks of a particular business system is a major pitfall that should be avoided as part of any major IT system deployment.

A good example of this can be found in the pricing calculation engine within CRM/D365E, which is utilised by the following entities within the system:

  • Opportunity
  • Opportunity Product
  • Quote
  • Quote Product
  • Order
  • Order Product
  • Invoice
  • Invoice Product

Rather than having to implement your own logic to generate prices for these entities, businesses can choose to utilise the pricing engine to automatically generate the net total for your Products, calculate appropriate Discounts and then figure out the final total at the end.

For those who are dissatisfied with how CRM performs this calculation, you will be pleased to hear that you have the option to override the default pricing engine and specify your own via a C# plugin. More information, and a very handy code example, can be found on our good friend MSDN:

The pricing engine in Microsoft Dynamics 365 supports a standard set of pricing and discounting methods, which might be limiting to your business depending on your specific requirements for applying taxation, discounts, and other pricing rules for your products. If you want to define custom pricing for your products in opportunities, quotes, orders and invoices, you can use the CalculatePrice message.

To use the custom pricing for your opportunities, quotes, orders, and invoices:

  1. Set the value of the Organization.OOBPriceCalculationEnabled attribute to 0 (false). You can also use the Sales tab in the system settings area in Microsoft Dynamics 365 or Microsoft Dynamics 365 for Outlook to disable system pricing. More information:  Configure product catalog information
  2. Create a plug-in that contains your custom pricing code for calculating the price for your opportunity, quote, order, or invoice.
  3. Register the plug-in on the CalculatePrice message.

Source: https://msdn.microsoft.com/en-us/library/dn817885.aspx

I think the most key thing as part of the above is not to overlook the simplest step – namely, modifying the setting within CRM/D365E that lets you specify your custom pricing engine in the first place. If this is not set, then you may spend many hours trying to figure out why your beautifully developed plugin is not working! It can be found very straightforwardly in Administration area of the application, on the System Settings page:

Whilst the code example provided by Microsoft gives you a good flavour of what you can potentially achieve with your own custom logic, I thought I would share two further examples that I recently was involved in developing, which may also prove useful when putting together your own custom pricing engine.

Calculating Custom Fields/Attributes

Arguably one of the biggest benefits of implementing your own custom pricing engine is being able to incorporate additional fields as part of the calculation. A recent real life example best demonstrates this. I was implementing a quoting solution for a business within Dynamics CRM 2015. The organisation was, fortunately, able to utilise much of the out of the box functionality within CRM as part of their existing processes. The only caveat was that they wanted the ability to add a Margin value at the Order level, in a similar vein to the Discount fields currently on the Quote entity – a Discount value and a Discount percentage value. The organisation wanted the option to do both, either or neither i.e. have the ability to specify a Margin value AND an additional percentage on top of that.

After configuring the appropriate fields within CRM to store both a currency value for the Margin and a decimal value for the Margin (%), we then proceeded to write some custom code that would achieve this aim. A snippet of this can be found below, which takes an existing total value of all Products on a Quote and then applies the correct calculation. It is worth explaining that the system returns NULL values if there is no data in the field when using the GetAttributeValue method (a fact I was already well aware of), which is why we have to specifically set the variables with a default value of 0 and perform the NULL check:

decimal margin = 0;
decimal marginPercent = 0;

if (quote.GetAttributeValue<Money>("new_mycustommarginamountfield") != null)
    {
        margin = quote.GetAttributeValue<Money>("new_mycustommarginamountfield").Value;
    }

if (quote.GetAttributeValue<Money>("new_mycustommarginpercentagefield"))
    {
        marginPercent = quote.GetAttributeValue<decimal>("new_mycustommarginpercentagefield");
    }

//Calculate margin amount based on the total amount

total = total + margin;
quote["totalamountlessfreight"] = new Money(total);
service.Update(quote);

//Calculate margin percentage based on the total amount

decimal marginPercentVal = marginPercent / 100 * total;
total = total + marginPercentVal;
quote["totalamountlessfreight"] = new Money(total);
service.Update(quote);

Calculating Sales Tax

CRM/D365E makes the assumption that tax will always be calculated on the Product Detail level. That’s why the Quote Product, Opportunity Product, Order Product and Invoice Product entities have a Tax field, demonstrated below on the Quote Product form:

There are a few problems with this, however:

  • You cannot set a default Tax for each Product in the system. What this means is that you have to drill down to every Product details entity and populate the Tax manually. Whilst you could look at a Business Rule, Workflow or some custom code to get around this issue, this seems like a rather complicated solution to something that you would expect to be easy to configure.
  • My experience indicates that most companies in the UK calculates tax on the gross amount of an order, and not at an individual Product level. Attempting to try and change a common practice to fit around a business system is a good example of what I spoke about in the introduction to this blog post.
  • Generally, most organisations will work with a flat rate of Tax for all Products (unless they are dealing with other countries). With this in mind, it seems a little crazy having to set this on an individual basis.

By using our own custom calculation logic, we can get around the above and implement a solution that best meets our need. For example, here is a code snippet that will take the total value of all Products on the Order entity and then calculate the VAT tax amount at 20%, saving the tax-only amount and the Net Total back to the system:

decimal vat = 0.20m;
decimal total = 0;
decimal tax - 0;

for (int i = 0; i < ec.Entities.Count; i++)
    {
        total = total + ((decimal)ec.Entities[i]["quantity"] * ((Money)ec.Entities[i]["priceperunit"]).Value);
        (ec.Entities[i])["extendedamount"] = new Money(((decimal)ec.Entities[i]["quantity"] * ((Money)ec.Entities[i]["priceperunit"]).Value));
        service.Update(ec.Entities[i]);
    }

//Calculate total from tax

tax = total * vat;
total = total + tax;
order["totaltax"] = new Money(tax);
order["totalamount"] = new Money(total);                
service.Update(quote);

It would be disingenuous of me not to point out that the above solution has its own faults – the biggest one being that your code would require manually updating if the tax rate ever changes in the future. You can perhaps get around this issue by instead storing the current tax rate within a CRM entity, that can be updated in line with any future changes. Your plugin could then query this entity/attribute each time the plugin is executed.

Conclusions or Wot I Think

Whilst the ability to override a key feature within CRM/D365E is incredibly welcome and a great example of how you can leverage the system without compromising on your existing business processes, it is arguable that this process is hardly straightforward. A passing knowledge of C# is mandatory to even begin to start implementing your own custom pricing engine, as well as good awareness of how CRM/D365E plugins work. This is all stuff that an administrator of the application may struggle to grasp, thereby requiring dedicated resource or knowledge within the business to implement the desired solution. It would be nice to perhaps see, as part of a future version of D365E, the ability to specify a custom pricing engine within the application itself – similar to how Business Rules were introduced and reduced the need for form-level JScript functions to achieve common tasks. Nevertheless, it has been good to discover that the CalculatePricing message is exposed within the application and that the application has the flexibility for end users to modify (and perhaps improve upon 🙂 ) one of its key features.