Author Archives: Joshua Sinkamba

My name is Joshua Sinkamba. I am an enthusiastic fully certified Microsoft Dynamics CRM/365 Software Developer who is passionate about programming, solving problems and building relationships. I have worked with every version of Microsoft Dynamics from version 5.0 (2011). In my free time, I enjoy playing soccer, chess and pool, reading, running and pushing my physical boundaries at the gym. Specialties: Microsoft Dynamics CRM/365 Development, .NET Framework, Web / Content Management Systems Development, VSTS / TFS / Git Implementation.

Microsoft Dynamics 365 and SSRS Reports: Dates and Time

In this article, we deep dive into the Microsoft Dynamics 365 Customer Engagement Date and Time field type, as well as explore the implications of this field type on SQL Server Reporting Services (SSRS) reports depending on the behavior and format selected. We demonstrate how to make SSRS reports to display Date and Time fields in the user’s time zone. Also, we look at how the different behaviors and formats of the Date and Time field type are stored in the SQL database. The included use case provides a real world scenario of where the information in this article came in handy.

Use Case

Inspiration

Recently on a client engagement, a bug was raised on a couple of SSRS reports I had built a few months ago. A date field on the reports was displaying the next day i.e. if a record was approved yesterday, the report was showing that it was approved today in some cases. Looking at the records from the Dynamics 365 user interface, i.e. views, Advanced Find and forms, the field was displaying the correct date despite the SSRS reports were showing that same date as the next day. This behavior was only observed on some of the records created by the clients.

I thought of sharing the problem solving approach and tools I used to resolve this bug with the hope the it may help another Microsoft Dynamics 365 software developer in the future.

What was going on

The field in question it is a Date and Time field but the reports are configured to show the date part of the field only, as per the requirements. After investigations, it turned out that the SSRS reports were consuming a Date and Time field type (Behavior: User Local. Format: Date and Time), without translating it into the user’s time zone. Therefore, if a user approved a record between 8pm and Mid-night EST, the date of approval would show up on the SSRS report as the following day. This is because if a Date and Time field type’s behavior is set to User Local, the actual data is store in UTC time but translated to the user’s time zone when displayed to the user in the Dynamics 365 user interface. Unlike the Dynamics 365 user interface, SSRS reports do not automatically provide the translation to the user’s time zone but is can be implemented as shown in the SSRS Reports section below. Therefore, the source of the discrepancy between the date seen by users in Dynamics 365 user and what was being shown in the report lies in the fact that the Dynamics 365 user interface was translating the UTC date into the user’s local time while the report was showing the date as it was recorded in the SQL database. As the client is based in the EST, the bug could only be replicated, under the same conditions, between 8pm and Mid-night EST, when EST and UTC exist on two calendar dates.

Some of the client’s employees was creating the data between 8pm and Mid-night EST. As the field’s behavior is set to User Local, in the four hours time window (8pm to Mid-night EST), UTC and EST are on two different dates, and records approved in period will stored on the following day in UTC, which the database time for User Local behavior fields.

The Tools to Debug the Problem

View the Behavior and Format of Date and Time FIelds

Open the Date and Time field in the Dynamics 365 solution and see how it is saved:
Date and Time field's Behavior and Format

Date and Time Fields: Behavior and Format

The table below shows the different behaviors and formats of Date and Time fields in Dynamics 365, and well as their implications.

BehaviorFormatChanging field’s behavior
User Local Note: This is the behavior of all date and time fields in the previous releases.

– The field values are displayed in the current user’s local time.
– In Web services (SDK), these values are returned using a common UTC time zone format.
Date Only – or – Date and TimeIn the user interface (UI), you can change certain out-of-the-box entity field’s behavior from the User Local to Date Only. For a list of entities and fields, see Changing the field behavior to Date Only on upgrade. You can change the custom entity field’s behavior from the User Local to Date Only or to Time-Zone Independent.

Changing the field behavior affects the field values that are added or modified after the field behavior was changed. The existing field values remain in the database in the UTC time zone format. To change the behavior of the existing field values from UTC to Date Only, you may need a help of a developer to do it programmatically. More information: Convert behavior of existing date and time values in the database. Warning: Before changing the behavior of a date and time field, you should review all the dependencies of the field, such as business rules, workflows, calculated fields, or rollup fields, to ensure that there are no issues as a result of changing the behavior. After changing the behavior of a date and time field, you should open each business rule, workflow, calculated field, and rollup field dependent on the field that you changed, review the information, and save it, to ensure that the latest date and time field’s behavior and value are used. You can restrict modifying the field’s behavior, by setting the CanChangeDateTimeBehavior managed property to False. More information: Set managed property to change date and time behavior
Date Only

– The concept of a time zone isn’t applicable to this behavior. The field values are displayed without the time zone conversion.
– The time portion of the value is always 12:00AM.
– The date portion of the value is stored and retrieved as specified in the UI and Web services (SDK).
Date OnlyThe Date Only behavior can’t be changed to other behavior types, once it’s set.
Time-Zone Independent

– The concept of a time zone isn’t applicable to this behavior. The field values are displayed without the time zone conversion.
– The date and time values are stored and retrieved as specified in the UI and Web services (SDK).
Date Only – or – Date and TimeThe Time-Zone Independent behavior can’t be changed to other behavior types, once it’s set.
Source: Microsoft

.

Date and Time fields in Dynamics 365 User Interface vs. SQL Database

Dynamics 365 User Interface: Date and Time fields

Here are the different possible combinations of storing simple dates and time data in Dynamics 365 (on a classic interface form for the Contact entity):
Date and Time fields on a Form

SQL Database: Date and Time fields

In this section, we will look at how the date and time fields in the previous section are stored in the database. The date and time fields are on the Contact entity (i.e. ContactBase table in my Dynamics 365 SQL database). Querying for the specified columns in the table where the full name is ‘Peter Parker’:

SELECT TOP (10)
       [FullName]    
      ,[hos_uselocal_dateonly]
      ,[hos_uselocal_dateandtime]
      ,[hos_dateonly_dateonly]
      ,[hos_timezoneindependent_dateonly]
      ,[hos_timezoneindependent_dateandtime]
  FROM [House_MSCRM].[dbo].[ContactBase]
  WHERE FullName = 'Peter Parker'

See the results below:
Viewing Date and Time fields in the datebase

From the database results above, it is worth noting:

  • Behavior of User Local and Format of Date Only: these fields are always stored as 4am in my database in EST, which is 12am UTC.
  • Behavior of User Local and Format of Date and Time: in my time zone, i.e. EST, these fields are always stored 4 hours ahead, i.e. UTC time, in the database. In contrast, in the user interface, this field always shows the data in my time zone.
  • Behavior of Time Zone Independent and Format of Date and Time: stores the data in the database as inputted in the user interface and does not respect time zones.

SSRS Reports: Display Dates and Time in the user’s time zone

There are two ways to create SSRS Reports for Dynamics 365. You can create a report in Dynamics 365 using the Report Wizard, export it and add to your Report Server Project in Microsoft Visual Studio IDE, with the built-in fields and parameters, from Dynamics 365. Alternatively, you can take the opposite path of creating a blank Report Server Project in Microsoft Visual Studio IDE, connecting to Dynamics 365, and adding the built-in fields and parameters as needed. If you take the former approach, you will get the built-in fields and parameters in Visual Studio, similar to the image below:
Visual Studio - built in fields and parameters

To translate UTC date and time data, for User Local behavior, in the SQL database into the user’s time zone, you have to use the SSRS Report formula below, where “CRM_UserTimeZoneName” is a parameter and <SSRSDateAndTimeField> is name of your Date and Time field in SSRS:

=Microsoft.Crm.Reporting.RdlHelper.DateTimeUtility.ConvertUtcToLocalTime(
<SSRSDateAndTimeField>, Parameters!CRM_UserTimeZoneName.Value)

“CRM_UserTimeZoneName” is a parameter passed in from Dynamics 365, containing the user’s time zone. The function “Microsoft.Crm.Reporting.RdlHelper.DateTimeUtility.ConvertUtcToLocalTime” iconverts UTC time to the user’s time zone. Therefore, when the user runs the report, they will see the date and time data in their time zone, despite it is stored as UTC by default in the SQL database.

Here are some application examples, where “Fields!hos_uselocal_dateandtimeValue.Value” is the SSRS date and time field in Visual Studio:

  • Display User Local Date and Time SSRS data field in the user’s time zone (showing both date and time data):
=Microsoft.Crm.Reporting.RdlHelper.DateTimeUtility.ConvertUtcToLocalTime(
Fields!hos_uselocal_dateandtimeValue.Value, Parameters!CRM_UserTimeZoneName.Value)
  • Display User Local Date and Time SSRS data field in the user’s time zone (showing the date component only without the time component of the date, in the format dd/MM/yyyy):
=Format(CDate(Microsoft.Crm.Reporting.RdlHelper.DateTimeUtility.ConvertUtcToLocalTime(Fields!hos_uselocal_dateandtimeValue.Value,
Parameters!CRM_UserTimeZoneName.Value)), "dd/MM/yyyy")

Dynamics 365 Online: Accessing the contents of your SQL database

In Dynamics 365 Online, you do not have access to the SQL database that hosts your Dynamics 365 organization. However, Microsoft allows you to access the data in your SQL database by duplicating and syncing to Microsoft Azure. From Azure, you can connect to duplicated SQL database and be able to run the SQL script in this article. For more details duplicating and syncing your Dynamics 365 online SQL database to Microsoft Azure, see: Microsoft Dynamic 365 Data Export Service: Duplicating and Syncing SQL database in Azure

Microsoft Dynamics 365: Server Side Bulk Records Operations

In Microsoft Dynamics 365 Customer Engagement server side automation, e.g. plugins and custom workflow activities, we can write code to create, retrieve, update and delete individual records. When we need to carry out these operations on multiple records, we can use a loop and query the database for each record. However, a much more efficient way to carry out bulk operations is to use the ExecuteMultipleRequest message (to Create, Update and Delete records in bulk), the focus on this article. Also, the application example demonstrates how to make use of these bulk operations methods as well as makes use of the RetrieveMultiple Query Expression (i.e. how to retrieve multiple records in single query) .

The bulk operations suite of methods below can be incorporated into a Dynamics 365 project and be called upon to carry out bulk operations. These methods provide an efficient way of carrying out operations on multiple records, thereby enhancing web pages response rates and user experience.

Bulk Create Records

public static void BulkCreateRecords(IOrganizationService service, EntityCollection entityCollection)
{            
	var multipleRequest = new ExecuteMultipleRequest()
	{                 
		Settings = new ExecuteMultipleSettings()
		{
			ContinueOnError = false,
			ReturnResponses = true
		},                
		Requests = new OrganizationRequestCollection()
	};

	foreach (Entity entity in entityCollection.Entities)
	{
		CreateRequest createRequest = new CreateRequest { Target = entity };
		multipleRequest.Requests.Add(createRequest);
	}

	ExecuteMultipleResponse multipleResponse = (ExecuteMultipleResponse)service.Execute(multipleRequest);
}

Bulk Update Records

public static void BulkUpdateRecords(IOrganizationService service, EntityCollection entityCollection)
{           
	var multipleRequest = new ExecuteMultipleRequest()
	{               
		Settings = new ExecuteMultipleSettings()
		{
			ContinueOnError = false,
			ReturnResponses = true
		},               
		Requests = new OrganizationRequestCollection()
	};
   
	foreach (Entity entity in entityCollection.Entities)
	{
		UpdateRequest updateRequest = new UpdateRequest { Target = entity };
		multipleRequest.Requests.Add(updateRequest);
	}

	ExecuteMultipleResponse multipleResponse = (ExecuteMultipleResponse)service.Execute(multipleRequest);
}

Bulk Delete Records

public static void BulkDeleteRecords(IOrganizationService service, EntityCollection entityCollection)
{            
	var multipleRequest = new ExecuteMultipleRequest()
	{ 
		Settings = new ExecuteMultipleSettings()
		{
			ContinueOnError = false,
			ReturnResponses = true
		},                
		Requests = new OrganizationRequestCollection()
	};
   
	foreach (Entity entity in entityCollection.Entities)
	{
		EntityReference entityRef = new EntityReference(entity.LogicalName, entity.Id);

		DeleteRequest deleteRequest = new DeleteRequest { Target = entityRef };
		multipleRequest.Requests.Add(deleteRequest);
	}

	ExecuteMultipleResponse multipleResponse = (ExecuteMultipleResponse)service.Execute(multipleRequest);
}

Example: Calling bulk operations methods and a demonstration of RetrieveMultiple

In the example below, we show how the methods provided above can be put to use to carry out bulk operations, by calling the bulk delete records method, as well as we make use of the RetrieveMultiple Query expression to retrieve multiple records in single database query (see the code below the comment “RetrieveMultiple Query Expression”).

About the plugin

The plugin example below is registered on the Update message Synchronously on the Account entity and PreOperation event pipeline stage of execution. The plugin is triggered by a custom Two Option field called Delete Tasks (logical name: “hos_deletetasks”) field. Therefore, when the user changes the value of the “hos_deletetasks” field from No/false to Yes/true, the plugin retrieves all the tasks that relate to that account and deletes them.

Other things to note

I have added the BulkCreateteRecords, BulkUpdateRecords and BulkDeleteRecords methods to my D365Helpers class, which contains my other reusable methods, so that they can used across multiple server-side automation classes.

using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;

namespace ItsFascinating.Plugins.D365
{
    public class DeleteFollowUpTasks : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {                  
            if (serviceProvider == null)
            {
                throw new ArgumentNullException("localContext");
            }
            #region Plugin Setup Variables
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            #endregion
            #region Validation Prior Execution
            string pluginMessageUpdate = "update";           

            if (context.Stage != 20)
            {
                tracingService.Trace("Invalid Stage: Stage = {0}", context.Stage);
                return;
            }
            
            if (!context.MessageName.ToLower().Equals(pluginMessageUpdate))
            {
                tracingService.Trace("Invalid Message: MessageName = {0}", context.MessageName);
                return;
            }
            #endregion
           
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                Entity primaryEntity = context.InputParameters["Target"] as Entity;
                string accountLogicalName = "account";

                if (primaryEntity.LogicalName != accountLogicalName)
                {
                    tracingService.Trace("Invalid Entity: EntityName = {0}", primaryEntity.LogicalName);
                    return;
                }                    

                try
                {
                    if (primaryEntity.LogicalName.Equals(accountLogicalName))
                        DeleteTasks(service, primaryEntity, tracingService);                   
                }                
                catch (FaultException ex)
                {
                    throw new InvalidPluginExecutionException(string.Format("An error occurred in the DeleteFollowUpTasks plug-in. {0}", ex.Message));
                }
                catch (Exception ex)
                {
                    tracingService.Trace(string.Format("DeleteFollowUpTasks plugin: {0}", ex.ToString()));
                    throw new Exception(string.Format("An error occurred in the DeleteFollowUpTasks plug-in. {0}", ex.Message));
                }
            }
        }

        private static void DeleteTasks(IOrganizationService service, Entity primaryEntity, ITracingService tracingService)
        {
            object deleteTasksFieldObj = new object(); ;
            string deleteTasksLogicalName = "hos_deletetasks";
            bool deleteTasks;
            string taskLogicalName = "task";
            string subject = "subject";
            string description = "description";
            string accountRegardingFieldLogicalName = "regardingobjectid";
            bool containsDeleteTasks = primaryEntity.Attributes.TryGetValue(deleteTasksLogicalName, out deleteTasksFieldObj);

            if (containsDeleteTasks)
            {
                deleteTasks = (bool)deleteTasksFieldObj;

                if (deleteTasks)
                {
                    //RetrieveMultiple Query Expression
                    var tasksQuery = new QueryExpression(taskLogicalName);
                    tasksQuery.ColumnSet = new ColumnSet(subject, description);
                    tasksQuery.Criteria.AddCondition(accountRegardingFieldLogicalName, ConditionOperator.Equal, primaryEntity.Id);
                    var tasksResults = service.RetrieveMultiple(tasksQuery);

                    if (tasksResults.Entities.Count > 0)
                        D365Helpers.BulkDeleteRecords(service, tasksResults);
                }
            }
        }
    }
}

Microsoft Dynamics 365: Hide and Show Buttons

In this article, we cover to how to hide buttons as well as how to show buttons that are hidden, in Microsoft Dynamics 365 Customer Engagement. To achieve this object, we will use an example with practical requirements. To implement what is in this article, the user must have a System Administrator security role in Dynamics 365.
Dynamics 365 Home Command Bar Buttons

Working with Ribbon Workbench for Dynamics 365

If you do not already have the Ribbon Workbench for Dynamics 365, go and download it: Ribbon Workbench for Dynamics 365. The Ribbon Workbench comes as a Microsoft Dynamics 365 managed solution. Therefore, after downloading it, you have to import it in your Dynamics 365 instance by going to: Settings >> Solutions >> Import. After successfully following the import dialogue to the end and successfully importing the solution, refresh the page and you will able to see the Ribbon Workbench button in the Solutions area of Dynamics 365:
Ribbon Workbench Button in Dynamics 365

Application Example: Hide Buttons

Below we will provide an example of practical client requirements and show step by step how the Ribbon Workbench for Dynamics can help us address these requirements.

Requirements

A national, provincial /state or city level education board that has a Microsoft Dynamics 365 Customer Engagement implementation that keeps track of all the schools and students in its jurisdiction. Each student in the system must be linked to a school and under no circumstance can a user of the system be able to create a student record that is not associated to a school record. This prevents the problem of creating of orphan student records in the system i.e. student records without an associated school record.

Implication of the Requirements

To implement these requirements, we will have to restrict the places where users can access the Student (logical name: contact ) entity’s New button and be able to create new student records. Student records should only be created in relation to a School (logical name: account) record i.e. from the School records’ Students sub-grids as shown in the image below.

  • The Student’s sub-grid on Schools’ form
    Create Student record via Form Sub-grid
  • The Student’s sub-grid on Schools’ related entities menu
    Related entities

    Create Student record via Form related entities

Therefore, after implementing the instructions provided under the sub-section “Implementation of the requirements” and section “Other Settings”, users will only be able to create student records from the from the Student entity’s sub-grids on the School records. Hence as a corollary, users will no longer be able to create Student records from:

  • Quick Create menu
    Create Student record via Quick Create
  • Advanced Find
    Create Student record via Advanced Find
  • Student entity’s Home Command Bar
    Create Student record via Home Command Bar
  • Student entity’s Form Command Bar
    Create Student record via Form Command Bar

Implementation of the requirements

  1. To Go to Settings >> Solutions, and create a new solution. Add the entities whose ribbons you wish to edit. In our example, we will be adding the Student (logical name: contact ) entity only to the new solution. Exclude all entity assets but include the entity’s metadata. Save and Publish All Customizations.Create Solution With Student Entity
  2. Under Settings >> Solutions, click on the Ribbon Workbench button to launch the Dynamics 365 Ribbon Workbench. Select the solution created in part 1 and click OK.
    Launch Ribbon Workbench
  3. After opening the solution in Ribbon Workbench, it will look like the image below. Ensure you are editing the intended entity (A in the image below). In our case, we intend to edit the ribbons for the Student (logical name: contact ) entity. On the Student (logical name: contact ) entity, we intend to hide all the New buttons except on the Student’s sub-grids (B in the image below).
    Ribbon Workbench - verify entity and identify except buttons
  4. To hide a button, right click on it and select “Hide”. After hiding all the Student (logical name: contact) entity New buttons, except on the Student’s sub-grid (highlighted in green in the image above), the solution in Ribbon Workbench will look like the image below:
    Hide New Button Except Sub-grid
  5. Click Publish. After the publishing process is done, the only place you will be able to see the New button, in your Dynamics instance, for the Student (logical name: contact) entity is on the sub-grids, as per the requirements above.

Application Example: Show Hidden Buttons

Months or years later, the requirements may change and the client may want to us to reverse the work done in the previous section and show the the hidden New buttons. To accomplish that:

  1. To Go to Settings >> Solutions, and create a new solution. Add the entities whose ribbons you wish to edit. In our example, we will be adding the Student (logical name: contact ) only to the new solution. Exclude all entity assets but include the entity’s metadata. Save and Publish All Customizations.Create Solution With Student Entity
  2. Under Settings >> Solutions, click on the Ribbon Workbench button to launch the Dynamics 365 Ribbon Workbench. Select the solution created in part 1 and click OK.
    Launch Ribbon Workbench
  3. After opening the solution in Workbench, it will look like the image below. Ensure you are editing the intended entity (A in the image below). In our case, we intend to edit the ribbons for the Student (logical name: contact ) entity. On the Student (logical name: contact ) entity, we intend to make visible the hidden buttons. In contrast to the “Application Example: Hide Button” section, the next time we load an entity in Ribbon Workbench, the hidden buttons are no longer shown command bars section (B in the image below) but are shown under the Hidden Actions section (C in the image below). To make visible these hidden buttons, right click on each button (C in the image below) and select “Un-hide”.
    Make hidden buttons visible
  4. Click Publish. After the publishing process is done, the “New” buttons that were previously hidden will become visible in your Dynamics 365 instance.

Other Settings

  • To fully ensure that there no orphan Student records are created, make sure the Student entity’s School field is set to Required and the field is added to all Student entity’s forms.
  • Quick Create: To stop users from creating the Student records from the Quick Create menu, remove the Quick Create option from the entity:
    1. Go to Settings >> Solutions
    2. Open the solution containing the entity
    3. In the solution, click on the entity. Remove the “Allow quick create” option.
      Remove the Quick Create option
    4. Save and publish the solution

Microsoft Dynamics 365: Counting Sub-grid Records and Enabling Users to Hide Empty Sub-grids

In Microsoft Dynamics 365, we can use sub-grids to present data to users in a tabular format. This is a nice and compact way of displaying data. However, sometimes there is no data in those sub-grids to display and the end users might prefer to get back that space and optimize the space on the form. In this blog post, we cover how we can dynamically obtain the number of records in a sub-grid using JavaScipt and use that information to give end users the flexibility to optimize the space on forms by hiding empty sub-grids.

There are two types of sub-grids on Dynamics 365 forms:

  • Read-only sub-grids: Show data in a tabular format but do not allow users to edit data directly on the sub-grid. To edit the data, users have to double click on the record in the grid to open the form, edit the data, and then save it.
  • Editable sub-grids: Apart from showing data in a tabular format, editable sub-grids offer rich inline editing capabilities on web and mobile clients including the ability to group, sort, and filter data within the same sub-grid so that you do not have to switch records or views. In addition to providing all these extensive editing capabilities, editable sub-grids still honor the read-only sub-grid metadata and field-level security settings as well as the general Microsoft Dynamics 365 security model.

Summary of the key syntax

Here is a high-level summary of the main Microsoft Dynamics 365 JavaScript syntax covered in this blog post.

  • Count the number of records in sub-grid, where <subgridName> is the logical name of the sub-grid :
    formContext.getControl("<subgridName>").getGrid().getTotalRecordCount();
  • Remove the blank option in an option set, where <optionsetName> is the logical name of the option set:
    formContext.getControl("<optionsetName>").removeOption("");
  • Get the option set’s selected value , where <optionsetName> is the logical name of the option set:
    formContext.getAttribute("<optionsetName>").getValue();
  • Set the selected value of an option set, where <optionsetName> is the logical name of the option set and <value> is the value the option set will be set to:
    formContext.getAttribute("<optionsetName>").setValue("<value>");
  • Enable/Disable a field, where <fieldName> is the logical name of the field and <Boolean> can be true or false (i.e. true disables field and false enables it):
    formContext.getControl("<fieldName>").setDisabled(<Boolean>);
  • Hide/Unhide section, where <nameOfTab> is the name of the tab containing the section, <nameOfSection> is the name of the section and <Boolean> can be true or false (i.e. true makes the section visible and false hides the section):
    formContext.ui.tabs.get("<nameOfTab>").sections.get("<nameOfSection>").setVisible( <Boolean>);

Application Example: Enabling Users to Hide Empty Sub-grids

Using the code below, we can give end users the flexibility to optimize the space on forms by hiding empty sub-grids. In the code below, the hideEmptyOfficesSubgrid function is the entry point and is called when saving the form and loading the form.

function hideEmptyOfficesSubgrid(executionContext) {  
    var formContext = executionContext.getFormContext();
    var operationsTabName = "tab_operations";
    var officeGridSectionName = "office_grid_section";
    var officeGridName = "office_subgrid";
    var enableOfficeGridName = "hos_enableofficesgrid";
    var no = 183840000;
    var yes = 183840001;
    formContext.getControl(enableOfficeGridName).removeOption(""); 

    setTimeout(function () {
        if (formContext.getControl(officeGridName) != null && formContext.getControl(officeGridName) != undefined) {
            var count = formContext.getControl(officeGridName).getGrid().getTotalRecordCount();
            var enableOfficeGrid = formContext.getAttribute(enableOfficeGridName);
            var enableOfficeGridCtrl = formContext.getControl(enableOfficeGridName);
            var officeGridSection = formContext.ui.tabs.get(operationsTabName).sections.get(officeGridSectionName);
            disableSubgridControlField(count, enableOfficeGridCtrl);
            hideEmptySubgrid(count, enableOfficeGrid, officeGridSection, no, yes);
        }
    }, 5000);      
}

function disableSubgridControlField(count, subgridCtrlField) {  
    if (count <= 0) 
        subgridCtrlField.setDisabled(false);       
    else if (count > 0) 
        subgridCtrlField.setDisabled(true);
}

function hideEmptySubgrid(count, subgridCtrlField, subgridSection, no, yes) {  
    if (areTheseTwoInputsIdentical(subgridCtrlField.getValue(), yes)) 
        subgridSection.setVisible(true);       
    else if (count <= 0 && (areTheseTwoInputsIdentical(subgridCtrlField.getValue(), no) || subgridCtrlField.getValue() == null)) 
        subgridSection.setVisible(false);
    else if (count > 0 && areTheseTwoInputsIdentical(subgridCtrlField.getValue(), no)) {
        subgridCtrlField.setValue(yes);
        subgridSection.setVisible(true);
    }
}

function areTheseTwoInputsIdentical(input1, input2) {
    if (input1 == input2) 
        return true;
    else 
        return false;
}

How the code works

In the first image below, the account record is not enabled for Offices (i.e. the value of Enable Office Grid option set is No). Therefore, Offices grid is empty and hidden.
Account record not enabled for Offices sub-grid

After enabling the account record for Offices by changing the value of the option set field, “Enable Offices Grid” to Yes, the Offices sub-grid shows up and we can add offices to the sub-grid as shown below. The “Enable Offices Grid” field controls the appearance and disappearance of the Offices sub-grid.
Account record enabled for Offices sub-grid

As shown in the image above, the Enabled Office Grid option set is locked as soon as we add and save records to the Offices sub-grid. This prevents a scenario of logic inconsistency where an account record has offices in it’s sub-grid and the user proceeds to change the value of the Enable Offices Grid field to No. Therefore, the Enable Office Grid field value can only be changed back to No and hide the Offices grid after the sub-grid emptied up and there are no more records in it. Therefore, using the solution above, end users can be empowered with the option of hiding empty sub-grids and optimize the space on the form.

Microsoft Dynamics 365: Varying Form Behavior On Form-type and Save-mode

Microsoft Dynamics 365 offers the ability to vary the behavior of forms based on the form type and how the form is saved, using JavaScript. In this blog post, we cover how we can make use of this flexibility offered by the platform to deliver unique solutions for end users.

Summary of the key syntax

Here is a high-level summary of the main Microsoft Dynamics 365 JavaScript syntax covered in this blog post.

  • Getting the type of form:
    formContext.ui.getFormType();
  • Hide a tab on a form (where “TabName” represents the name of the tab you would like to hide):
    formContext.ui.tabs.get("TabName").setVisible(false);
  • Get the object with methods to manage the Save event:
    executionContext.getEventArgs();
  • Get the form Save Mode:
    executionContext.getEventArgs().getSaveMode();
  • Inhibit the default save behavior on a form
    executionContext.getEventArgs().preventDefault();
  • Check if the default save behavior was inhibited
    executionContext.getEventArgs().isDefaultPrevented();
  • Get the form context
    executionContext.getFormContext();

Form Type

To get the Form Type, you use the following JavaScript code:

formContext.ui.getFormType();

Here are the different Dynamics 365 form types and their respective JavaScript return values:

Form type Return Value
Undefined0
Create1
Update2
Read Only3
Disabled4
Bulk Edit6

Application Example: Varying form behavior on Form Type

In this application example, we will hide the Products and Services tab on the Create type form (with JavaScript Return Value = 1). However, it will be still be visible on all other applicable form types. This prevents users from adding information to the Products and Services tab before a Contact record has been created and saved. Therefore, the user must first create the Contact record (using the Create form type) and after the record has been created, the users will be presented with the Update form type (with JavaScript Return Value = 2) and will be able to access the Products and Services tab as well as its contents.

The Contact entity’s create form type will look similar to this (i.e. the Products and Services tab is hidden):
Contact - Create form type

In contrast, the Update form type still shows the Products and Services tab.
Contact - Update form type

Here is the code to executes the requirement above.

//Hide Products and Services tab on Create form 
function HideProductsServicesTab(executionContext) {   
  //Get form context   
  var formContext = executionContext.getFormContext();   
  //Get form type   
  var formType = formContext.ui.getFormType();   
  //If formtype is Create, hide Products and Services tab on form     
  if (formType == 1) {
    formContext.ui.tabs.get("productsandservices").setVisible(false);   
  }   
  //To see the form type return value in your browser console
  console.log("Form type = " + formType); 
}

Save Mode

To get the form’s Save Mode, you use the following JavaScript code:

executionContext.getEventArgs().getSaveMode();

Here are Dynamics 365’s different Save Modes and their respective JavaScript return values as well as the applicable entities.

Entity Save Mode Return Value
AllSave1
AllSave and Close2
AllDeactivate5
AllReactivate6
EmailSend7
LeadDisqualify15
LeadQualify16
User or Team owned entitiesAssign47
ActivitiesSave as Completed58
AllSave and New59
AllAuto Save70

Application Example: Varying form behavior on Save Mode

Here is an example of how you can vary the form’s behavior on the Save Mode.

//Inhibit auto save functionality on a form
function InhibitAutoSave(executionContext) {
  var eventArgs = executionContext.getEventArgs();
  //To see the Save Mode return value in your browser console
  console.log("Save Mode = " + eventArgs.getSaveMode());
  //If save mode is 'Save and Close' or 'Auto Save', inhibit default behavior i.e. save 
  if (eventArgs.getSaveMode() == 2 || eventArgs.getSaveMode() == 70) {
    eventArgs.preventDefault(); 
  } 
  //To see if the auto save behavior was prevented in your browser console
  console.log("Default behaviour prevented: " + eventArgs.isDefaultPrevented());
 }

If you have some unsaved changes on a form, the code above inhibits the regular form auto save behavior as well as inhibits the regular Save and Close auto save behavior, that you ordinarily get when you navigate away from from a form with unsaved changes. Instead of auto-saving (i.e. the default behavior), if you try to navigate away from a form with an unsaved changes, the JavaScript code above will block the auto save behavior and offer you the pop up notification below. If you choose “OK”, you will loose all your unsaved changes, and if you choose “Cancel”, you will remain the same form. With the default auto save behavior inhibited, users have to manually save the form, by clicking on the Save button or using the keyboard shortcut: “Control + S”.
Auto Save Inhibited: Do you want to loose your changes?

What if you want to inhibit the auto-save behavior throughout your Dynamics 365 organization (i.e. disable auto-save on all forms), instead of a specific form? It would be inefficient to implement the JavaScript code above on all forms across all your Dynamics 365 organization entities. An efficient way execute such a requirement (i.e. on all forms) is to:

  1. Go to Settings > Administration.
  2. Click on System Settings.
  3. Click on the General tab
  4. Set the Enable auto save on all forms option, to No.
  5. Click OK, in the footer of the window.

Disabling Auto Save in a Dynamics 365 Organization

Microsoft Dynamics 365: Notifications Using JavaScript

In Microsoft Dynamics 365, sometimes we need to send automated notifications to end users. These notifications can come from the server (i.e. back-end automation using workflows, plugins, etc) or from the code loaded in the user’s web browser (i.e. front-end or client side automation using JavaScript). The latter notifications will make up the scope of this blog post. We will cover form level notifications (with and without persistence), field level notifications and popup boxes notifications using JavaScript.

Summary of the key syntax

Here is a high-level summary of the main Microsoft Dynamics 365 JavaScript notifications demonstrated in this blog post.

  • Form Level Notifications – Set Notification
    formContext.ui.setFormNotification(message, messageType, uniqueId);
  • Form Level Notifications – Clear Notification
    formContext.ui.clearFormNotification(uniqueId);
  • Field Specific Notifications – Set Notification
    formContext.getControl(fieldLogicalName).setNotification(message, uniqueId);
  • Field Specific Notifications – Clear Notification
    formContext.getControl(fieldLogicalName).clearNotification(uniqueId);
  • Alert Box
    alert(message);
  • Confirmation Box
    confirm(message);
  • Prompt Box
    prompt(message, sampleText);

Form Level Notifications

Here is an example of form level notifications applied to the Contact entity’s form. The notification options available at form-level are: Error, Warning and Information. These notification options have different icons as shown in the image below.
Page Level Notification

Below is the JavaScript code that was used to generate the notifications shown in the image above. Therefore, every time a user opens up a contact form, these notifications will always appear at the top of the form.

function FormLevelNotification(executionContext) {
    var formContext = executionContext.getFormContext();    
    formContext.ui.setFormNotification("Example of an ERROR notification. ", "ERROR");
    formContext.ui.setFormNotification("Example of a WARNING notification. ", "WARNING");
    formContext.ui.setFormNotification("Example of an INFORMATION notification.", "INFO");    
}

Instead of having persistent notifications (ever present whenever a user opens the contact form), it is commons for organizations to implement a modified version of these notifications i.e. where the notifications above expire after a specifies amount of time. Therefore, every time a user opens a contact form (or any other form where this functionality is implemented), they get notifications similar to image above, but after a specified amount of time, the notifications disappear and user end up with form like the one below.
Page Level Notification Expired

This modified option where the form level notifications disappear after a specified amount of time can be accomplished using the code below. In code below, I have chosen an expiration time of 30 seconds. Therefore, the form level notifications would only persist for the first 30 seconds after a user opens up a contact form.

function FormLevelNotificationWithExpiration(executionContext) {
    var formContext = executionContext.getFormContext();
    var notificationTime = 30000; // 30 seconds in milliseconds 
    var errorId = "error";
    var warningId = "warning";
    var infoId = "info";
    //Set notifications
    formContext.ui.setFormNotification("Example of an ERROR notification. ", "ERROR", errorId);
    formContext.ui.setFormNotification("Example of a WARNING notification. ", "WARNING", warningId);
    formContext.ui.setFormNotification("Example of an INFORMATION notification.", "INFO", infoId);

    //Clear the notifications after the specified amount of time time e.g. 5 seconds
    setTimeout(
        function () {      
            formContext.ui.clearFormNotification(errorId);
            formContext.ui.clearFormNotification(warningId);           
            formContext.ui.clearFormNotification(infoId);
        },
        notificationTime
    );
}

Field Specific Notifications

To guide users in completing a form correctly, you can provide field specific notification like the ones below. The example below shows the subscription section on the Contact entity’s form. Logically, the Subscription Start Date must precede the Subscription End Date. Whenever a users enters a Subscription End Date that precedes the Subscription Start Date, the field level notifications appear advising the user that “The Subscription End Date cannot be before Subscription Start Date” as show below.
Field Level Notifications

The functionality shown in the image above was accomplished using the following JavaScript functions.

//Validation of the TV Subscription Dates
function TvSuscriptionDateValidation(executionContext) {
    var formContext = executionContext.getFormContext();
    var tvSubStartDateLogicalName = "hos_tvsubstartdate";
    var tvSubEndDateLogicalName = "hos_tvsubenddate";
    var startDateField = formContext.getAttribute(tvSubStartDateLogicalName);
    var endDateField = formContext.getAttribute(tvSubEndDateLogicalName);
    var endDateFieldControl = formContext.getControl(tvSubEndDateLogicalName);
    var startDate, endDate;

    if (startDateField != null && endDateField != null) {
        startDate = startDateField.getValue();
        endDate = endDateField.getValue();
        if (IsDate(startDate) && IsDate(endDate) && startDate > endDate ) {
            //Display an error message if the dates entered are logically invalid.
            endDateFieldControl.setNotification("The Subscription End Date cannot be before Subscritpion Start Date.", "tvEndDate");
            endDateField.setValue(null);
        }
        else {
            endDateFieldControl.clearNotification("tvEndDate");            
        }
    }
}

//Verify that the field contains date instead of a null value
function IsDate (input) {
    if (Object.prototype.toString.call(input) === "[object Date]") {
        return true;
    }   
    return false;
}

Popup Notifications

Sometimes you really to get the user’s attention, that is, prevent them from interacting with the form until they have acknowledged your message. To accomplish that endeavor using JavaScript, you can use the popup boxes. There are 3 types of popup boxes (i.e. alert box, confirmation box and prompt box). In this section, we will show what these popup look like in Dynamics 365 and provide an example of how to implement them.

Alert Box

Here is an example of an Alert Box in Dynamics 365. The user can acknowledge the message by pressing OK and proceed interact with the form.
Alert Box

The functionality shown in the image above was accomplished using the following JavaScript function.

function AlertBox() {
    alert("This is an example of a JavScript Alert window ");

    //Alternatively way of writing an Alert Box with the Window prefix:    
    //window.alert("This is an example of a JavScript Alert window ");
}

Confirmation Box

Here is an example of an Confirmation Box in Dynamics 365. The user is given two options. Depending on the user’s choice, you can proceed to add more logic. In this example, we use an alert box to notify user of the choice that was selected.
Confirmation Box

The functionality shown in the image above was accomplished using the following JavaScript function.

function ConfirmBox() {    
    var confirmationText;
    if (confirm("Would you like to proceed?")) {
        confirmationText = "You pressed OK!";
    } else {
        confirmationText = "You pressed Cancel!";
    }
    //Using the alert notification to show the option selected
    alert(confirmationText);

    //Alternatively way of writing an Confirm Box with the Window prefix:    
    //window.confirm("Press a button: 'OK' or 'Cancel'");
}

Prompt Box

Here is an example of an Prompt Box in Dynamics 365. The user is given a text box where they can enter an answer as well as two options (i.e. OK and Cancel). Depending on the user’s responses, you can proceed to add more logic. In this example, we use an alert box to notify user of the text that was entered, if they click on OK.
Prompt Box

The functionality shown in the image above was accomplished using the following JavaScript function.

function PromptBox() {
    var userText = prompt("Please enter your name below", "This is sample text ");
    //Using the alert notification to show the text entered in the prompt box
    alert("You said your name is: \n" + userText);   

    //Alternatively way of writing an Prompt Box with the Window prefix:
    //window.prompt("Please enter some text below", "This is sample text ");
}

Best Practices and Recommendations

For demonstration purposes in this blog post, I included the notification messages in the code. However, for easier maintenance and to give more flexibility to the end users, who may not be Dynamics 365 software developers, it is recommended to put the notification messages in the Dynamics 365 Configuration Data and then create JavaScript helper function(s) that retrieve the data at runtime using a key. Using this approach, end users with the appropriate security roles can update the JavaScript notification messages anytime without calling upon the services of a Dynamics 365 software developer with JavaScript experience.

Microsoft Dynamics 365: How to Remove Unwanted Special Characters from Text Fields

Microsoft Dynamics 365 has a flexible and extendable architecture, and can be integrated with other platforms. In integrations, it is common for data entered in Dynamics 365 to be shared with other platforms. Although Dynamics 365 text fields are flexible to save special characters, other platforms, that integrate with Dynamics 365, may not be as flexible. For example, when you integrate Dynamics 365 with SharePoint or One Drive, you may want to use the record’s name in Dynamics 365 to create the record’s document set or folder in SharePoint or One Drive . In such a case, Dynamics 365 fields being used by SharePoint or One Drive have to adhere to those platforms’ more restrictive special characters restrictions. The the invalid characters for OneDrive, OneDrive for Business on Office 365, and SharePoint Online are ” * : < > ? / \ | and for OneDrive for Business on SharePoint Server 2013 they are ~ . ” # % & * : < > ? / \ { | }.

The plugin in this post addresses this integration challenge by preventing users from saving restricted special characters in the text field(s) enabled text validation. When creating or updating a record in Dynamics 365, the plugin will check the text field(s), enabled for text validation, and strip out the characters that are not part of the permitted list characters.

To demonstrate this plugin’s functionality, we enabled text validation on the Account Name field for the Account entity. The validation rule applied by the plugin is: the Account Name field is only permitted to take in alphanumeric characters, space and 2 special characters i.e. underscore and hyphen. If a user enters any characters in the field, other than the permitted list of characters, they will be stripped out when they save the record. In the image below, a user entered both valid and invalid characters. Before Save:
Before Saving, user has entered invalid special characters

When saving the record, the plugin stripped out the invalid characters from the text field, leaving only the permitted characters (i.e. alphanumeric characters, space and 2 special characters i.e. underscore and hyphen). After Save:
After Saving, invalid special characters are removed

The Plugin

using System;
using System.Text.RegularExpressions;
using Microsoft.Xrm.Sdk;


namespace ItsFascinating.Plugins.D365
{
    /// 
    /// This plugin removes non-approved special characters from the Account Name field of the Account entity
    /// 
    public class AccountNameCharactersRemover : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            string pluginMessageCreate = "create";
            string pluginMessageUpdate = "update";
            Entity primaryEntity = new Entity();

            if (serviceProvider == null)
            {
                throw new ArgumentNullException("localContext");
            }

            #region Plugin Setup Variables
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            #endregion
            #region Validation Prior Execution
            //Exist if plugin if it's not called at stage 20 of the excution pipeline
            if (context.Stage != 20)
            {
                tracingService.Trace("Invalid Stage: Stage = {0}", context.Stage);
                return;
            }
           
            //Only execute plugin if plugin message is Create or Update 
            if (!(context.MessageName.ToLower().Equals(pluginMessageCreate) || context.MessageName.ToLower().Equals(pluginMessageUpdate)))
            {
                tracingService.Trace("Invalid Message: MessageName = {0}", context.MessageName);
                return;
            }
            #endregion

            #region Plugin Logic
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                try
                {
                    primaryEntity = context.InputParameters["Target"] as Entity;
                    
                    // Display Name: "Account Name" | Database Name: "name"
                    string accountNameFieldLogicalName = "name";
                    Object accountNameObj;
                    string accountName, updatedAccountName;
                    //Regex pattern for alphanumeric characters, space and 2 special characters i.e. underscore and hyphen                 
                    string permittedCharacters = "[^0-9a-zA-Z_ -]+";
                   
                    //Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
                    if (primaryEntity.Attributes.TryGetValue(accountNameFieldLogicalName, out accountNameObj))
                    {
                        //Get value of that has been entered in the text field
                        accountName = Convert.ToString(accountNameObj);
                        //Remove remove unpermitted characters
                        updatedAccountName = Regex.Replace(accountName, permittedCharacters, "");
                        //Update the value of the text field, after removing the restricted characters
                        primaryEntity.Attributes[accountNameFieldLogicalName] = updatedAccountName;
                    }
                }
                catch (Exception ex)
                {
                    throw new InvalidPluginExecutionException(string.Format("Error occured in the PreUpdateAccount plugin: {0}", ex.Message));
                }
            }
            #endregion
        }
    }
}

Microsoft Dynamics 365: The Complete Guide to Getting and Setting Fields Using a Plugin

When extending the Microsoft Dynamics 365 platform, there is often a need to retrieve and/or set field values. In this post, we will cover how to get and set values for the Microsoft Dynamics 365/CRM platform fields using a plugin, developed using the Microsoft C# programming language. You can also develop plugins and perform the operations demonstrated in this post using other .Net platform-supported programming languages. In Dynamics 365, you can create the following types of fields (or datatypes): Single Line of Text, Option Set, MultiSelect Option Set, Two Options, Image, Whole Number, Floating Point Number, Decimal Number, Currency, Multiple Lines of Text, Date and Time, Lookup and Customer.

Plugin Set Up

Here is a plugin setup you can use to implement the field specific code in this post.

using System;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk;

namespace ItsFascinating_Plugins_D365
{
    public class PreUpdateAccount : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            string pluginMessageUpdate = "update";
            Entity primaryEntity = new Entity();

            if (serviceProvider == null)
            {
                throw new ArgumentNullException("localContext");
            }                

            #region Plugin Setup Variables
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.InitiatingUserId);
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            #endregion
            #region Validation Prior Execution
            //Exist if plugin if it's not called at stage 20 of the excution pipeline
            if (context.Stage != 20)
            {
                tracingService.Trace("Invalid Stage: Stage = {0}", context.Stage);
                return;
            }
            //Exist if plugin if it called for any other operation other than Update 
            if (!context.MessageName.ToLower().Equals(pluginMessageUpdate))
            {
                tracingService.Trace("Invalid Message: MessageName = {0}", context.MessageName);
                return;
            }
            #endregion
            #region Plugin Logic
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                try
                {
                    primaryEntity = context.InputParameters["Target"] as Entity;


                    //INSERT THE FIELD SPECIFIC CODE BELOW HERE


                }
                catch(Exception ex)
                {                 
                    throw new InvalidPluginExecutionException(string.Format("Error occured in the PreUpdateAccount plugin: {0}", ex.Message));
                }
                
            }
            #endregion
        }
    }
}               

Field Specific Code

To use the field specific code below, in the plugin above, copy the code below and paste it below the line “//INSERT THE FIELD SPECIFIC CODE BELOW HERE”, in the Plugin Setup Code section.

Single Line of Text

Here is an example of a Single Line of Text field on a Dynamics 365 form:
Single Line of Text

Here is the C# code for getting and setting the value of a Single Line of Text field (Display Name: “Account Number” | Database Name: “accountnumber”):

//Get and Set Single Line of Text field value
// Display Name: "Account Number" | Database Name: "accountnumber"
string accountNumberFieldLogicalName = "accountnumber";
Object accountNumberObj;
string accountNumber;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(accountNumberFieldLogicalName, out accountNumberObj))
{
	//Get value of Single Line of Text field
	accountNumber = Convert.ToString(accountNumberObj);

	//Set value of Single Line of Text field
	primaryEntity[accountNumberFieldLogicalName] = "ACC-1000-TV-2019-ON";
}

Multiple Lines of Text

Here is an example of a Multiple Lines of Text field on a Dynamics 365 form:
Multiple Lines of Text

Here is the C# code for getting and setting the value of a Multiple Lines of Text field (Display Name: “Description” | Database Name: “description”):

//Get and Set Multiple Lines of Text field value
// Display Name: "Description" | Database Name: "description"
string descriptionFieldLogicalName = "description";
Object descriptionObj;
string description;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(descriptionFieldLogicalName, out descriptionObj))
{
	//Get value of Multiple Lines of Text field
	description = Convert.ToString(descriptionObj);

	//Set value of Multiple Lines of Text field
	primaryEntity[descriptionFieldLogicalName] =
		"To be, or not to be, that is the question: \n" +
		"Whether \'tis nobler in the mind to suffer \n" +
		"The slings and arrows of outrageous fortune, \n" +
		"Or to take Arms against a Sea of troubles, \n" +
		"And by opposing end them: to die, to sleep; \n" +
		"No more; and by a sleep, to say we end \n" +
		"The heart-ache, and the thousand natural shocks \n" +
		"That Flesh is heir to? \'Tis a consummation \n" +
		"Devoutly to be wished.To die, to sleep, \n" +
		"perchance to Dream; aye, there\'s the rub, \n" +
		"For in that sleep of death, what dreams may come, \n" +
		"When we have shuffled off this mortal coil, \n" +
		"Must give us pause.";
}

Whole Number

Here is an example of a Whole Number field on a Dynamics 365 form:
Whole Number field

Here is the C# code for getting and setting the value of a Whole Number field (Display Name: “Number of Employees” | Database Name: “numberofemployees”):

//Get and Set Whole Number field value
// Display Name: "Number of Employees" | Database Name: "numberofemployees"
string numberOfEmployeesFieldLogicalName = "numberofemployees";
Object numberOfEmployeesObj;
int numberOfEmployees;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(numberOfEmployeesFieldLogicalName, out numberOfEmployeesObj))
{
	//Get value of Whole Number field
	numberOfEmployees = Convert.ToInt32(numberOfEmployeesObj);

	//Set value of Whole Number field
	primaryEntity[numberOfEmployeesFieldLogicalName] = 120000;
}

Decimal Number

Here is an example of a Decimal Number field on a Dynamics 365 form:
Decimal Number field

Here is the C# code for getting and setting the value of a Decimal Number field (Display Name: “Exchange Rate” | Database Name: “exchangerate”):

//Get and Set Decimal Number field value
// Display Name: "USD/CAD Exchange Rate" | Database Name: "hos_usdcadexchangerate"
string exchangeRateFieldLogicalName = "hos_usdcadexchangerate";
Object exchangeRateObj;
decimal exchangeRate;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(exchangeRateFieldLogicalName, out exchangeRateObj))
{
	//Get value of Decimal Number field
	exchangeRate = Convert.ToDecimal(exchangeRateObj);

	//Set value of Decimal Number field
	primaryEntity[exchangeRateFieldLogicalName] = 1.312775;
}

Floating Point Number

Here are some examples of Floating Number fields on a Dynamics 365 form:
Floating Point Number field

Here is the C# code for getting and setting the value of a Floating Number field (Display Name: “Address 1: Longitude” | Database Name: “address1_longitude”):

//Get and Set Floating Point Number field value
// Display Name: "Address 1: Longitude" | Database Name: "address1_longitude"
string longitudeFieldLogicalName = "address1_longitude";
Object longitudeFieldObj;
float longitudeField;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(longitudeFieldLogicalName, out longitudeFieldObj))
{
	//Get value of Floating Point Number field
	longitudeField = Convert.ToSingle(longitudeFieldObj);

	//Set value of Floating Point Number field
	primaryEntity[longitudeFieldLogicalName] = -79.387054f;
}

Currency

Here is an example of a Currency field on a Dynamics 365 form:
Currency field

Here is the C# code for getting and setting the value of a Currency field (Display Name: “Annual Revenue” | Database Name: “revenue”):

//Get and Set Currency field value
// Display Name: "Annual Revenue" | Database Name: "revenue"
string revenueFieldLogicalName = "revenue";
Object revenueObj;
decimal revenue;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(revenueFieldLogicalName, out revenueObj))
{
 //Get value of Currency field                                               
 revenue = ((Money)revenueObj).Value;

 //Set value of Currency field
 primaryEntity[revenueFieldLogicalName] = new Money(10200500800.78m);
}

Two Options

Here are some examples of Two Options fields on a Dynamics 365 form:
Two Options field

Here is the C# code for getting and setting the value of a Two Options field (Display Name: “Email” | Database Name: “donotemail”):

//Get and Set Two Options field value
// Display Name: "Email" | Database Name: "donotemail"
string dontAllowEmailsFieldLogicalName = "donotemail";
Object dontAllowEmailsObj;
bool dontAllowEmails;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(dontAllowEmailsFieldLogicalName, out dontAllowEmailsObj))
{
	//Get value of Two Options field                                               
	dontAllowEmails = (bool)dontAllowEmailsObj;

	//Set value of Two Options field
	primaryEntity[dontAllowEmailsFieldLogicalName] = true;
}

Option Set

Here is an example of an Option Set field on a Dynamics 365 form:
Option Set field

Here is the C# code for getting and setting the value of an Option Set field (Display Name: “Contact Method” | Database Name: “preferredcontactmethodcode”):

//Get and Set Option Set field value
// Display Name: "Contact Method" | Database Name: "preferredcontactmethodcode"
string contactMethodLogicalName = "preferredcontactmethodcode";
Object contactMethodObj;
int contactMethod;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(contactMethodLogicalName, out contactMethodObj))
{
	//Get value of Option Set field 
	contactMethod = ((OptionSetValue)contactMethodObj).Value;

	//Set value of Option Set field
	primaryEntity[contactMethodLogicalName] = 3;
}

MultiSelect Option Set

Here is an example of a MultiSelect Option Set field on a Dynamics 365 form:
MultiSelect Option Set field

Here is the C# code for getting and setting the values of a MultiSelect Option Set field (Display Name: “Geographical Areas of Operation” | Database Name: “hos_geographicalareasofoperation”) :

//Get and Set MultiSelect Option Set field value
// Display Name: "Geographical Areas of Operation" | Database Name: "hos_geographicalareasofoperation"
string locationFieldLogicalName = "hos_geographicalareasofoperation";
Object locationFieldObj;
OptionSetValueCollection locationField;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(locationFieldLogicalName, out locationFieldObj))
{
	//Get the collection of value(s) for MultiSelect Option Set field
	locationField = (OptionSetValueCollection)locationFieldObj;

	//Set collection of value(s) for MultiSelect Option Set field (version 1 - single line implementation)
	primaryEntity[locationFieldLogicalName] = new OptionSetValueCollection(new List<OptionSetValue>() { new OptionSetValue(183840000), new OptionSetValue(183840001), new OptionSetValue(183840003), new OptionSetValue(183840006), new OptionSetValue(183840010) });

	//Set collection of value(s) for MultiSelect Option Set field (version 2 - multiple lines implementation)
	/*
	OptionSetValueCollection locationMultiOptionSet = new OptionSetValueCollection();
	locationMultiOptionSet.Add(new OptionSetValue(183840000));
	locationMultiOptionSet.Add(new OptionSetValue(183840001));
	locationMultiOptionSet.Add(new OptionSetValue(183840003));
	locationMultiOptionSet.Add(new OptionSetValue(183840006));
	locationMultiOptionSet.Add(new OptionSetValue(183840010));
	primaryEntity[locationFieldLogicalName] = new OptionSetValueCollection(locationMultiOptionSet);
	*/
}

Date and Time

Here are some examples of Date and Time fields on a Dynamics 365 form:
Date and Time field

Here is the C# code for getting and setting the value of a Date and Time field (Display Name: “Follow Up Date” | Database Name: “hos_followupdate”):

//Get and Set Date and Time field value
// Display Name: "Subscription Start" | Database Name: "hos_subscriptionstart"
// Display Name: "Subscription End" | Database Name: "hos_subscriptionend"
string subscriptionStartLogicalName = "hos_subscriptionstart";
string subscriptionEndLogicalName = "hos_subscriptionend";
Object subscriptionStartdObj;
DateTime subscriptionStart = new DateTime();

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(subscriptionStartLogicalName, out subscriptionStartdObj))
{
	if (subscriptionStartdObj != null)
	{
		//Get value of Date and Time field  
		subscriptionStart = Convert.ToDateTime(subscriptionStartdObj);

		//Set value of Date and Time field   
		primaryEntity[subscriptionEndLogicalName] = subscriptionStart.AddYears(1);
	}
	else
	{
		//If the Subscription Start is null, set the Subscription End as well
		primaryEntity[subscriptionEndLogicalName] = null;
	}
}

Lookup

Here is an example of a Lookup field on a Dynamics 365 form:
Lookup field

Here is the C# code for getting and setting the value of a Lookup field (Display Name: “Account Manager” | Database Name: “hos_accountmanager”) :

//Get and Set Lookup field value
// Display Name: "Account Manager" | Database Name: "hos_accountmanager"
string accountManagerLogicalName = "hos_accountmanager";
Object accountManagerObj;
EntityReference accountManager;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(accountManagerLogicalName, out accountManagerObj))
{
	if (accountManagerObj != null)
	{
		//Get value of a Lookup field and its attributes                                              
		accountManager = (EntityReference)accountManagerObj;
		Guid guid = accountManager.Id;
		string entityName = accountManager.LogicalName;
		string name = accountManager.Name;

		//Set value of a Lookup field and its attributes                        
		primaryEntity[accountManagerLogicalName] = new EntityReference("systemuser", new Guid("A6AF4DAB-10C4-E911-A2E2-005056AE4389"));
	}
}

Customer

Here is an example of a Customer field on a Dynamics 365 form:
Customer field

Here is the C# code for getting and setting the value of a Customer field (Display Name: “Main Customer” | Database Name: “hos_maincustomer”):

//Get and Set Customer field value
// Display Name: "Main Customer" | Database Name: "hos_maincustomer"
string mainCustomerLogicalName = "hos_maincustomer";
Object mainCustomerObj;
EntityReference mainCustomer;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(mainCustomerLogicalName, out mainCustomerObj))
{
	if (mainCustomerObj != null)
	{
		//Get value of Customer field and its attributes                                              
		mainCustomer = (EntityReference)mainCustomerObj;
		Guid guid = mainCustomer.Id;
		string entityName = mainCustomer.LogicalName;
		string name = mainCustomer.Name;

		//Set value of Customer field and its attributes                        
		primaryEntity[mainCustomerLogicalName] = new EntityReference("account", new Guid("2E4D98F9-8EF9-E911-A2E8-005056AE4389"));
	}
}

Extra Details

The plugin demonstrated in this post was developed on Microsoft Dynamics 365 version 9.0.3.7 and Microsoft Dynamics 365 SDK version 9.0.2.12.

How to Integrate Microsoft Dynamics 365 Online with SharePoint and OneNote

In this post, we cover how to integrate Microsoft Dynamics 365 Online with SharePoint and OneNote. We start off with why you should consider integrating Dynamics 365 with SharePoint and OneNote. Secondly, a full step-by-step guide is provided on how to create a SharePoint Site for Dynamics 365 document management. Thirdly, a detailed guide on how to integrate Microsoft Dynamics 365 Online with SharePoint Online is provided. We conclude with an elaborate guide of how to integrate Microsoft Dynamics 365 Online with OneNote.

Why Consider Integrating Dynamics 365 with SharePoint and OneNote?

  • Cost: It is cheaper to store documents in Microsoft SharePoint than in Microsoft Dynamics 365 online.
  • Enhanced document Management functionality: SharePoint online is a Microsoft platform that specializes in document management and hence has more enhanced features focused on document management e.g. creating sub-sites, information rights management, SharePoint business intelligence, etc.
  • Enhance collaboration: teams can retain the great Dynamics 365 collaboration tools while also utilizing the advance document management features of SharePoint.
  • Enhanced productivity: by allowing users to access documents in multiple places. If someone is working in SharePoint, they do not need to go back to Dynamics 365 to access documents uploaded in Dynamics 365.
  • Seamless user experience: Despite the documents are stored in SharePoint after the integration, to a Dynamics 365 Online user, it feels like the documents still reside in Dynamics 365. The user experience is not compromised by this integration.

How To Create a SharePoint Site for Dynamics 365 Document Management

Step 1: Login in to https://admin.microsoft.com as a global or SharePoint administrator. If you see a notification that you have no permission to access the page, you do not have SharePoint administrator rights or Office 365 global administrator permissions in your organization.

Step 2: In the left pane, under Admin centers, select SharePoint. You might need to select Show all to see the full list of admin centers. This will take take you to the SharePoint home page.

Step 3: On the SharePoint home page, click Create site to start the process of creating a SharePoint site.
SharePoint Home Page

Step 4: You will get pop up window, asking you to choose the type of SharePoint site you would like to create. Choose Team site:
Choosing type of SharePoint site to create

Step 5: Enter details about your team site. Take note of Site Address, we will need it later when integrating Microsoft Dynamics 365 with this SharePoint team site. Afterwards click Next.
3.2. Enter details about the SharePoint Site

Step 6: You can add members and/or additional owners of the site. This is not a required step, you can come and add site owners and additional members later after you have created the site. Click Finish to complete the process of creating your SharePoint site.
Add site members and additional owners

Step 7: After Step 6, your new site will be created and it will look similar to:
New SharePoint team site

Integrating Dynamics 365 Online With SharePoint

For this demonstration, we are using version 9.1.0000.9825 of Dynamics 365 (2019 release wave 2 enabled) online. However, the integration process is the same or very similar in other versions of Dynamics 365/CRM online.

Step 1: In your Dynamics 365/CRM instance, login with the System Administrator and/or System Customizer security role. Then navigate to Settings >> Document Management. On this page, click on Configure Server-Based SharePoint Integration.
D365 Document Management Page

Step 2: You will get the following pop up, asking you where your SharePoint sites are located. In this demonstration, we are working with Dynamics 365 online and SharePoint online, so the Online option is selected. Afterwards, click Next.
Choose location of SharePoint site

Step 3: Enter the URL of the SharePoint Site (see Step 5 of section How To Create a SharePoint Site for Dynamics 365 Document Management above). A check will be conducted on whether the URL you have provided is a valid SharePoint online site and if it exists in the same Office 365 tenant as your Dynamics 365 organization. After enabling server-based SharePoint integration, you cannot go back to the client-side integration, the default setting prior to this integration.  If you would like to proceed, click Next.
Enter SharePoint Site URL

Step 4: After your SharePoint site has been validated by Dynamics 365, click Finish.
Validation of SharePoint site by Dynamics 365

Step 5: In Dynamics 365, go to Settings >> Document Management. You will notice that the Configure Server-Based SharePoint Integration option, mentioned in Step 1, is no longer available. This is because we have correctly configured Dynamics 365 with SharePoint. On this page, now click on Document Management Settings to complete the process of integrating Dynamics 365 with SharePoint.
Document Management Settings

Step 6: After executing the previous step, the window below will pop up, where you can select the Dynamics 365 entities you would like to enable for document management in SharePoint. Afterwards click Next to continue the integration process.
Configure Dynamics 365 entities for document management

Step 7: Select the SharePoint folder structure for the entities selected in the previous step. In the example below, I have selected a folder structure based on entities. Afterwards, click Next.
Choose SharePoint folder structure

Step 8: The following window will pop up. Click OK, if you would like to proceed with the creation of document libraries in the provided SharePoint site.
SharePoint Document Libraries Creation

Step 9: Wait until the document libraries have been created. After the document libraries have been created you will get a window similar to the one below, notifying you that the process has been completed. Click Finish.
Document Library Creation Process Complete

You have successfully enabled Dynamics 365 for document management in SharePoint. For the entities enabled, files uploaded for their records will be stored in SharePoint and you can make utilize the full range of document management capabilities offered by SharePoint. Your documents will be accessible in both Dynamics 365 and SharePoint.

To view the SharePoint sites enabled for document management in your Dynamics 365 organization, go to Settings >> Document Management. Click on SharePoint Sites. You will get a window similar to the one below:
Active SharePoint Sites for Dynamics 365 Organization

To view the Dynamics 365 entities’ folders created in SharePoint, navigate to the SharePoint team site you provided above. In the left navigation panel, click on Site Contents. You will get a window similar to:
Dynamics 365 entities' folders in SharePoint

Integrating Dynamics 365 Online With OneNote

Step 1: In Dynamics 365, go to Settings >> Document Management. On this page, click on OneNote Integration.
OneNote Integration

Step 2: After executing the previous step, the window below will pop up, where you can select Dynamics 365 entities you would like to enable for OneNote Integration. Click Finish to complete the process.
Choose the entities to enable for OneNote Integration

You have successfully integrated Dynamics 365 with OneNote. For records of the enabled entities, notes made in Dynamics 365 will be synced with OneNote in SharePoint Online. Also, you can access these notes on your desktop (i.e. OneNote Desktop) and mobile devices (i.e. OneNote Mobile).