The basic - understanding Dynamics 365 CE plugins

A dynamics 365 plugin is a piece of code, written in a .NET language, typically C#, ( other options like VB are available, but when using the Kipon.Solid framework, C# is the only supported option.), that in most cases hooks into the persistancy flow when operating against an entity in Dynamics 365. A plugin is by definition a "server-side" thing. It does not have any UI. It only relates to the the UI indirectly, if the UI is triggering an operation, ex. by updating an entity, that the plugin is listening for. The plugin actually does not know if it was a UI, or a tool, or an integration service that actually triggered the operation. Ít is just listening for events, and operates accordingly. In most cases events are traditional CRUD (Create,Read,Update,Delete) operations against a specific record for a specific entity, but more excotic scenarios like actions are supported as well.

Lets jump right into the code


using System;
using Microsoft.Xrm.Sdk;

namespace Kipon.PluginExample.Plugins
{
    public class BasicConcept : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            var trace = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            var organizationService = factory.CreateOrganizationService(context.UserId);
            var target = (Entity)context.InputParameters["Target"];
            var message = context.MessageName;
            var stage = context.Stage;

            // You are ready
            // in below examples, code are assumed being place here.
        }
    }
}

Above code demonstrates the very basic of a Dynamics 365 plugin. You create a class that implementes the IPlugin interface. In the plugin, you extract the context from the serviceProvider given to the execute method, creates a factory, so you can access data in the database, extract the target record from the input parameters, and - for visualization only, assign the message and stage we are in, just to make it visible that this information is stored in the plugin execution context.


There are some very important pitfalls you should be aware of. Even though this looks like a normal .NET class, there are circumstances you must be aware of, when building plugins. First of all, you just implement IPlugin interface on a class. You do not create the instance of the plugin your self. That is NOT part of the api. The Dynamics 365 is creating the instance on a need base, and plugin instances are reused, and not recycled or garbage-collected for each invocation of the Execute method. So by design you should never add properties or fields to a plugin. Values on properties and fields will be shared cross executions, and you can get unpredicted results if you rely on values in a property or field.

Fields and properties in the class that implements IPlugin is a NO-GO.

From my experience there will be only one execution on a plugin instance at a time, but Dynamics 365 might create several instance of the same plugin for loadbalancing.

Secondly, plugins are executed in its own process, and one part of the pipeline might be executed in one instance of the plugin execution engine, while another part of the process will be executed in a completly different process. This design has been put in place for scalability and performance, but it also increase the complexity. This means you cannot share informations between plugin steps, the normal way, ex. by assigning a value to a service in pre stage, and then extract that value in post stage. Each stage are completly state-less, and any share of information in the flow should be done over the entity model, or in rare cases you can share information over a Shared variable in the execution context.

Information should be shared between plugins only over the entity model, or the context shared variables.

Entities - the data model of Dynamics 365 CE

Most plugins is listening for an event related to a specific entity. The entity is the very basic building block of the platform. Whenever an operation is done against an entity, this will fire a message to plugins that has been setup to listen for the specific operation. For Create, Retreive, Update, Delete, the design of Dynamics 365 CE enforce single record operations, so whenever a plugin i triggered due to a client invoking one of these operations, the method call will have a target representing a single record in a single entity. When it comes to RetrieveMultiple, the message is still bounded to a specific entity, and it is always the root entity (or first entity) defined in a query, but it does not have a target that defines the one and only record relevant for this plugin execution. As such, Create, Retrieve, Update and Delete are plugins bounded to a single record on a single entity, while RetrieveMultiple is unbounded - it does not have a single target instance or reference. In a plugin, the operation performed is called the Message. By extracting the message on the running plugin context in the plugin, you will know what was triggering the plugin. While Create, Retrieve, Update and Delete are the most common messages, the system supportes other messages, and you can even define you own. The later is called Actions and allow you to define a set of input and output parameters of relevance for the plugin. Actions as well can be bounded and unbounded. A bounded action always have a target reference to the entity row of relevance, while an unbounded action does not have a specific target reference in the InputParameters.

Code examples in this article has been created with the purpose of understanding the basic nature of plugins. They have NOT been created with the purpose of copy-paste. Methods and patterns has been used for simplicity. If you go for the Kipon.Solid plugin method, you wish to work SOLID, with explicit strongly typed code, and maximize the use of compile-time-check and typesafety. So read the code on this page for conceptual understanding only.

Events

Whenever an operation is done against a single record in a single entity, it will trigger an event pipeline. Each event in the pipeline represent different stages in the persistancy process. When create plugins for the Dynamics 365 platform, there are basically 4 stages of relevance. Validate, Pre, Post and Post Async.

Validate - (10)

The purpose of the validate event is to allow you to do some basic guards on your data. You can validate the information in the record, and if it does not live up to your standard, (ex. mandatory fields are missing, formats are wrong etc.), you can throw an exception, and thereby prevent the event to parse the data through to the database.

The validate event is from a conceptual point of view, executed before the transaction is started. Conceptual because that is how you should consider the transaction state in the validate stage. In real life, the transaction might actually have been started for a plugin, even when running in the validate stage. That will be the case, ex. if one plugin is triggering an operation in pre, post or post async stage, witch then trigger this plugin in validate stage. In other words, if a plugin is the root cause of a pipeline, and it executes in validate state, then the transaction has not been started, while if the event is derived from another plugin that is in a stage where transaction has been started, then our plugin in validate state will be executed within that transaction that has been already been started. If out plugin execute operations against the organization that require a transaction, ex. Create, Update or Delete, then that will start the transaction, and the rest of the pipeline will continue in that transaction.

For doing triviel validations, it is not that important wether a transaction has been started or not, but if you do other kind of operations, ex. generate unqiue numbers or similar, then it is critical that you know that the transaction has been started, and appropriate locks are done on a database level, according to your requirement.

In validate state you should do initial validation of the incomming data, and throw exceptions if not conform, and you can populate system maintained redundancy that does not require a transaction to be populated correctly.

Needless to say, that validate is done BEFORE any data regarding the target of the plugin hits the database.


if (!target.Attributes.ContainsKey(nameof(Entities.Account.Name).ToLower()))
    throw new InvalidPluginExecutionException("Accounts cannot be created without a name");

A few operations must be done in Validate stage to work. If you wish to have code that assign the unique id of a newly created entity, you must hook into the validate state of the create event, and override the id of the entity. It is considered best pratcise to let Dynamics 365 assign id's to entities, but in some cases i can make sense to have code doing that part. In such case you can ether let the client assign the id explicitly or you can override the value of the id in Validate stage. If you override the id of an entity in pre or post stage it will have no impact.

Overwrite createdon, createdby. In come cases you wish to override the createdon timestamp. For this to work, it must be done in validate state of a create message. Overriding createdon or createdby in any other state or message will not work.

Pre - (20)

The pre stage event is, as the name indicates, triggered BEFORE any data regarding the target of the plugin hits the database. In prestage the transaction HAS been started. This means that any fetch, create/update/delete operation done against the database in this stage, is done within that transaction. In pre stage you typically assign values to fields that are system maintained, ex. a unique number for the row, or you can even manipulate fields in the entity, that has already a value assigned by the client. As an example, you could enforce uppercase letter for first letter in names, or enforce specific pattern on ways to set telephone numbers and more. Any modificiation to a field in pre stage will override the value given by the client, so the value ending up in the database is the result of your manipulation.

Needless to say, that pre is done BEFORE any data regarding the target of the plugin hits the database.


target["my_uniquenumber"] = System.DateTime.Now.Ticks.ToString();

Above method is NOT a safe way to generate a unique number for a record. The code is just to demonstrate that you can assign values in the target entity in this stage, that will make it to the database, alongside other values assigned to the target by the client invoking the process.

Overwrite modifiedon, modifiedby, or silent update.

In some cases you wish to update a record silent, or even set a different value for the modifiedon and/or modifiedby

Silent update can be accomplished by removing "modifiedon" and "modifiedby" from the target entity in pre stage. If you wish to set a different value, it is also done in pre-stage, first by removing the current values, secondly by adding the values you wish to put in place. Be aware that this technice only works on update event. Ex. if you trigger a SetState event on an entity, the modifiedon and modifiedby will be assigned, and there is no way you can do that silent.

Post - (40)

The post stage event is, as the name indicates, triggered AFTER the entity data has been put into the database. In post-stage the transaction HAS been started, and is still OPEN. This means that any fetch, create/update/delete operation done against the database in this stage, is done within the transaction. In post stage you typically execute derivative events, ex. updating total amount on a header record, every time a line record, related to the header has been created/updated or deleted.

Never update the target record it self. If you need to manipulate with the target record, do it in PRE stage, so your calculation of values in the record it self, is persisted together with the primary flow. Updating the record it self can cause endless loops, and will have negative impact on performance. So it is a DON´T DO thingy.

Remember, any manipulation of the target record in POST stage WILL NOT make it to the database. You should consider the target record READONLY when you are in post stage.

POST: In post stage you perform derived operations, ex. update a related header whenever a line is created, updated or deleted.

if (target.ko_amount != null && target.ko_amount.Value != 0M && target.ko_headerid != null)
{
    var header = new Entities.ko_header { ko_headerId = target.ko_headerid.Id };
    header.ko_amount = target.ko_amount;
    header.ko_accumulate = true;
    organizationService.Update(header);
}

Above code i a conceptual view of what you can/should do in a post event. The example introduces a number of concepts that are required, ex. creating an instance of another entity within a plugin, and call the organizationService to update that record. The basic idea on above pattern is also to let the header entity it self handle the need for accumulation, by setting a variable "ko_accumulate" to indicate that in this update, a plugin on the header record must take the amount update in this transaction, and add it to whatever value is already in the database. That part of the code will be in its own plugin, and is omitted in this example.

Post - (40) - async

When defining a plugin to run post, you have the option to define it to run async. This will basically push the operation into a background process for "later execution".

Plugins running POST async will start a new transaction. This means that things you do in this workflow will not be visible to the client right away.

Before you define your plugin to run async, you should consider your business requirement, and validate that it is ok that the code running in the async workflow is NOT executed in the same transaction as the primary flow. For each async workflow, you should also consider an error handling strategy. Workflows fails, and if a plugin running async is failing, you end up in a situation where the primary part of the transaction is done, while the derived part, running async is undone. If the derived event is critical for data-consistancy, you should make sure you have a strategy for handling async event that did not make it. It is NOT always possible to restart and async workflow that has stopped due to error at the runtime.

On top of that, there is no guarantee on the order of execution, when it comes to async workflows. When things are running within a transaction, you can easily predict the order of things. That is not the case for async workflows. They are put into a queue, and executed by a background process, and there is no way you can predict the order of execution. If a job fails, it will end up in the process error queue, and in some cases you can rerun the process, meaning that it will run hours or days later than the original event.

Async is in general good for performance - because it off-loads this process executed by the client, in some situations - it can be bad for data consistancy.

Parameters

Plugins needs information to execute. Below we take a dive into how parameters are parsed around in the plugin stack.

Input

Plugin require input parameters, and these are parsed to the plugin over the context in the InputParameters indexed property of the context


var target = (Entity)context.InputParameters["Target"];

Above code snipper shows the basic concept. Where context is an instance of IPluginExecutionContext, we can extract the entity that is actually being created or updated with this method. Depending on the message (operation), the input parameter Target will have different flavor. For Create and Update, you get and instance of type Entity, while for Delete or any bounded action, you get an instance of EntityReference, only holding the logical name of the entity and the unique id of the instance.

Unbounded messages does not have a Target input parameters, MultiRetrieve as an example comming into the top of my mind.

Such messages have other parameters, specific for the message. The MultiRetrieve ex. has a paramter "Query" that in pre stage defines the query to be executed.

For Create, Update and Delete messages, you can defined additional parameters called images to be parsed to the plugin. An images is basically a representation of the current row in a certain stage. Basically there are two type of images that can be parsed, Preimages and Postimages

As the names indicates, preimage is reflecting how the entity was looking before this operation, while postimages are reflect how it looks after this operation.

PreEntityImages and PostEntityImages
Stage Message Pre image Post image
Validate Create N/A N/A
Pre Create N/A N/A
Post Create N/A Available
Validate Update Available N/A
Pre Update Available N/A
Post Update Available Available
Validate Delete Available N/A
Pre Delete Available N/A
Post Delete Available N/A

Images are defined when plugins are registred in CRM. Microsoft is providing a tool that is decoupled from the code where you can perform this registration. This tool is called the Microsoft Dynamics 365 CE plugin registration tool. The Kipon.Solid plugin framework has its own tool to provide this functionality. This tool does the same as the standard tool provided by Microsoft, but instead of giving you a UI, where you can navigate and link your code to events and images manually, The Kipon.Solid plugin framework provides a tool that is analyzing the actual code of your plugin, and creates and updates the registrationg accordingly.

When defining pre-/post images, they are given a name, and you extract the images from the input parameters by this name


var preimage = (Entity)context.PreEntityImages["MypreimageName"];

Actions - parameters

Above defines the Pre/Post parameters for messages related to CRUD. For actions, it is the action it self that defines the set of parameters.

Action parameters view

When you define your own actions, you set the name to be used in the parameter list for the action.

Above example is a bounded action, and it is bounded to the account entity. In such case, the action can only be called for a specific account, and the account is parsed as an EntityReference in the Target input parameter.

Output

For CUD operations you normally do not operate on the output parameters even though it is perfectly possible. You should ONLY manipulate output in POST stage of a plugin that is NOT running async. Manipulation of output in Pre-stage will not make it to the client, and manipulation of output in async, nobody will listen. The process is running async. and nothing but the plugin-execution-engine it self will ever see the results posted out in output.

That said, it is perfectly possible to manipulate output as well, and for custom defined actions that is what you SHOULD do. Your action is intended to do something based on input, and then report back to the client by sending output.

The output variables is also accessed through the plugin execution context.


context.OutputParameters["Count"] = 42;

In above example we are assigning the output parameter Count the value 42, to let the client understand the meaning of life. Ofcause in real life example, we might do some actually analyze of our data to produce some results that makes more sense.

Shared variables

The plugin execution context has a SharedVariables index property alongside the InputParameters and the OutputParameters. This container can be used to share information between steps in the SAME pipeline. So, - if you collect some knowledge in pre-stage of a pipeline, you can store it in the SharedVariables, and then extract it ex. in the post-stage, or even in another pre-stage plugins running after the first one. You can ONLY share information within the SAME pipeline. So if I update account x, and in validate update of account x, i put something into the shared variables, i can extract the variable in pre update, post update and post async update of the same update of x. The shared variable will NOT be visible to any other pipelines, even though these pipelines has been triggered by the same base event.

Lets say we update account x, and in pre stage we set variable N to 10. Later in the process we update primary contact for some reason, and one could think that the update event of the contact could extract the variable N, added to shared variables on update account x. That is NOT the case. Shared variables can only bee seen within the same pipeline as it was shared within.

Shared variables must be serializable. The Dynamics 365 plugin infrastructure is persisting the shared variables to some storage to be able to access the data, even in cases where the pre-stage is running in one instance of the plugin engine, where other steps is running in other instance.

Services

From the very start of this page, first example, we demonstrated the use of standard services and how to make them available for our code.

Below we will list the most important standard service of the Dynamics 365 platform.

IPluginExecutionContext

The plugin execution context defines the context of hte plugin. What was the message, in witch stage, parameters via InputParameters, OutputParameters, Images and SharedVariables.

IOrganizationServiceFactory

The organization service factory is providing functionality to create an IOrganizationService witch basically has the the CRUD operations as methods. So why do we need a factory. Why not just extract the organization service directly. Because you must define the user context to be used for the organization service. The factory has a single method CreateOrganizationService(userid) that returns an instance of the organization service, but you can parse a userid. Normally you would parse the userid provided by the plugin execution context, because then whatever you do against that instance of the organization service, it will be done with the settings and permission of that user. But in some cases plugin should be allowed to do things that the user is not allowed to do. If you parse null to the method, the plugin will run in the context of a system user (meaning administartor rights). If you parse the userid of another user, it will be executed in the context of that users. In the later example, the initiating user must be granted permission to run code onbehalf of other users.

IOrganizationService

As you can see in the above explanation, you cannot extract the organization service directly from the service provider. You must extract a factory class, and then create an instance from there. The organization service is basically you point of access to the Dynamics 365 database. It is pretty much solving the same assignment as an SQL provider is giving for a traditional database application. A plugin is by definition running transactional, Validate-Pre-Post in one transaction, Post-async in a new single transaction. So every operation you do against the organization service is done within the current transaction context. You can use IOrganizationService to execute any message against the CRM instance, such as Create, Update, Delete, Retreive, or any custom actions. When building plugins with Kipon.Solid framework, you rarely access the IOrganizationService directly. Instead you inject a IUnitOfWork that works on strongly typed entities, but under the hood, the generated implementation of IUnitOfWork is using the IOrganizationService to perform operations. As such, the IOrganizationService is proberbly the most import service on the platform.

ITracingService

The ITraceService allow you to add trace cababilities to your plugins. The is handy when your plugins grows in size, and it becomes harder to find reasons for exceptions we did not take into considuration when initially creating the plugin. By calling traceService.Trace("something"), the trace will go into a plugin trace log, and you can evaluate the results. ITraceService is running in its own transaction, so even if the main flow of the plugin is failing, anything that has been put into the traceService before the exception, will be logged in the assembly plugin login, and you can view this information from a normal Entity view inside Dynamics 365 CE.

Errors

Any platform interacting with a database like Dynamics 365 will produce errors, so how should we address this problem in plugins. The short answer is DON'T. Any unpredicted error should simply fall through the drain to allow the Dynamics 365 platform to deal with the error by it self.

Consider below scenario:

The example below is the recipe for disaster

try
{
    var header = new Entity("ko_related");
    header["ko_amount"] = new Money(10M);
    organizationService.Create(header);
} catch (Exception)
{
    // so we could not created related, but lets ignore and continue anyway
}

You could think, that in above example, ok, we where unable to create the "ko_related" entity, not a big deal, we continue anyway. But the call to the organization service "Create" has a hidden agenda. If any method called on the organization service, (Create, Update, Delete, Retreive, MultiRetrieve or any action) is failing, the transaction is stopped and rollback is performed. So continue beyond that point simply does not make sense. You should never catch exceptions thrown by the organization service, and because the organization service is often hidden behind other services, you should never catch exception, except when you are 100% sure about what you are dealing with.

What about throwing exceptions.

While catching exceptions can be dangerous, throwing exceptions can be the right thing to do, if illigal situations is comming to a plugin. In most cases you should only throw exceptions in Validate stage. Find errors as early as possible and notify the client, and that way save the system from doing operations that will be rolledback anyway.

If you throw an exception in a plugin and the exception makes it down the drain, all the way to the Dynamics 365 plugin infrastructure, the current transaction will be rolled back, even if the exception was not realted to any IOrganizationService operation, and non of the operations performed in the pipeline will end up in the database.

You can throw any exception, but from an api point of view, you should only throw Microsoft.Xrm.Sdk.InvalidPluginExecutionException. InvalidPluginExecutionException is known to the plugin infrastructure, and any message within the exception is considered safe and will make it all the way to the client. Ex. in Classic or Unified Interface, you will get a popup stating "A business process error: [your message]".

Conclusion

You should now have a basic understanding of the Dynamics 365 plugin design. I recommend you continue to the SOLID page to see how the Kipon.Solid.Plugin framework is making it easier to stay SOLID while developing plugins for the Dynamics 365 platform.

© Kipon ApS 2020, 2021, 2022, 2023, 2024. All content on the page is the property of Kipon ApS. Any republish or copy of this content is a violation. The content of this site is NOT open source, and cannot be copied, republished or used in any context without explcit permission from the owner.