Archive
[Dynamics CE] – Strange Plug-in issue due to the usage of global variables
Other day, one of my colleagues reported a plug-in issue which could not be reproduced consistently. All the troubleshooting options (i.e., Plug-in Profiler, Tracing) had been used but was unable to found the root cause.
On close retrospection of code, we found out the usage global variables in Plug-in class file, which made the Plug-in ‘Stateful’.
Due to this, issue could not be reproduced when tested with Plug-in Profiler as it creates single Plug-in instance. Issue will only be reproduced when concurrent users instantiate plug-ins.
You have to make sure to create your Plug-in as ‘Stateless’ to avoid issues during concurrent Plug-in execution.
What is ‘Stateful’ Plug-in?
- If the Plug-in ‘Execute’ method refer any global variable(s), makes the Plug-in ‘Stateful’.
public class CustomersVirtual : IPlugin{
// ‘context’ variable declared as Global
IPluginExecutionContext context = null;
EntityCollection results = new EntityCollection();
public void Execute(IServiceProvider serviceProvider) {
context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));if (context.MessageName.ToLower() == “retrieve”){
Entity customer = new Entity(“raj_virtualcustomer”);
customer[“raj_customersid”] = Guid.NewGuid();
customer[“raj_name”] = “ABC Corporation”;//add it to the collection
results.Entities.Add(customer);
}
In the above Plug-in sample, variables ‘context’ and ‘results’ been defined as global and referred in ‘Execute’ method which makes it ‘Stateful’
What is ‘Stateless’ Plug-in:
- If the Plug-in ‘Execute’ method, has its own local variables and does not refer any global variables make the Plug-in ‘Stateless’.
public class CustomersVirtual : IPlugin{
public void Execute(IServiceProvider serviceProvider){// ‘context’ variable declared as Local variable
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));EntityCollection results = new EntityCollection();
if (context.MessageName.ToLower() == “retrieve”)
{
Entity customer = new Entity(“raj_virtualcustomer”);
customer[“raj_customersid”] = Guid.NewGuid();
customer[“raj_name”] = “ABC Corporation”;//add it to the collection
results.Entities.Add(customer);
}
In this sample, variables ‘context’ and ‘results’ been defined as Local in ‘Execute’ method which makes it ‘Stateless’
Why ‘Stateless’ Plug-in is recommended?
- As per the design, Dynamics platform caches plug-in class instances and hence the constructor is not called for every invocation of plug-in execution.
- IPlugins should be stateless is that multiple system threads could execute the same, shared, plug-in instance concurrently.
- This opens up members of classes that implement IPlugin to potential thread-safety issues which could lead to data inconsistency or performance problems.
- Read-only, static, and constant members are inherently thread-safe and can also be used reliably within a plug-in class.
How the Dynamics platform Caches Plug-in?
- For performance reasons, Dynamics DOES NOT dispose of the object after it completes execution.
- Dynamics caches the object and calls Execute on the same object instance on the next activation of the plug-in.
- Certain operations, such as changing a plug-in’s registration properties, will trigger a notification to the platform to refresh the cache. In these scenarios, the plug-in will be reinitialized..
- This means that you need to be very careful when defining class-level variables in the plug-in class as multiple threads can execute the code at one time
Bottom line:
- Don’t use Global variables in Plug-ins
Refer below articles for more insights:
🙂
[Logic App and HTTP Request Trigger] Create a record in D365
We got an ask from client that they should be able to Insert contacts in D365 from their Java application and requested our help.
To fulfill the ask, one option would be configuring S2S authentication so that Java application can connect to D365 using ADAL library and insert contacts.
Another option is using “Logic App” with “Http Request Trigger”.
High level approach:
- Create a Logic App with ‘When a HTTP request is received’ trigger
- Pass Contact record information in Json format as Request
- Use ObjGen site to generate desired Json data.
- This site seamlessly generates Json data based on our input. As I am going to create a Contact record, I generated below JSon data.
- From Logic App, read the Json data and create Contact record using D365 connector
- Finally capture the created Contact GUID in ‘Response’
Lets see the step by step approach to create Logic App and test.
Pre-requisites:
- Dynamics 365 instance. Subscribe for trail if you have’t already
- Azure Subscription.Subscribe for trail if you have’t already
Once you got the D365 and Azure Portal subscriptions below are the steps to create LogicApp.
Create a Logic App:
- Connect to your Azure Portal and create a new ‘Logic Apps’. Refer article for steps to create Logic App.
- From the list select ‘When a HTTP request is received’ trigger
- In the next screen, click on “Use sample payload to generate schema” to generate the Json schema.
- Paste the Json format prepared in ObjGen site
- Click on ‘Done’ so that LogicApp generates “Request Body jSON Schema”
- Next, we need to connect to D365 to save the Contact
- Click ‘New Step’ and select ‘Dynamics 365’ connector. From the ‘Actions’ select ‘Create a new record’ and provide credentials and connect to your Instance
- From the ‘Entity Name’, select ‘Contacts’
- Map the Contact entity fields with the fields from ‘Dynamic content’ popup.
- As ‘Company Name’ is a Look up to ‘Account’ entity, make sure you map
- ‘Company Name Type’ = ‘accounts’
- ‘Company Name’ = ‘id’ passed from Json
- As ‘Company Name’ is a Look up to ‘Account’ entity, make sure you map
- Next we need to capture the ‘Response’, choose ‘Response’ from ‘Actions’
- In the ‘Response’ control,
- In the ‘Headers’, add ‘Content-Type’ as key and ‘application/json’ as Value.
- In the ‘Body’, form your output string and ‘Contact’ field from ‘Dynamic Content’ to capture the GUID
- Save the Logic App
Test the Logic App:
- Copy the “HTTP POST URL” from the “When a HTTP request is received” control.
Using Post Man:
- Create a ‘POST’ request and paste the URL copied from Logic APP
- In the ‘Headers’, add ‘Content-Type’ as key and ‘application/json’ as Value.
- In the ‘Body’, paste the Json (You can copy the Json format prepared in ObjGen site)
- Click ‘Send’ to call Logic App and capture the Response.
Using jScript and HTML:
- Below the jScript to call the Logic APP URL by passing JSon and capture the response
<html lang=”en” xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<meta charset=”utf-8″ />
<title>Test Logic App</title>function TestLogicApp() {
try {
var xhr = new XMLHttpRequest();
var url = “Logic App URL“;
xhr.open(“POST”, url, true);
xhr.setRequestHeader(“Content-Type”, “application/json”);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
alert(xhr.responseText);
}
};
var data = JSON.stringify({
“firstname”: “Rajeev”,
“lastname”: “Pentyala”,
“Account”: {
“id”: “A16B3F4B-1BE7-E611-8101-E0071B6AF231”,
“Name”: “A Datum Corporation”
}
});
xhr.send(data);
} catch (e) {
alert(“Error – ” + e.description);
}
}</head>
<body>
<input type=”button” value=”Post” onclick=”TestLogicApp()” />
</body>
</html>
- Open the HTML page and click the ‘Post’ button to capture the Response returned from LogicApp
Troubleshoot and track the history:
- To troubleshoot and track the Requests, from the ‘Logic App’, click on ‘Overview’ and check under ‘Run history’
🙂
[MS Word] The Linked file isn’t available
Other day, I was getting below “The Linked file isn’t available” pop-up, while opening a word document shared by client.
Reason:
- Word document has charts and graphs controls embedded, which were rendering based on the data from another excel sheet.
- Excel sheet had configured as a linked file in the Word document
- Since Excel sheet was not shared by client in the first go, Word document was showing “The Linked file isn’t available” popup.
Fix:
- Once you got the link file (i.e., Excel sheet in my case), below are the steps to fix, if you are still getting the popup.
- From Word document, click on File -> Info -> Related Documents -> Edit Links to Files
- In the ‘Links’ window, either click on ‘Change Source’ to correct the ‘Link file’ path or click ‘Break Link’ button if you want to remove the files link.
- Once the file paths are corrected, click on ‘Update Now’ to reflect the changes in Word document
- Now, Word document should open with no “The Linked file isn’t available” pop-up.
🙂
[Log4net] Exception while reading ConfigurationSettings
We were using Log4net logging framework to log exceptions in our web application and encountered below exception during the log:
Reasons:
There can be many reasons right from misplacing Log4net config settings to not loading the config file.
Fix:
Below is the step by step approach on how to use Log4net framework.
Install Log4net NuGet package:
- From Visual Studio, open Tools -> NuGet Package Manager -> Manage NuGet packages for solution…
- Search for ‘Log4net’ and install the package
Add configurations in App.config/Web.config:
- In your App.config/Web.config file, add a new config section named ‘log4net’. Make sure <configSections> is the first node under root <configuration> node.
- Add <log4net> node with details like Path where you would like to store the logs and other properties.
- Your App.config/Web.config file looks as below:
<configuration>
<configSections>
<section name=”log4net” type=”log4net.Config.Log4NetConfigurationSectionHandler,Log4net”/>
</configSections>
<log4net>
<appender name=”General” type=”log4net.Appender.RollingFileAppender”>
<file value=”D:\log.txt“/> <!–Physical path to log files–>
<appendToFile value=”true”/>
<rollingStyle value=”Size”/>
<!– Specifies the number of files that will be written. This is an example for ten files. The files will be named engine.log.1, engine.log.2, engine.log.3, etc. –>
<maxSizeRollBackups value=”50″/>
<!– The File Size limit for each file. KB stands for Kilobytes, MB (MegaByte) is also an option. –>
<maximumFileSize value=”5MB”/>
<staticLogFileName value=”true”/>
<layout type=”log4net.Layout.PatternLayout”>
<conversionPattern value=”%date %-5level – %message%newline”/>
</layout>
</appender>
<root>
<appender-ref ref=”General”/>
</root>
</log4net>
<startup>
<supportedRuntime version=”v4.0″ sku=”.NETFramework,Version=v4.6.1″ />
</startup>
</configuration>
Using Log4net in your code:
- We need to load the Log4net configurations defined App.config file in class file using below statement
- Below statement can be either placed in your Class file or project’s “AssemblyInfo.cs” file
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
- Define LogManager Object using below statement
private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
- Use Log.Info(“”) to log info and Log.Error(“”) to log exception details.
- With above specified statements your class file looks as below
using log4net;
// Load Log4net configurations defined App.config file
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace Log4Net{
class Program{
// Define LogManager Object
private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);static void Main(string[] args){
try{
Log.Info(“Log Starts”);
// Throw error
throw new Exception(“Exception!!!!”);
}
catch (Exception ex){
Log.Error(“Error – ” + ex.Message);
}
finally{
Log.Info(“Log Ends”);
Console.ReadLine();
}
}}
- Execute the logic and you should get a log file stored in specified location with logs
Refer article for Log4net best practices.
🙂
[D365 CE] Migrate data to field with “Time Zone” format
Have you ever worked with “Decimal” field with “Time Zone” Format?
Lets assume your Dynamics application has a global presence and you need to capture “Customer” info along with their “Time Zone”, we can create a Decimal field with “Format” set as “Time Zone”.
With “Time Zone” format, even though, you create a field of “Decimal” type, in UI it will render as drop down and in ‘Advanced Find’ you get a string.
Lets see how to migrate data to the “Time Zone” format field.
Whats “Time Zone” format:
As you would have noticed in above screens, “Decimal” field rendered as Drop down with all time zone options. Now the question is where we are getting these option?
The answer is, Dynamics has an internal entity named “Timezonedefinition” which pulls the time zone options from.
“Timezonedefinition” entity will have below 2 fields
- Standardname – Name of the Time Zone
- Timezonecode – Time zone code which is a decimal value
When you add a ‘Decimal’ field with ‘Time Zone’ format to an entity, in UI (i.e., Forms, Advanced Find, Views, etc.) you would get the ‘Standardname’ (i.e., Time zone name) however in the back end ‘Timezonecode’ gets saved.
To check that, export the records to Excel and you would find decimal values in “Timezone” field.
As you notice above, “(GMT-10:00) Hawaii” field rendered as 2 in exported excel, which “Timezonecode”
How to migrate data to “Time Zone” format field:
- First you need to fetch the “Time zone codes” using “RetrieveMultiple” SDK call (Sample code below)
var request = new RetrieveMultipleRequest{
Query = new QueryExpression(“timezonedefinition”){
ColumnSet = new ColumnSet(“timezonecode”, “standardname”),
}
};var response = (RetrieveMultipleResponse)_service.Execute(request);
foreach (var item in response.EntityCollection.Entities){
var timeZone = (Entity)item;Console.WriteLine(String.Concat(“Time zone name: “, timeZone[“standardname“], “, time zone code: “, timeZone[“timezonecode“]));
}
- Executing above code would fetch “Timezone Names” along with “Timezonecode”
- Once you have the codes, you can migrate the records by setting the “Time zone codes” to “Time zone format” field.
- In the excel file, add new records with “Timezonecode”
- After the successful excel import you should see new records in UI with “Time Zone Name” replaced with “Time zone code” passed from excel.
🙂
[D365 CE Online] Debug Async Plug-in using “Persist to Entity” profiler option
As you are aware, using Plug-in Profiler option we can debug D365 online plug-ins by throwing exception and capturing the Serilaized profile. Refer my previous article
With the ‘Exception’ profiling option, you first need to trigger the event and capture the exception log. For example, if you need to debug “Post Account Create” plug-in, you first need to trigger the event (i.e., Create Account from D365 application) and capture the exception log. This ‘Exception’ option would be apt, if your plug-in registered in Sync mode.
What if your plug-in is Async? Answer is, using “Persist to Entity” profiler option.
Let’s see how to debug Async Plug-in step by step.
Configuring Profiler in Plugin Registration Tool:
- Connect to your Dynamics instance using Plug-in registration tool.
- Click on ‘Install Profiler’ button
- Register a new plug-in assembly and add an Async step
- In this article, I registered an Async step on Post Account Creation
- Select the Plug-in step and click on ‘Start Profiling’
- In the “Profiler Settings” screen, choose “Persist to Entity” option and click “OK”
Trigger Account Creation from D365 Application:
- Connect to D365 application and create a new Account
- As we opted for “Persist to Entity” profiler option, an entry will get created in “Settings -> Plug-in Profiles” entity.
- Go to “Settings -> Plug-in Profiles”, open the newly created “Plug-in Profile” record (Note: Async plug-in takes time to create a new “Plug-in Profile” record).
- In the “Plug-in Profile” record, you would notice the Serialized content in “Serialized Profile” field. This is same as what you get in Exception download log if you would have opted for “Exception” profile setting.
Debug the Plug-in:
Once an entry got created in “Plug-in Profile” record
- Open the Plug-in class file in Visual Studio and attach “Plug-in Registration Tool” process
- Add a break point in your Plug-in class file to debug.
- Open the “Plugin Registration Tool”
- Select the Plug-in step and click on “Debug/REPLAY PLUG-IN EXECUTION” button
- In the “Replay Plug-in Execution” screen, set
- Profile = Click on ‘Down Arrow’ button to pick your “Plug-in Profile” record from D365 (Refer screen)
- Assembly Location = Plug-in dll
- Plugin = Class Name
- Click on “Start Execution”
- You should see a break point hit in Visual Studio.
- Press F10 to proceed with debugging.
🙂
[D365 CE] – Error while registering the plug-in assembly
Other day I got an error while registering a new Plug-in assembly
Reason:
- Its Plug-in assembly .Net Framework version issue.
- My plug-in class library defaulted to 4.6.1 version where as Dynamics 365 requires version 4.5.2 of the .NET Framework for plugin assemblies
Fix:
- Its a simple fix, open your Plug-in ‘Class library’ project in Visual Studio
- Go to Properties and change the .Net Framework to 4.5.2
- Save and Build solution
- Try registering the plug-in and it should work now.
🙂