Implementing No Code Dynamics 365 Service Bus Listener using Logic Apps

My last post demonstrated how to implement Dynamics 365 Service Bus listener using Azure Function.
This implementation type requires writing code and if you need absolute flexibility, Azure Function is a great solution. Additionally, Azure Function Apps support pay-as-you-go pricing with free grants, which should be considered when planing your architecture costs.

So why would you want to replace Azure Function with Logic Apps?
Mainly because in many cases, no code is required to implement patterns like Inbound Asynchronous Integration. This enable non-developers to implement complex integration scenarios in very short time.
How is this achieved? By leveraging Logic Apps built-in connectors to ASB, Dynamics 365 and numerous other applications. You can even expose custom connectors to your organizational APIs.
When working with Logic Apps, you should note that triggers (except HTTP WebHook trigger) use polling to detect relevant business events. While you can manage the trigger polling interval, you are billed by each polling action (trigger invocation) even if there is no relevant business event to operate on.

Using the built-in Dynamics 365 connectors allow simple CRUD operations but using the Command pattern can enable more advanced approach, where Logic Apps creates a Command (custom entity) record and Dynamics 365 A\Sync. Workflow operates on the request.

In this post I’ll walkthrough the process of setting a Logic Apps process as an ASB one-way listener which posts incoming messages into Dynamics 365.
To demonstrate posting a message to ASB I’ll use Postman.

Business Scenario:

Whenever a new Account record is created in Contoso’s ERP application, reflect the new Account data into Dynamics 365 CE.

Prerequisites:

  1. Azure subscription allowing you to provision Azure Service Bus and Azure Logic Apps
  2. Microsoft Dynamics 365 9.x instance with admin privileges, a trial will do

Walkthrough:

  1. Setup Azure Service Bus queue 
  2. Setup Logic App process as ASB listener
    In Azure portal click ‘Create a resource’, type ‘Logic App’ and then ‘Create’
    undefined
    undefined
    undefined
    Type in Logic App details and click ‘Create’.
    Once created, navigate to the new resource
    undefined
    Select ‘When a message is received in a Service Bus queue’ trigger
    undefined
    Click +
    undefined
    Enter a meaningful connection name and click to select existing ASB queue. Then click ‘Create’ and ‘Continue’
    undefined
    undefined
    Change the polling interval if you want and click ‘+ New step’
    undefined
    Type ‘Data operations’ and select it from the menu. Then select the Parse JSON action
    undefined
    undefined
    Select the content field and feed in the following expression:
    decodeBase64(triggerBody()?[‘ContentData’])
    undefined
    Click ‘Use sample payload to generate schema’ and then paste in the following JSON sample payload.
    {“name”: “The Krusty Krab”, “websiteurl”: “http://thekrustykrab.com”, “numberofemployees”: 3}
    Click ‘Save’ on the command bar.

    Click ‘New Step’ on your process, then type in Dynamics 365. Select Dynamics 365 and then ‘Create Dynamics 365 record action’
    undefined
    undefined
    Click ‘Sign in’ and sign into your Dynamics 365 organization
    undefined
    Select your organization name, the target entity and then map each required field to the matching detail in the JSON payload.
    To map the nubmerofemployees integer data, put in the following expression:
    int(body(‘Parse_JSON’)?[‘numberofemployees’])
    undefined
    You should now have the following process defined:
    undefined
  3. Test by clicking ‘Run’ which will make you process start polling ASB.
    undefined
    Next, post a message with the required format to ASB queue.
    Note that if you left the trigger interval its 3 minutes default, you may wait up to 3 minutes for the message to be processed.
    You can do this using Postman and this useful collection

    Once Logic Apps picks the new message, you’ll see the process run
    undefined
    Finally, check the new Account record created in your Dynamics 365 organization
    undefined

    If you just set this listener up for learning purposes and you don’t actually need it running, make sure you disable your Logic App process so it will not burden your Azure budget for nothing.


Implementing Dynamics 365 Service Bus Listener using Azure Function

One of the built-in triggers for Azure Function App is Service Bus (ASB) queue trigger, which makes Azure Function a listener for ASB events.
While Azure Service Bus provides a robust decoupling mechanism, the receiving side has to poll for messages or implement a listener

This trigger makes Azure Functions an ideal implementation component for Inbound Asynchronous Integration with Microsoft Dynamics 365,
one that allows you to write custom code, but releases you from explicitly writing a listener.
An Inbound Asynchronous Integration pattern describes scenarios where a business event occurs in an external application and must be reflected into Dynamics 365.
Processing of the event in Dynamics 365 does not occur immediately and sometimes no response is required from Dynamics 365 side (one way).

In this post I’ll walkthrough the process of setting an Azure Function as an ASB one-way listener which posts incoming messages into Dynamics 365.
To demonstrate posting a message to ASB I’ll use Postman.

Business Scenario:

Whenever a new Account record is created in Contoso’s ERP application, reflect the new Account data into Dynamics 365 CE.

Prerequisites:

  1. Azure subscription allowing you to register apps, provision Azure Service Bus and Azure Function App
  2. Microsoft Dynamics 365 9.x instance with admin privileges, a trial will do

Walkthrough:

  1. Setup Azure Service Bus queue and make a note of the connection string
  2. Register an AAD App and application user to allow using Microsoft Dynamics 365 Web API. Note the Application Id  and secret
  3. Setup Azure Function App with Service Bus queue trigger
     
    1. In Azure Portal, click ‘Create a resource’
      undefined
    2. Select Function App
      undefined
    3. Search for Function App (classic) and create a new one
      undefined

      undefined
    4. Set Function details and click ‘Create’
      undefined
    5. Select the new Function and click Function app settings
      undefined
    6. Select 1 as Runtime version
      undefined
    7. Select the Functions and Click New function
      undefined
    8. Select Azure Service Bus Queue trigger with C#
      undefined
    9. Set triggers details. Under Service Bus connection click new and select details for the ASB queue you set on stage 1

      undefined
      undefined
    10. Once back in the Function code, replace existing code with the code found here.
      This code parse the incoming message, authenticates to Dynamics 365 and create a new Account record.
      Replace the ApplicationId, Secret and WebAPIURL to match the details you noted on step 2 and your organization Web API URL.
      Save the Function and make sure it compiles.

      undefined
  4. Test by posting a message with the required format to ASB queue.
    You can post messages to ASB using Postman and this useful collection
    undefined
    Make sure Azure Function processes the message
    undefined
    and that the record was created in Dynamics 365
    undefined

Post message to Azure Service Bus via Custom Workflow Activity

While Dynamics 365 Plug-ins allow declarative integration with Azure Service Bus via ServiceEndPoints, integrating through Custom Workflow Activity requires some coding.

So why would you prefer a Custom Workflow Activity over a Plug-in?
Here are just a few good reasons:

  1. Cost-efficiency: A Custom Workflow Activity component can be embedded in Custom Actions, A/Sync. Workflow, Dialog (while it’s still here), while Plug-in component can only be called and operate via the Plug-in execution context. When you put time into writing code, you want to allow as much re-use as possible.
  2. Declarative: once registered, Custom Workflow Activity can be leveraged by non-developers in various Processes, while Plug-in is usually accessible only to developers.
  3. Ubiquity: A Custom Action wrapping a Custom Workflow Activity can be accessed from other processes, client side code, server side code and consumers external to Dynamics 365
  4. Flexibility: unlike the built in declarative integration, addressing Azure Service Bus via code allows you to add conditional logic and also reduce the message payload (see component code).

Convinced or not, the following walkthrough demonstrates using the Post Message To Azure Service Bus Custom Workflow Activity component to post message to ASB queue.
You can view the source code here and modify as required.

Prerequisites:

  1. Azure subscription allowing you to provision Azure Service Bus
  2. Microsoft Dynamics 365 9.x instance with admin privileges, a trial will do
  3. Access to Plugin Registration Tool

Walkthrough:

  1. Setup Azure Service Bus and make a note of the connection string
  2. Use the Plugin Registration Tool to register a ServiceEndPoint and the noted connection string
  3. Download, import and publish the unmanaged solution available here
  4. Configure the solution contained Workflow by selecting the registered ServiceEndPoint, stating semi-column delimited properties list and an optional custom message. Activate the Workflow Rule.


  5. Test by manually activating the Workflow on a Contact record.
    If all goes well, a new message will appear in ASB queue. You can view the queue dashboard in Azure Portal.

    undefined
    If you want to view the actual message content, you can use Azure Service Bus Explorer

    undefined

Few implementation notes:

  1. While the IWorkflowContext allows accessing the target record properties via Pre/Post entity images, this technique is not documented and therefor considered unsupported.
    For this reason, the code explicitly retrieves and push the required attributes values into the SharedVariables collection.
    The propertiesSet input parameter allows sending in a list of required properties.
  2. Clearing the IWorkflowContext data heavy collections before posting it to Azure Service Bus can reduce the the message payload weight by ~80%, which can simplify handling the message and improve performance.





Implementing Lead Landing Page with Flow (in 10 min. or less)

Microsoft Flow, along with Logic Apps, Power Apps and CDS has revolutionized integration with Microsoft Dynamics 365.
I have been working with Dynamics products since 2005 and when comparing the resources required back then to hook up a landing page to Dynamics, I estimate that modern solutions require less than 5%.
In addition, you don’t have to be an expert developer to implement simple integration scenarios, as declarative mechanisms like Flow and Logic Apps can handle the heavy lifting.    

In this post I’ll walkthrough the process of Implementing a Lead Landing Page with Flow while writing the minimum amount of required code.

The ingredients:

  1. Microsoft Flow Handle Landing Page Lead triggered by HTTP request, creating a Lead record in Microsoft Dynamics 365 instance using Flow built in Dynamics Create Record Action.
  2. HTML page implementing landing page UI along with JavaScript code sending Lead data to the Handle Landing Page Lead Flow.

Prerequisites:

  1. Have access to Microsoft Dynamics 365 online instance and Flow environment residing in the same tenant

Walkthrough:

  1. Create a ‘Handle Landing Page Lead’ Flow 

    Start a Flow from scratch and create a Request trigger. Once saved,  the URL value will be filled automatically.

    Start a Flow from scratch and create a Request trigger. Once saved,  the URL value will be filled automatically.

    Next, add a Parse JSON action and paste in the following schema

    {
        “type”: “object”,
        “properties”: {
             “topic”: {
                “type”: “string”
              },
              “firstName”: {
                 “type”: “string”
              },
              “lastName”: {
                 “type”: “string”
              },
              “email”: {
                 “type”: “string”
              },
              “mobilePhone”: {
                 “type”: “string”
              }
         }
    }

    Next, add a Parse JSON action and paste in the following schema

    Next, add Dynamics Create Record action. Select your target Dynamics 365 instance and map to the Lead entity.
    Map the previous Parse JSON action output values as Lead attributes.

    Next, add Dynamics Create Record action

    Finally, add a parallel branch element. Add two HTTP Response actions, one handling Lead creation success and the other for failure.
    Both returning code 200, each returning the respective message in the response body.
    Click each response ellipsis and set the ‘Configure run after’ option to handle the
    previous step success and failure respectively. 
    Finally, save the Flow.

    Finally, add a parallel branch element

    Click each response ellipsis and set the ‘Configure run after’ option to handle the

  2. Create HTML Landing page

    Create the following HTML page. Replace the flowRequestURL variable value with Flow Request Action URL generated in the first step.

    <html>
    <head>
        <meta charset="utf-8" />
        <title>Implementing Lead Landing Page with Flow</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    </head>
    <body style="font-family:'Segoe UI';background-color:lightskyblue">
            <b>Contact us</b>
            <table>
                <tr>
                    <td><label>Topic</label></td>
                    <td><input type="text" id="topic" name="topic" placeholder="insert topic"></td>
                </tr>
                <tr>
                    <td><label>First Name</label></td>
                    <td><input type="text" id="firstName" name="firstName" placeholder="insert first name"></td>
                </tr>
                <tr>
                    <td><label>Last Name</label></td>
                    <td><input type="text" id="lastName" name="lastName" placeholder="insert last name"></td>
                </tr>
                <tr>
                    <td><label>Email Address</label></td>
                    <td><input type="text" id="email" name="email" placeholder="insert email address"></td>
                </tr>
                <tr>
                    <td><label>Mobile Phone</label></td>
                    <td><input type="text" id="mobilePhone" name="mobilePhone" placeholder="insert mobile phone number"></td>
                </tr>
                <tr>
                    <td colspan="2">
                        <input value="Send" type="button" onclick="sendRequest()">
                    </td>
                </tr>
            </table>    
        <br />
        <div id="messageArea"></div>
    
        <script type="text/javascript">
    
            // Send new Lead request to Microsoft Flow
            sendRequest = function () {
    
                //set Flow request URL - available once Flow Request step is saved 
                var flowRequestURL = "https://FLOWREQUESTURL";
    
                //set Lead object
                var leadData = {
                    topic: $("#topic").val(),
                    firstName: $("#firstName").val(),
                    lastName: $("#lastName").val(),
                    email: $("#email").val(),
                    mobilePhone: $("#mobilePhone").val()                
                };
    
                //send request
                var req = new XMLHttpRequest();
                req.open("POST", flowRequestURL, true);
                req.setRequestHeader("Accept", "application/json");
                req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
                req.onreadystatechange = function () {
                    if (this.readyState == 4) {
                        req.onreadystatechange = null;
                        //handle success
                        if (this.status == 200) {
                            $("#messageArea").text(this.response);
                        }
                        //handle failure
                        else {
                            $("#messageArea").text(this.response);
                        }
                    }
                }
                //send request
                req.send(window.JSON.stringify(leadData));
            }
        </script>
    </body>
    </html>
    

Test your landing page by submitting a new Lead. If all works as expected, it will be instantly created in your Dynamics 365 instance.

Test your landing page by submitting a new Lead

If all works as expected, it will be instantly created in your Dynamics 365 instance.

Microsoft Graph API – Assign DYN365 License to AAD User

The automated process of user provisioning becomes common in many projects. Often, the process includes components outside of Dynamics 365 such as  creating a user in Azure Active Directory, assigning plans and licenses and adding user to AAD groups. All of these can be automated using the Microsoft Graph API.

In this post, I’ll demonstrate assigning Microsoft Dynamics 365 license to an existing user with Postman. You can later convert Postman requests to JS or C# code or use in Flow and Logic Apps.

Prerequisites

  1. Have Postman application installed
  2. Have access to Office 365 and Azure environments

Walkthrough

  1. Register a new Web App/API in Azure AD with a Client Secret and copy Application Id key, callback URL and secret to Notepad.
    Make sure your app is granted the ‘Read directory data’ privilege

    Make sure your app has at least the Read directory data privilege

  2. Set a request to retrieve available SKUs

    In Postman, create a new GET request to the following address, after replacing <YOUR_DOMAIN_NAME> with your actual domain name (e.g. bikinibottom.onmicrosoft.com)

    https://graph.microsoft.com/v1.0/<YOUR_DOMAIN_NAME>.onmicrosoft.com/subscribedSkus

    In Postman, create a new GET request

  3. Get an Authorization Token

    Before actually accessing the Graph API, you’ll need an access token to authenticate your requests.
    Click the Authorization tab, and set type to OAuth2.0.
    Click the Get New Access Token and select the Authorization Code option for the Grant type attribute In the dialog opened.
    Fill in the Client ID, Client Secret and Callback URL details copied previously to Notepad.

    Fill in the Client ID, Client Secret and Callback URL details

    Click the ‘Request Token’ button to receive a dialog containing an Access Token. Scroll down and clock ‘Use Token’ button

    Click the ‘Request Token’ button to receive a dialog containing an Access Token

  4. Retrieve Subscriptions

    Click ‘Send’ to execute the request which will retrieve a list of commercial subscriptions that your organization has acquired.

    execute the request which will retrieve a list of commercial subscriptions that your organization has acquired

    If you have Dynamics 365 licenses, the following node will be included in the response.
    Copy the skuId value which will be used in the next step.

    If you have Dynamics 365 licenses, the following node is included in the response

  5. Assign License to User

    Create an additional POST request to assign license to user with the following URL. Replace the target user principal name 
    (e.g.
    sandycheeks@bikinibottom.onmicrosoft.com)

    https://graph.microsoft.com/v1.0/users/<USER_PRINCIPAL_NAME>/assignLicense

    In the Authorization tab, set the same settings as the first request. As you already have a token, no need to get a new one.
    Add a header of  Content-Type = application/json

    Add a header of  Content-Type = application/json

    Set the request body with the previously copied skuId

    Set the request body with the previously copied skuId

    Click ‘Send’ to send the request and assign Dynamics 365 license to the target user.
    A correct response looks like this

    A correct response looks like this

 

 

 

Auto Backup Dynamics 365 Solution using Flow & Dropbox

Harnessing the scaffold suggested in my last post, I would like to suggest a simple way to automatically backup Dynamics 365 solution with Flow/Logic Apps.

The following Flow will allow you to copy Dynamics 365 solution file into Dropbox account on schedule for backup or any other purpose.

You can download the scaffold Flow solution here and import into your Flow environment. Then, update the necessary details according to the following walkthrough. The last Dropbox related Action is not included, you’ll add it yourself.


Prerequisites

  1. Access to Microsoft Dynamics 365 online instance and Flow environment
  2. Register Microsoft Dynamics 365 online instance in Azure AD and have the Application Id key ready.Make sure you set the oauth2AllowImplicitFlow as described here.
  3. Have an accessible Dropbox account.

Walkthrough

Here is the full Flow collapsed Flow:

full Flow collapsed Flow

  1. Download the scaffold Flow solution here and import into your Flow environment
  2. Edit the imported Flow and set the following variables with values to match your environment:
    1. Client id & Dynamics 365 instance URL

      Client id & Dynamics 365 instance URL

    2. Set Dynamics 365 instance user name and password

      Set Dynamics 365 instance user name and password

    3. Set the target Dynamics 365 solution unique name and state the Dropbox target folder name (where the solution file will be created).
      Set false is the target solution is unmanaged, true otherwise

      Set the target Dynamics 365 solution unique name and Dropbox target folder where the solution file will be created.

    4. Add the Dropbox ‘Create File’ Action after the Parse JSON Action:
      Authenticate to your Dropbox account to allow creating the solution File in the target folder

      Authenticate to your Dropbox account to allow creating the solution File in the target folder

Once the Flow is activated, the target solution file will be created on schedule in the target Dropbox folder

Once the Flow is activated, the target solution file will be created on schedule in the target folder

Execute a Recurring Job in Microsoft Dynamics 365 with Flow

I have written about executing recurring jobs in Dynamics 365 few times in the past. Over time, I suggested different scheduling mechanisms such as Microsoft Dynamics Workflow Timeout step or Azure Scheduler, as the pattern I suggested allows changing the scheduling mechanism without impacting other solution parts.

Asynchronous Batch Process Pattern

Flow can be also used as a scheduling mechanism, one that does not require coding like Azure Function,  as it has a built in integration with Microsoft Dynamics 365 Online.
Once invoked on schedule, the executing component query Dynamics 365 for target business records and apply some business logic (Process) to each business record.

In this post I would like to demonstrate a solution for executing a recurring job in Microsoft Dynamics 365 using Flow as the scheduling component.

Sample business requirement

Weekly Leads Evaluation: once a week, disqualify all Leads that are more than 5 days old and are not rated ‘Hot’.
Add a note to each Lead record to indicate that it was disqualified by an automated process.

Walkthrough

  1. Download, Import and publish the ABP unmanaged solution.
  2. Create the Action component: Disqualify Lead Workflow

    This Workflow Rule will be applied to each valid target business record. It simply attaches a Note and changes the Lead record state to Disqualified. It can be a/synchronous, but you may want to start with a asynchronous execution to monitor the process execution.
    Make sure you define the process as an on-demand process with no automated triggers.

    Create the Action component: Disqualify Lead Workflow

  3. Define Target Business Records Query

    Using the Advanced Find, define a new query to retrieve all Lead records where Created On date is older than 5 days and are not rated ‘Hot’.
    Click to Edit Columns button and remove all possible columns to maximize the query efficiency.
    Click the Download Fetch XML button and open the resulting file with some text editor. Copy the FetchXML query text to the clipboard

    Define Target Business Records Query

  4. Create a Batch Process Record

    This record is used to orchestrate by holding a FetchXML query to define the target business records and also the Action process which will be applied on the the target business records.
    If you imported an unmanaged solution, find the Batch Process entity and set it to appear in the Settings area.
    Create a Batch Process record and paste the FetchXML query text into the Target Records textbox.
    Name the record ‘Weekly Leads Evaluation Process’.
    Select the Disqualify Lead Workflow (created in step 2) in the Process Lookup field and save the record.
    Copy the Reference Token value for the next step.

    Create a Batch Process Record

  5. Define Flow Rule

    In your tenant, navigate to the Flow area and create an empty Flow named Weekly Leads Evaluation.
    Define a Recurrence Trigger to trigger the batch process

    Define a Recurrence Trigger to trigger the batch processDefine a Recurrence Trigger to trigger the batch process

    Next, define a List records action.
    In the Filter Query text box, paste the Batch Process Reference Token value and precede it with dyn_referencetoken eq (e.g. dyn_referencetoken eq ‘LOGY8Y1W6’).

    Next, define a List records action

    Define Update record action which will automatically wrap it with Apply to each wrapper. Add a Condition before the Update Action to verify that the Flow operates only if the Batch Process record status is Scheduled. 

    Define Update record action which will automatically wrap it with Apply to each wrapper

  6. Activate

    Change the Batch Process record status to Scheduled and save.
    Activate the Flow Rule.
    You can monitor the Flow activation by navigating to the Run History area.

    You can monitor the Flow activation by navigating to the Run History area

    For each run, you can see the completion status (success/failure) and failure reason

    For each run, you can see completion status (success/failure) and failure reason

Similarly, you can now add additional scheduled batch processes. 

Implementation Details

Custom Code Tracing & Exception Logging in Dynamics 365 v.9

As a Solution Architect I often review Microsoft Dynamics 365 custom server and client side code.
One of the most common rejects regards tracing and exception handling mechanisms, or their absence. Some code constructs may have empty Try/Catch blocks or none at all, other catch exceptions and just re-throw. As for tracing, code often contains debugging ‘aids’ such as alerts and debugger statements or no tracing notifications at all.

Why is this a reject?

  • Unhandled raw exceptions float to UI, confusing and frustrating users while exposing internal implementation details to potential attackers
  • System Administrator is unaware of custom code exceptions unless users decide to report an error message or a bug
  • Tracing mechanism help diagnose faults and sometimes allow resolving without debugging or recompiling code

Microsoft Dynamics 365 contains a Plug-in Trace repository which contains trace and exception entries originating from Plug-in and Custom Workflow Activity components.
While this is a good built in solution, it has some drawbacks:

  • It does not support common types of custom code such as client side JavaScript and external components consuming Microsoft Dynamics API directly
  • There is no straight forward method to filter Plug-in Trace notification to search for specific text in trace/exception messages
  • No option to trigger a notification to system administrator once an exception is logged
  • No option to accurately correlate a user reported error message to a specific exception entry

In this post I would like to suggest an updated implementation of a more wholesome infrastructure for tracing and exceptions logging. Using this  infrastructure, any developer can easily log trace entries and exceptions from any custom code interacting with Microsoft Dynamics 365 application, while admins can easily monitor the application health in that aspect.

WIIFM

Why using this infrastructure?

  • Easily log trace entries and exceptions from any client and server side custom code interacting with Microsoft Dynamics 365 application in a unified manner
  • You can add processes triggered by exception Log record creation to alert system administrator regarding a fault as it occurs
  • Easily search and filter specific exceptions/trace records according to unique Reference Token provided by users/clients using Quick/Advanced Find

In the following How To Use section you can download a un/managed solution and follow setup and usage instructions.  You can view the GitHub project here.

How to use

  1. Download and import solution

    You can download a managed solution here.
    If you want to make any changes to this solution, download the un-managed version. In that case, open the solution, navigate to the Log entity and check ‘Settings’ to display the entity in the settings area. Finally, publish the solution.

    open the solution, navigate to the Log entity and check ‘Settings’ to display the entity in the settings area

  2. Using in an entity form JavaScript code

    Add the dyn_utils.js Web Resource to the target entity form libraries collection.
    Call the LogTrace and LogException functions in your custom code.

    Add the dyn_utils.js Web Resource to the target entity form Libraries collection

        //perform some business logic including tracing and exception handling 
        ns.DoSomeBusinessLogic = function () {
            //define verbose trace entry
            var traceEntry = {
                //title
                "title": "loggingSample.DoSomeBusinessLogic",
                //description
                "description": "Start execution",
                //related business record id 
                "relatedBusinessRecordId": "2514FC63-9E58-4D1E-8226-69256D0197E3",
                //related business record URL
                "relatedBusinessRecordURL": "https://xxx.crm.dynamics.com/main.aspx?etc=8&extraqs=&histKey=520872127&id=%7b2514FC63-9E58-4D1E-8226-69256D0197E3%7d&newWindow=true&pagetype=entityrecord#923042",
                //set current user id as related user id 
                "relatedUserId": getContext().userSettings.userId
            };
    
            //trace function execution start asynchronously
            Utils.LogTrace(traceEntry, true, SuccessHandler, FailureHandler);
    
            try {
                //some business logic including some exception 
                NonExistingFunctionCall();
            }
            catch (err) {
                //define verbose exception entry
                var exceptionEntry = {
                    //title
                    "title": "loggingSample.DoSomeBusinessLogic",
                    //description
                    "description": Constants.MSG_GENERAL_FAILURE,
                    //related business record id 
                    "errorMessage": err.message,
                    //related business record URL
                    "stackTrace": err.stack
                };
    
                //log exception and return reference token
                var referenceToken = Utils.LogException(exceptionEntry, false);
    
                //notify user with a reference token to report back to admin
                alert(Constants.MSG_GENERAL_FAILURE + referenceToken);
            }
            finally {
                //define thin trace entry 
                var traceEntry = {
                    //title
                    "title": "loggingSample.DoSomeBusinessLogic",
                    //description
                    "description": "Start execution",
                };
    
                //trace function exectuion end asynchronously
                Utils.LogTrace(traceEntry, true, SuccessHandler, FailureHandler);    
            }
        }
    
  3. Using in Web Resource JavaScript code

    You can view the code in action by launching the dyn_loggingSample.htm Web Resource (replace organization base address)
    https://YOUR_ORG_NAME.crm.dynamics.com//WebResources/dyn_loggingSample.htm

    You can view the code in action by launching the dyn_loggingSample.htm Web

    Make sure you include the dyn_utils.js as a referenced resource:

    
    
        
        Logging Sample
        
        
        
        
    
    

    The sample code in the dyn_loggingSample.js demonstrates tracing and exception handling, same as in the code sample above.

  4. Using in Plug-in/Custom Workflow Activity/external SDK client

    The following code sample represents usage in Plug-in and is similar to CWA and external SDK client usage:

        public class LogTraceAndException : IPlugin
        {
            public void Execute(IServiceProvider serviceProvider)
            {
                // Obtain the execution context from the service provider
                IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
                // Get a reference to the organization service
                IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                IOrganizationService organizationService = factory.CreateOrganizationService(context.UserId);
    
                //define a trace entry, all attributes are optional except title 
                Log traceEntry1 = new Log
                {
                    title = "ServerSideLogging: Execute",
                    description = "Execution end",
                    //replace user id with an valid one
                    relatedUser = new EntityReference("systemuser", 
                        Guid.Parse("37ACCC73-0FCE-474B-A206-19C08163438B")),
                    relatedBusinessRecordURL = "https://YOURORGNAME.crm.dynamics.com/main.aspx?etc=1&extraqs=&histKey=87055256&id=%7b381FA757-60D7-E811-A97E-000D3AB20035%7d",
                };
    
                //log trace entry, get reference token back 
                string refToken1 = GenericLogging.Log(traceEntry1, 
                    GenericLogging.LogType.Trace, organizationService);
    
                try
                {
                    //some exception raising code.
                    //If transaction is rolled back, Log entries will persist
                    throw new Exception("An error occurred, please notify the System Administrator");
                }
                catch (Exception ex)
                {
                    //define an exception entry 
                    Log excptionEntry = new Log
                    {
                        title = "ServerSideLogging: Execute",
                        errorMessage = ex.Message,
                        stackTrace = ex.StackTrace
                    };
    
                    //log exception entry, get reference token back
                    string refToken2 = GenericLogging.Log(excptionEntry,
                        GenericLogging.LogType.Exception,
                        organizationService);
    
                    throw new InvalidPluginExecutionException(
                        string.Format("An error occurred, please notify the System Administrator. Reference token: {0}", 
                        refToken2));
                }
            }
        }
    

    A resulting exception raised from the Plug-in code includes the reference token. Once reported to an Admin, he can easily find the relevant Log entry using Quick Find

    A resulting exception includes the reference token

    Once reported to an Admin, he can easily find the relevant Log entry using Quick Find

    View Log details

Implementation Notes

I have written about the subject in the past and the described approach has not changed much. The implementation has been updated to leverage version 9 features (currently available on for Online implementations)

What has changed since last version:

  • Using Action definition to activate, deactivate and control logging level
  • Using built-in auto-numbering attribute to generate reference token, so a designated CWA component is no longer required
  • Optional usage of client side asynchronous logging operation to keep UX uninterrupted

In order to support logging from transactional components (such as Plugin registered to pre/post operation stages), the Log method in GenericLogging.cs file is using the ExecuteMultipleRequest class to execute the dyn_Log request.
As the ExecuteMultipleRequest instance is external to the Plugin transaction, it manages to create a Log record without the Plugin transaction rolling it back.
Using the Execute method directly with the dyn_LogRequest request instead, would have executed the Log Action, but the created Exception record would have been deleted by the Plugin transaction rollback, leaving no trace.

Custom Trace & Exception Logging diagram

 

Drag & Drop File Upload Revisited

Finally found the time for overdue maintenance on the Drag & Drop solution I created two years ago.

Why revisit?

First of all, as the CodePlex platform, previous home of this solution, is being decommissioned, downloading the component got the whole CodePlex project and few visitors commented that they could not find the actual solution.
So now you can download an unmanaged solution from it’s  new home @ Github.

Second, I fixed a major bug related to the plural name of some entities.
For most entities, appending ‘s’ to the entity schema name would result in the matching entity name for Web API. Some entities, like opportunity, do not match this pattern.

Third, I wanted to update the Web API version to 9.0 and to make sure the solution works with Dynamics 365 Online current version.

Drag and Drop demo

Microsoft Portal – Retrieve Dynamics 365 Data Asynchronously – Part 2

In part 1 of this post I demonstrated building and using a service which receives a FetchXML query and returns Dynamics 365 data to any Portal page in an asynchronous manner as a JSON object. This service is similar to the SDK’s RetrieveMultiple message.
In this part 2, I’ll demonstrate a different service, which like the SDK Retrieve message, receives a record type, record id (GUID) and columns set to return the required data as a JSON object.
This is useful when you already have a specific Dynamics 365 record id at hand and you want to retrieve additional data for this record.

  1. Create a Web Template  named RetrieveService

    Set the Mime Type is to application/json.
    create web template

    Paste the following code in to the web template source and save.

    {% comment %} test for required parameters {% endcomment %}
    {% if request.params[‘entityname’] and request.params[‘id’] and request.params[‘columnset’] %}

    {% capture msg_no_data %}No data for specified attribute {% endcapture %}

    {% comment %} extract parameters {% endcomment %}
    {% comment %} extract target entity {% endcomment %}
    {% assign entityname = request.params[‘entityname’] %}
    {% comment %} extract target record id {% endcomment %}
    {% assign id = request.params[‘id’] %}
    {% comment %} parse requested attributes into array {% endcomment %}
    {% assign columnset = request.params[‘columnset’] | split: “,” %}

    {% comment %} query for target entity  {% endcomment %}
    {% assign item = entities[request.params[‘entityname’]][request.params[‘id’]] %} 

    {% comment %} Emit JSON response {% endcomment %}
        {% if item %}{
            {% comment %} Iterate throguh requestd attributes array {% endcomment %}
            {% for att in columnset %}
                {% comment %} Handle optionset attribute {% endcomment %}
                {% if item[att].label %}”{{ att }}”:”{{ item[att].label | default: msg_no_data }}”
                {% comment %} Handle lookup attribute {% endcomment %}
                {% elseif item[att].name %}”{{ att }}”:{“name”:”{{ item[att].name | default: msg_no_data }}”,”id”:”{{ item[att].id | default: msg_no_data }}”}                           
                {% comment %} Handle other attributes {% endcomment %}
                {% else %}”{{ att }}”:”{{ item[att] | default: msg_no_data }}”
                {% endif %}{% unless forloop.last %},{% endunless %}
            {% endfor -%}                   
        }   
        {% endif %}

    {% comment %} handle missing parameters {% endcomment %}
    {% else %}
      { “error”:”Missing one or more required parameter(s): entityname, id, columnset” }
    {% endif %}

  2. Create a Page Template named RetrieveService
    Map the Page Template to the Web Template created on step 1 above.
    Uncheck the Header and Footer checkbox.

    image

  3. Create a Portal Page named RetrieveService
    Set Home as parent page.
    Map the page to the Page Template created on step 2 and copy the partial URL which is used in the next step

    create a portal page

  4. Consume Service

    Use the following code sample anywhere you need asynchronous data retrieval of from Dynamics 365. In my example it is located in the Home page JavaScript portion.
    Note the URL address which maps to the Page (created above) URL.
    Replace the entityname, id and columnset parameters.
    Most important: make sure your users have the required Entity Permissions for the entities queried, otherwise they will receive empty results.

    $(function (){
      //define asynchronous request from any portal page
        $.ajax({
            method: “POST”,       
            url: “/retrieveservice/”,
            data: {
                //define entity name, id and columnset 
              entityname: ‘incident‘,
              id: ‘D2A697CD-D3C7-E811-A965-000D3AB0F1D7‘,
              columnset: ‘ticketnumber,title,createdon,incidentid,statecode,caseorigincode,customerid,ownerid‘  
            }
        }).done(function (msg){
            console.log(msg);
        }).fail(function (jqXHR, textStatus, errorThrown){
            console.log(textStatus);
        });
    });

    As the sample code logs the resulting JSON object to the console, open the browser developers tool (F12) to view it.

    Resulting JSON object