This is an accompanying blog post to my YouTube video Dynamics 365 Customer Engagement Deep Dive: Creating a Basic Plug-in, the second in a series aiming to provideĀ tutorials on how to accomplish developer focused tasks within Dynamics 365 Customer Engagement. You can watch the video in full below:

Below you will find links to access some of the resources discussed as part of the video and to further reading topics:

PowerPoint Presentation (click here to download)

Full Code Sample

using System;
using System.Globalization;

using Microsoft.Xrm.Sdk;

namespace D365.SamplePlugin
{
    public class PreContactCreate_FormatNameValues : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            //Obtain the execution context from the service provider.

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

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

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

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

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

            {
                Entity contact = (Entity)context.InputParameters["Target"];

                string firstName = contact.GetAttributeValue<string>("firstname");
                string lastName = contact.GetAttributeValue<string>("lastname");

                TextInfo culture = new CultureInfo("en-GB", false).TextInfo;

                if (firstName != null)
                {

                    tracingService.Trace("First Name Before Value = " + firstName);
                    contact["firstname"] = culture.ToTitleCase(firstName.ToLower());
                    tracingService.Trace("First Name After Value = " + contact.GetAttributeValue<string>("firstname"));

                }

                else

                {
                    tracingService.Trace("No value was provided for First Name field, skipping...");
                }

                if (lastName != null)

                {
                    tracingService.Trace("Last Name Before Value = " + lastName);
                    contact["lastname"] = culture.ToTitleCase(lastName.ToLower());
                    tracingService.Trace("Last Name After Value = " + contact.GetAttributeValue<string>("lastname"));
                }

                else

                {
                    tracingService.Trace("No value was provided for Last Name field, skipping...");
                }

                tracingService.Trace("PreContactCreate_FormatNameValues plugin execution complete.");

            }
        }
    }
}

Download/Resource Links

Visual Studio 2017 Community Edition

Setup a free 30 day trial of Dynamics 365 Customer Engagement

C# Guide (Microsoft Docs)

Source Code Management Solutions

Further Reading

MSDN – Plug-in development

MSDN – Supported messages and entities for plug-ins

MSDN – Sample: Create a basic plug-in

MSDN – Debug a plug-in

I’ve written a number of blog posts around plug-ins previously, so here’s the obligatory plug section šŸ™‚ :

Interested in learning more about JScript Form function development in Dynamics 365 Customer Engagement? Then check out my previous post for my video and notes on the subject. I hope you find these videos useful and do let me know if you have any comments or suggestions for future video content.

This is an accompanying blog post to my YouTube videoĀ Dynamics 365 Customer Engagement Deep Dive: Creating a Basic Jscript Form Function, the first in a series that aims to provide tutorials on how to accomplish developer focused tasks within Dynamics 365 Customer Engagement. You can watch the video in full below:

Below you will find links to access some of the resources discussed as part of the video and to further reading topics.

PowerPoint Presentation (click here to download)

Full Code Sample

function changeAddressLabels() {

    //Get the control for the composite address field and then set the label to the correct, Anglicised form. Each line requires the current control name for 'getControl' and then the updated label name for 'setLabel'

    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_line1").setLabel("Address 1");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_line2").setLabel("Address 2");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_line3").setLabel("Address 3");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_city").setLabel("Town");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_stateorprovince").setLabel("County");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_postalcode").setLabel("Postal Code");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_country").setLabel("Country");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line1"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line1").setLabel("Address 1");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line2"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line2").setLabel("Address 2");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line3"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line3").setLabel("Address 3");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_city"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_city").setLabel("Town");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_stateorprovince"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_stateorprovince").setLabel("County");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_postalcode"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_postalcode").setLabel("Postal Code");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_country"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_country").setLabel("Country");
}

Download/Resource Links

Visual Studio 2017 Community Edition

Setup a free 30 day trial of Dynamics 365 Customer Engagement

W3 Schools JavaScript Tutorials

Source Code Management Solutions

Further Reading

MSDN – Use JavaScript with Microsoft Dynamics 365

MSDN – Use the Xrm.Page. object model

MSDN – Xrm.Page.ui control object

MSDN – Overview of Web Resources

Debugging custom JavaScript code in CRM using browser developer tools (steps are for Dynamics CRM 2016, but still apply for Dynamics 365 Customer Engagement)

Have any thoughts or comments on the video? I would love to hear from you! I’m also keen to hear any ideas for future video content as well. Let me know by leaving a comment below or in the video above.

With 2018 now very firmly upon us, it’s time again to see what’s new in the world of Dynamics 365 certification. Nothing much has changed this time around, but there are a few noteworthy updates to be aware if you are keen to keep your certifications as up to date as possible. Here’s my round-up of what’s new – let me know in the comments if you think I have missed anything out.

MCSE Business Applications 2018

The introduction of a dedicated Microsoft Certified Solutions Architect (MCSA) and Microsoft Certified Business Applications (MCSE) certification track for Dynamics 365 was a positive step in highlighting the importance of Dynamics 365 alongside other core Microsoft products. Under the new system, those wishing to maintain a “good standing” MCSE will need to re-certify each year with a brand new exam to keep their certification current for the year ahead. Those who obtained their MCSE last year will now notice on their certificate planner the opportunity to attain the 2018 version of the competency via the passing of a single exam. For Dynamics 365 Customer Engagement focused professionals, assuming you only passed either the Sales or Customer Service exam last year, passing the other exam should be all that is required to recertify – unless you fancy your chances trying some of the new exams described below.

New MCSE Exams

Regardless of what boat you are relating to the Business Applications MCSE, those looking to obtain to 2018 variant of the certification can expect to see two additional exams available that will count towards the necessary award requirements:

The exams above currently only appear on the US Microsoft Learning website but expect them to appear globally within the next few weeks/months.

MB2-877 represents an interesting landmark in Microsoft’s journey towards integrating FieldOne as part of Dynamics CRM/Dynamics 365 Customer Engagement, with it arguably indicating the ultimate fruition of this journey. To pass the exam,Ā  you are going to have to have a good grasp of the various entities involved as part of the field service app, as well as a thorough understanding of the mobile application itself. As is typically the case when it comes to Dynamics 365 certification, the Dynamics Learning Portal (DLP) is going to be your destination for preparatory learning resources for the exam; along with a good play around with the application itself within a testing environment. If you have access to the DLP, it is highly recommended you complete the following courses at your own pace before attempting the exam:

Dynamics 365 for Retail is a fairly new addition to the “family” and one which – admittedly – I have very little experience with. It’s rather speedy promotion to exam level status I, therefore, find somewhat surprising. This is emphasised further by the fact that there are no dedicated exams for other, similar applications to Field Service, such as Portals. Similar to MB2-877, preparation for MB6-897 will need to be directed primarily through DLP, with the following recommended courses for preparation:

Exam Preparation Tips

I’ve done several blog posts in the past where I discuss Dynamics CRM/Dynamics 365 exams, offering help to those who may be looking to achieve a passing grade. Rather than repeat myself (or risk breaking any non-disclosure agreements), I’d invite you to cast your eyes over these and (I hope!) that they prove useful in some way as part of your preparation:

If you have got an exam scheduled in, then good luck and make sure you study! šŸ™‚

Working in-depth amidst the Sales entities (e.g. Product, Price List, Quote etc.) within Dynamics CRM/Dynamics 365 Customer Engagement (CRM/D365CE) can produce some unexpected complications. What you may think is simple to achieve on the outset, based on how other entities work within the system, often leads you in a completely different direction. A good rule of thumb is that any overtly complex customisations to these entities will mean having to get down and dirty with C#, VB.Net or even JScript. For example,Ā we’ve seen previously on the blog how,Ā with a bit of a developer expertise, it is possible to overhaul the entire pricing engine within the application to satisfy specific business requirements.Ā There is no way in which this can be modified directly through the application interface, which can lead to CRM deployments that make imaginative and complicated utilisation of features such as Workflows, Business Rules and other native features. Whilst there is nothing wrong with this approach per-say, the end result is often implementations that look messy when viewed cold and which become increasingly difficult to maintain in the long term. As always, there is a balance to be found, and any approach which makes prudent use of both application features and bespoke code is arguably the most desirous end goal for achieving certain business requirements within CRM/D365CE.

To prove my point around Sales entity “oddities”, a good illustration can be found when it comes to working with relationship field mappings and Product records. The most desirable feature at the disposal of CRM customisers is the ability to configure automated field mapping between Entities that have a one-to-many (1:N) relationship between them. What this means, in simple terms, is that when you create a many (N) record from the parent entity (1), you can automatically copy the field values to a matching field on the related entity. This can help to save data entry time when qualifying aĀ Lead to anĀ Opportunity, as all the important field data you need to continue working on the record will be there ready on the newly createdĀ Opportunity record. Field mappings can be configured from the 1:N relationship setting window, via theĀ MappingsĀ button:

There are a few caveats to bear in mind – you can only map across fields that have the same underlying data type and you cannot map multiple source fields to the same target (it should be obvious why this is šŸ™‚ ) – but on the whole, this is a handy application feature that those who are more accustomed to CRM development should always bear in the mind when working with CRM/D365CE.

Field mappings are, as indicated, a standard feature within CRM/D365CE – but when you inspect the field relationships betweenĀ theĀ Product andĀ Quote Product entity, there is no option to configure mappings at all:

Upon closer inspection, many of the relationships between theĀ Product entity and others involved as part of the sales order process are missing the ability to configure field mappings. So, for example, if you have a requirement to map across the value of theĀ Description entity to a newly createdĀ Quote ProductĀ record, you would have to look at implementing a custom plugin to achieve your requirements. The main benefit of this route is that we have relatively unrestricted access to the record data we need as part of a plugin execution session and – in addition – we can piggyback onto the record creation process to add on our required field “in-flight” – i.e. whilst the record is being created. The code for achieving all of this is as follows:

using System;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace D365.BlogDemoAssets.Plugins
{
    public class PreQuoteProductCreate_GetProductAttributeValues : 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 tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

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

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

            {
                Entity qp = (Entity)context.InputParameters["Target"];

                //Only execute for non-write in Quote Product records

                EntityReference product = qp.GetAttributeValue<EntityReference>("productid");

                if (product != null)

                {

                    Entity p = RetrieveProductID(service, product.Id);
                    string desc = p.GetAttributeValue<string>("description");
                    tracingService.Trace("Product Description = " + desc);
                    qp.Attributes["description"] = desc;

                }

                else

                {
                    tracingService.Trace("Quote Product with record ID " + qp.GetAttributeValue<Guid>("quotedetailid").ToString() + " does not have an associated Product record, cancelling plugin execution.");
                    return;
                }
            }
        }

        public Entity RetrieveProductID(IOrganizationService service, Guid productID)
        {
            ColumnSet cs = new ColumnSet("description"); //Additional fields can be specified using a comma seperated list

            //Retrieve matching record

            return service.Retrieve("product", productID, cs);
        }
    }
}

They key thing to remember when registering your Plugin via the Plugin Registration Tool (steps which regular readers of the blog should have a good awareness of) is to ensure that theĀ Event Pipeline Stage of Execution is set toĀ Pre-operation. From there, the world is your oyster – you could look at returning additional fields from theĀ ProductĀ entity to update on yourĀ Quote Product record or you could even look at utilising the same plugin for the Order Product and Invoice Product entities (both of these entities also haveĀ Description field, so the above codeĀ should work on these entities as well).

It’s a real shame that Field Mappings are not available to streamline the population of record data from the Product entity; or the fact that there is no way to utilise features such as Workflows to give you an alternate way of achieving the requirement exemplified in this post. This scenario is another good reason why you should always strive to be a Dynamics 365 Swiss Army Knife, ensuring that you have a good awareness of periphery technology areas that can aid you greatly in mapping business requirements to CRM/D365CE.

Working with Dynamics CRM/Dynamics 365 Customer Engagement (CRM/D365CE) solution imports can often feel a lot like persuing a new diet or exercise regime; we start out with the best of intentions of how we want things to proceed, but then something comes up to kick the wheel off the wagon and we end up back at square one šŸ™‚ Anything involving a change to an IT system can generally be laborious to implement, due to the dependencies involved, and things can invariably go wrong at any stage in the process. The important thing is to always keep a cool head, take things slowly and try not to overcomplicate things from the outset, as often the simplest or most obvious explanation for an issue is where all due attention should be focused towards.

In the case of CRM/D365CE, we have the ability to access full log information relating to a solution import – regardless of whether it has failed or succeeded. This log can prove to be incredibly useful in troubleshooting solution import failures. Available as an XML download, it can be opened within Excel to produce a very readable two tab spreadsheet containing the following information:

  • TheĀ Solution tab provides high-level information regarding the solution package, its publisher, the status of the import and any applicable error messages.
  • TheĀ ComponentsĀ tab lists every single attempted action that the solution attempted to execute against the target instance, providing a timestamp and any applicable error codes for each one.

The above document should always be your first port of call when a solution import fails, and it will almost certainly allow you to identify the root cause of the failure – as it did for me very recently.

An unmanaged solution import failed with the top-level error messageĀ Fields that are not valid were specified for the entity. Upon closer investigation within the import log, I was able to identify the affected component – a custom attribute on theĀ Quote entity – and the specific error message generated – Attribute…has SourceType 0, but 1 was specified:

The reason why the error was being generated is that a field with the same logical name was present within the environment, something which – for clearly understandable reasons – is not allowed. In this particular scenario, we were doing some tidy up of an existing solution and replacing a calculated field with a new field, with a different data type, using the same attribute name. The correct step that should have been taken before the solution import was to delete the “old” field in the target environment, but this was accidentally not included in the release notes. After completing this and re-attempting the solution import, it completed successfully.

The likelihoodĀ of this error ever occurring in the first place should be remote, assuming that you are customising your system theĀ right way (i.e. using Solution Publisher prefixes for all custom attributes/entities). In this occasion, the appropriate note as part of the release documentation for the solution would have prevented the issue from occurring in the first place. So, as long as you have implemented a sufficiently robust change management procedure, that includes full instructions that are required to be completed both before and after a solution import, you can avoid a similar situation when it comes to replacing entity attributes within your CRM/D365CE solution.