© Sanjaya Yapa  2019
Sanjaya YapaCustomizing Dynamics 365https://doi.org/10.1007/978-1-4842-4379-4_5

5. Advanced Customizations

Sanjaya Yapa1 
(1)
Kandy, Sri Lanka
 

The objective of this chapter is to explore the advanced customizations that can be applied to Dynamics 365. This includes implementing complex business processes with plugins and custom workflow activities. Also, you will learn how you can deploy these using the SPKL Task Runner, which was discussed in Chapter 2. Finally, you will also look at querying data using the Web API endpoints.

Getting Ready

To start with advanced customizations, you must first create the early bound types and include them in the Visual Studio project. You can create a separate Dynamics 365 solution to anchor the advanced customizations but you could also include them in the base solution. Having a single solution makes it simple to eliminate any complexities down the line. When it comes to plugin and custom workflow development, you can either use early-bound types or late-bound methods. The primary benefit of using early-bound types is that this eliminates typos when it comes to using attributes, making your code less prone to errors. Plus, this allows you to use LINQ queries, speeding up the development process. Additionally, this allows you to keep your code simple. There are several ways to generate early-bound types.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig1_HTML.jpg
Figure 5-1

Early Bound Generator plugin

Creating the Plugin

Let’s look at how this plugin can be used to generate the early-bound entity types. First, establish the connection with the Dynamics 365 instance. Once you’re connected successfully and open the Early Bound Generator plugin, you will be directed to the screen shown in Figure 5-2.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig2_HTML.jpg
Figure 5-2

Early Bound Generator plugin after opening it

You must make sure you enter the correct namespace name for the project in which you are going to use the entity classes. If you click the Create All button, you can create all the entities, option sets, and actions at one time. Or you can create them individually by going into each tab (Entities, Option Sets, and Actions) and clicking the Create Entities/Option Sets/Actions button on each tab, which will generate the relevant class files. The generated files will be located in the folder shown in Figure 5-3.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig3_HTML.jpg
Figure 5-3

Generated early-bound classes

Now you can add these files to the project, as illustrated in Figure 5-4. As you can see, all the namespaces are set as SBMA.Plugins.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig4_HTML.jpg
Figure 5-4

Adding early-bound generated classes to the project

So, let’s look at the requirements for SBMA. When a new member is created, a subscription must be created based on the membership type (Platinum, Gold, Silver, and Bronze). This action should be automated so the subscription record and relevant payment record are created behind the scenes swiftly so that when the user goes in for the verification, the records are there. Figure 5-5 shows the high-level business process.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig5_HTML.jpg
Figure 5-5

Member subscription and payment creation process

We will show you a quick trick to make the plugin development easier. If you have Visual Studio 2015 installed, install the Dynamics CRM Developer Toolkit and create a Dynamics 365 plugin project. Navigate to the plugins folder, and you will see the PluginBase.cs class. As part of Microsoft best practices, this particular class will give you a wealth of resources to improve your plugin development. Include it in your project and make sure you change the namespace. Then, when you write the plugin, you can extend your plugin from the PluginBase class. Remember, you do not have to use fancy patterns when writing plugins; just stick to the basic code principles and always try to keep it simple.

The plugin code will create only the Member Subscription record. Creating the payment record will be done through a Dynamics 365 action. You can code the payment record creation in the plugin, but actions, as explained earlier, reduce the amount of coding you have to do. You will learn how to create an action to automatically create the Member Payment record through the plugin later in the chapter. See Figure 5-6, the PluginBase.cs class is added to the Plugin Project.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig6_HTML.jpg
Figure 5-6

PluginBase class added to the solution

Let’s kick off the development by creating the MemberPlugn.cs file, which will extend the PluginBase.cs file, as shown here:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ThreadTask = System.Threading.Tasks;
using Microsoft.Xrm.Sdk;
using Microsoft.Crm.Sdk;
namespace SBMA.Plugins
{
  public class MembershipPlugin : PluginBase
  {
      private readonly string postImageAlias = "PostImage";
      public MembershipPlugin() : base(typeof(MembershipPlugin)){}
      protected override void ExecuteCrmPlugin(LocalPluginContext localPluginContext) {}
  }
}
As per the requirements, you will first need to retrieve the member subscription type the user has selected. The following code segment retrieves it by passing the GUID of the new membership record created:
/// <summary>
/// Get membership type details of the new member
/// </summary>
/// <param name="crmServiceContext"></param>
/// <param name="memberid"></param>
/// <returns></returns>
private sbma_membershiptype GetMembershipTypeOfMember
                              (CrmServiceContext crmServiceContext, Guid membershipTypeId)
{
   var membershiptype = (from mt in crmServiceContext.sbma_membershiptypeSet.Where
                        (m => m.sbma_membershiptypeId == membershipTypeId)
                         select mt).FirstOrDefault();
   return (sbma_membershiptype)membershiptype;
}
To get the newly created ID of the membership record, you will have to get the post-image of the record. The post-image and pre-image of a record are useful in updates. A pre-image can be described as a snapshot of the attributes of a record before the core operations. Similarly, a post-image can be described as a snapshot of the entity attributes after the core operations. As shown in Table 5-1, the availability of the pre- and post-images differs within the execution pipeline.
Table 5-1

Plugin Event Pipeline and Image Availability

Message

Stage

Pre-Image

Post-Image

Create

PRE

No

No

Create

POST

No

Yes

Update

PRE

Yes

No

Update

POST

Yes

Yes

Delete

PRE

Yes

No

Delete

POST

Yes

No

In this case, the plugin is fired after creating the membership record, which will get you the record after creating it. So, you will update the code file, as shown here:
protected override void ExecuteCrmPlugin(LocalPluginContext localPluginContext)
{
  if (localPluginContext == null)
                  throw new ArgumentNullException("Local Plugin Context");
  IPluginExecutionContext executionContext
                              = localPluginContext.PluginExecutionContext;
  ITracingService trace = localPluginContext.TracingService;
  //Get the post image
  Account newMembershipEntity = (executionContext.PostEntityImages != null &&
      executionContext.PostEntityImages.Contains(this.postImageAlias)) ?
      executionContext.PostEntityImages[postImageAlias].ToEntity<Account>() :
             new Account();
}
After obtaining all the required data, you are now in a position to create the related subscription record. The following code creates the Subscription record:
/// <summary>
/// Creating the member subscription
/// </summary>
/// <param name="organizationService"></param>
/// <param name="member"></param>
private void CreateMemberSubscription(IOrganizationService organizationService, Account member)
{
  CrmServiceContext crmServiceContext =new CrmServiceContext(organizationService);
  //Get Membership Type of the member
  sbma_membershiptype membershipType = GetMembershipTypeOfMember(crmServiceContext,
                                       member.sbma_MembershipTypeId.Id);
  //Set the Membershipcription Properties
  sbma_membersubscription membersubscription = new sbma_membersubscription
  {
    //Se the entity reference with Member record
    sbma_MemberId = new EntityReference(member.LogicalName, member.AccountId.Value),
    //Set the entity reference with Membership Type record
    sbma_MembershipTypeId = new EntityReference(
         membershipType.LogicalName, membershipType.sbma_membershiptypeId.Value),
    //Set the subscription due date
    sbma_SubscriptionDueDate = DateTime.Now.AddDays(7.0),
    //Set the Subscription status to pending
    sbma_SubscriptionStatus = new OptionSetValue(
                     (int)sbma_membersubscription_sbma_SubscriptionStatus.Pending)
  };
  //Calling the organization service to create the new member subscription
  organizationService.Create(membersubscription);
}
You can see that you call GetMembershipTypeOfMember in this method. This creates the member subscription. You need to define the membership type as well. Finally, you need to call this method in the main execution method, as shown here:
protected override void ExecuteCrmPlugin(LocalPluginContext localPluginContext)
{
  if (localPluginContext == null)
      throw new ArgumentNullException("Local Plugin Context");
  IPluginExecutionContext executionContext =
                        localPluginContext.PluginExecutionContext;
  ITracingService trace = localPluginContext.TracingService;
  //Get the post image
  Account newMembershipEntity = (executionContext.PostEntityImages != null &&
      executionContext.PostEntityImages.Contains(this.postImageAlias)) ?
      executionContext.PostEntityImages[postImageAlias].ToEntity<Account>() :
      new Account();
  //Create the membersusbscription
  CreateMemberSubscription(localPluginContext.OrganizationService,
                                                     newMembershipEntity);
}
Now that you have completed coding the plugin, you must deploy it. You will be using the SPKL Task Runner for the deployment. Install it in your project, as demonstrated in an earlier chapter. To deploy the plugin via SPKL, you must add a few attributes to your code, as shown here:
namespace SBMA.Plugins
{
  [CrmPluginRegistration(MessageNameEnum.Create,
      "account", StageEnum.PostOperation,
      ExecutionModeEnum.Synchronous, "name", "Post-Create Account",
      1000, IsolationModeEnum.Sandbox, Image1Name = "PostImage",
      Image1Type = ImageTypeEnum.PostImage,
      Image1Attributes = "accountid,name,accountnumber,sbma_membershiptypeid")]
  public class MembershipPlugin : PluginBase
  {

As you can see, we have set these attributes to deploy our plugin as a synchronous plugin, and the plugin message is Create. Since this is executed after the Membership record is created, it is set as a Post Create plugin. As discussed earlier, you need a post-image, which is the record that is created and that defines the attributes for the post-image, which will be returned to your code. After setting up these attributes, connect to the Dynamics 365 instance with the SPKL Task Runner by executing the deploy-plugins.bat command. To learn more about plugin deployment with the SPKL Task Runner, refer to https://www.develop1.net/public/post/2017/05/12/Deploying-Plugins-using-spkl-Task-Runner .

Figure 5-7 shows the membership being created. When the membership record is created, the relevant subscription will be created as well, as shown in Figure 5-8.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig7_HTML.jpg
Figure 5-7

Creating a membership record

../images/471991_1_En_5_Chapter/471991_1_En_5_Fig8_HTML.jpg
Figure 5-8

Created membership subscription

You can do many things with this process. For instance, when the subscription is created, until it is paid, you could trigger a workflow that waits until the due date and sends an e-mail to the member saying the subscription is not paid. The workflow will stop if the subscription status is set to Paid. Another scenario would be to set the subscription status to Paid if the related payment is completed. The takeaway from this is that no matter how complex the scenario is, you can implement it using these advanced customizations of Dynamics 365.

You will need to investigate the other side of this on your own. If you do not map your processes correctly, it will be maintenance nightmare and will have a big impact on the performance. Even though you have a lot of flexibility to extend the application, you need to use that flexibility with care.

Creating Custom Workflow Activities

In this section, you will learn how to write a custom workflow activity to implement complex business processes. As part of the scenario discussed in the previous section, now you must create the payment records to track the payments. To complete the process, a workflow should be triggered behind the scenes to create the payment record as soon as the subscription record is created. This workflow should then wait for seven days from the date the payment record is created and send an e-mail reminder to the primary contact of the business to pay the membership fee.

This workflow will be triggered when the member subscription record is created, and the custom workflow activity will be triggered to create the payment record. You need a custom workflow activity here, because you have to determine the payment method defined on the membership record. Remember, only one level of entity relationship is available to access from a standard workflow. Similarly, when sending the e-mail, you need the e-mail address of the primary contact of the member.

To begin creating the workflow, let’s first create the parent workflow. We discussed the details of how to create a workflow in the previous chapter. In this example, we will also demonstrate the use of input and output variables.

To begin writing the workflow activity, you will be using early-bound classes. Follow the steps to generate the early-bound classes described earlier. You need to generate it again, because the assembly for the workflow activity is different, in this case, SBMA.Workflow. In the XrmToolBox, change the assembly name as required, generate the entity reference classes, and add them to the project. See Figure 5-9.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig9_HTML.jpg
Figure 5-9

Generating entity references for the workflow activity

As shown next, after setting this up, add the workflow activity class to your project:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
namespace SBMA.Workflows
{
  public class CreateMemberPayment : CodeActivity
  {
      public override void ExecuteCRMWorkFlowActivity
            (CodeActivityContext context, LocalWorkflowContext crmWorkflowContext)
      {
        base.ExecuteCRMWorkFlowActivity(context, crmWorkflowContext);
      }
  }
}

Important

To use the Microsoft.Xrm.Sdk.Workflow library, you must install it from NuGet. Get the latest version from https://www.nuget.org/packages/Microsoft.CrmSdk.Workflow/ . Also, as we did with plugins, we will be using the WorkflowActivityBase class that was generated and the CodeActivity class, which is the parent class of the custom activity and which is a child class of System.Activities.CodeActivity.

One of the cool things about workflow activities is that you can pass parameters to the activity and return values from it using out arguments. For this activity, you must pass the Member reference, which is the owner of the subscription created. With this reference, you can determine the preferred payment method of the member. Let’s code.

For this workflow activity, you must pass the member reference and subscription reference. You can see from the following code that the two input variables are declared:
[Input("Member")]
[ReferenceTarget("account")]
public InArgument<EntityReference> InMember { get; set; }
[Input("MemberSubscription")]
[ReferenceTarget("sbma_membersubscription")]
public InArgument<EntityReference> InMemberSubscription { get; set; }
public override void ExecuteCRMWorkFlowActivity
      (CodeActivityContext context, LocalWorkflowContext crmWorkflowContext)
{
  var accountId = this.InMember.Get(context).Id;
  var memberSubscriptionId = this.InMemberSubscription.Get(context).Id;
  CreateMemberPaymentRecord(crmWorkflowContext.OrganizationService,
                  12, memberSubscriptionId, accountId);
}
To create the member payment record, you will require the payment due amount, payment due date, payment method, and member subscription reference. The following code illustrates the methods required to get this information:
/// <summary>
/// Get Subscription fee
/// </summary>
/// <param name="crmServiceContext"></param>
/// <param name="membersubscription"></param>
/// <returns></returns>
private decimal GetDuePayment(CrmServiceContext crmServiceContext,
                              sbma_membersubscription membersubscription)
{
  var membershipType = (from mt in crmServiceContext.sbma_membershiptypeSet
                        where mt.sbma_membershiptypeId ==
                        membersubscription.sbma_MembershipTypeId.Id
                        select mt).FirstOrDefault();
  return membershipType.sbma_SubscriptionFee.Value;
}
/// <summary>
/// Get member details
/// </summary>
/// <param name="crmServiceContext"></param>
/// <param name="member"></param>
/// <returns></returns>
private Account GetMember(CrmServiceContext crmServiceContext, Guid memberId)
{
  var membership = (from m in crmServiceContext.AccountSet.Where
                        (a => a.AccountId == memberId) select m).FirstOrDefault();
  return membership;
}
/// <summary>
/// Get Subscription Details
/// </summary>
/// <param name="serviceContext"></param>
/// <param name="memberSubscriptionId"></param>
/// <returns></returns>
private sbma_membersubscription GetSubscription(CrmServiceContext serviceContext, Guid memberSubscriptionId)
{
  var memberSubscription = (from sm in serviceContext.sbma_membersubscriptionSet
                            where sm.sbma_membersubscriptionId ==
                                                     memberSubscriptionId
                            select sm).FirstOrDefault();
  return memberSubscription ;
}
Then call these methods and create the member payment, as illustrated here:
/// <summary>
/// Create member payment record
/// </summary>
/// <param name="organizationService"></param>
/// <param name="memberSubscriptionId"></param>
/// <param name="accountId"></param>
private void CreateMemberPaymentRecord(IOrganizationService organizationService, Guid memberSubscriptionId, Guid accountId)
{
  CrmServiceContext = new CrmServiceContext(organizationService);
  sbma_membersubscription membersubscriptionlocal =
                        GetSubscription(crmServiceContext, memberSubscriptionId);
  Account membershipLocal = GetMember(crmServiceContext, accountId);
  sbma_memberpayment memberpayment = new sbma_memberpayment()
  {
    sbma_AmountDue = new Money(GetDuePayment(crmServiceContext,
                                             membersubscriptionlocal)),
    sbma_PaymentDue = membersubscriptionlocal.sbma_SubscriptionDueDate,
    sbma_PaymentMethod = new OptionSetValue(
                             (membershipLocal.sbma_PaymentMethod.Value ==
                             (int)Account_sbma_PaymentMethod.CreditCard) ?
                             (int)Account_sbma_PaymentMethod.CreditCard :
                             (int)Account_sbma_PaymentMethod.DirectDebit),
    sbma_MemberSubscriptionId = new EntityReference
                  (membersubscriptionlocal.LogicalName, membersubscriptionlocal.Id)
  };
 organizationService.Create(memberpayment);
}
We will be using the SPKL Task Runner to deploy the custom workflow activity; therefore, we have installed the SPKL Task Runner to the workflow project and set the attributes for the workflow activity as we did for the plugins. The completed code should look something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
namespace SBMA.Workflows
{
  [CrmPluginRegistration("CreateMemberPayment", "CreateMemberPayment",
                                    "","",IsolationModeEnum.Sandbox)]
  public class CreateMemberPayment : CodeActivity
  {
    [Input("Member")]
    [ReferenceTarget("account")]
    public InArgument<EntityReference> InMember { get; set; }
    [Input("MemberSubscription")]
    [ReferenceTarget("sbma_membersubscription")]
    public InArgument<EntityReference> InMemberSubscription { get; set; }
    public override void ExecuteCRMWorkFlowActivity(CodeActivityContext context, LocalWorkflowContext crmWorkflowContext)
    {
      // Retrieve the account number from the input paramenter
      var accountId = this.InMember.Get(context).Id;
      // Retrieve the membersubscription form the input parameter
      var memberSubscriptionId = this.InMemberSubscription.Get(context).Id;
     CreateMemberPaymentRecord(crmWorkflowContext.OrganizationService,
                                  memberSubscriptionId, accountId) ;
    }
    /// <summary>
    /// Create member payment record
    /// </summary>
    /// <param name="organizationService"></param>
    /// <param name="memberSubscriptionId"></param>
    /// <param name="accountId"></param>
    private void CreateMemberPaymentRecord(IOrganizationService organizationService, Guid memberSubscriptionId, Guid accountId){}
    /// <summary>
    /// Get Subscription fee
    /// </summary>
    /// <param name="crmServiceContext"></param>
    /// <param name="membersubscription"></param>
    /// <returns></returns>
    private decimal GetDuePayment(CrmServiceContext crmServiceContext,
                              sbma_membersubscription membersubscription){}
      /// <summary>
      /// Get member details
      /// </summary>
      /// <param name="crmServiceContext"></param>
      /// <param name="member"></param>
      /// <returns></returns>
      private Account GetMember(CrmServiceContext crmServiceContext ,
                                                          Guid memberId) {}
      /// <summary>
      /// Get Subscription Details
      /// </summary>
      /// <param name="serviceContext"></param>
      /// <param name="memberSubscriptionId"></param>
      /// <returns></returns>
      private sbma_membersubscription GetSubscription(CrmServiceContext
                               serviceContext, Guid memberSubscriptionId){}
    }
}
The next step is to run the deploy-workflows.bat command of the SPKL Task Runner to install the workflow activity. Once it’s installed, you can see the custom workflow activity when the Add Step drop-down is expanded. See Figure 5-10.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig10_HTML.jpg
Figure 5-10

Custom workflow activity listed with workflow steps

When adding the step to the workflow, you must set the two parameters as illustrated in Figure 5-11.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig11_HTML.jpg
Figure 5-11

Passing the input arguments to the workflow

When the workflow is executed, the payment record will be created based on the membership and the member subscription information.

Calling Custom Actions from JavaScript

In this section, let’s look at how you can call an action using JavaScript and the Web API. The following code can be used externally to create records in Dynamics 365. This approach is extremely useful in scenarios where you allow external programs to interact with the application. In this example, you will look at applying for membership with the organization. First let’s create the action shown in Figure 5-12. This action has a set of parameters that could include the fields of the external application, and once the data is submitted, an applicant record in Dynamics 365 will be created.
../images/471991_1_En_5_Chapter/471991_1_En_5_Fig12_HTML.jpg
Figure 5-12

Submitting the application

The following is the JavaScript code that calls the action. The function callCustomAction is a common function that can be used to call any action in Dynamics 365.
function submitApplication()
{
      var actionName = "sbma_SubmitApplication";
      // Define the input parameters
      var inParams = {
            "Topic" : ("#Topic"),
            "FisrtName" : ("#FisrtName"),
            "LastName" : ("#LastName"),
            "CompanyName" : ("#CompanyName"),
            "BuisnessPhone" : ("#BuisnessPhone"),
            "Email" : ("#Email"),
            "AnnualRevenue" : ("#AnnualRevenue")
      };
      var actionResponse = callCustomAction(actionName, inpParams);
}
//Call the custom action
callCustomAction = function(actionName, inputParameters)
{
      var results = null;
      var orgUrl = Xrm.Page.context.getClientUrl();
      // Web request
      var request = new XMLHttpRequest();
      request.open("POST", orgUrl + actionName,false);
      request.setRequestHeader("Accept", "application/json");
      request.setRequestHeader("Content-Type","application/json; charset-utf-8");
      request.setRequestHeader("OData-MaxVersion", "4.0");
      request.setRequestHeader("OData-Version", "4.0");
      request.onreadystatechange = function () {
      if (this.readyState == 4) {
            request.onreadystatechange = null;
            if (this.status == 200) {
                  alert("Application submitted successfully.");
            } else {
                  var error = JSON.parse(this.response).error;
                  alert(error.message);
            }
      };
      request.send(window.JSON.stringify(inputParameters);
      return results;
}

Again, this is a simple example. Most importantly, there are many other ways to achieve the same functionality within the Dynamics 365 ecosystem, and choosing the one best suited to achieving the project objectives must done with great care.

Summary

This chapter covered the implementation of complex business logic with plugins and custom workflow activities. In the next chapter, you will be taking the implementation a little bit further by integrating Dynamics 365 with Azure.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset