Welcome to the fifteenth post in my series focused on providing a set of revision notes for the PL-400: Microsoft Power Platform Developer exam. In the previous post, we finished our discussion of the Extend the user experience exam area by evaluating command buttons and demonstrating how valuable the Ribbon Workbench tool is in helping us fine-tune aspects of a model-driven Power App’s interface. This topic rounded off our discussion of the Extend the user experience topic, meaning that we now move into a new section titled Extend the platform. This area has equal weighting (15-20%), and the first topic concerns how we Create a plug-in. Specifically, candidates must demonstrate knowledge of the following:
Create a plug-in
- describe the plug-in execution pipeline
- design and develop a plug-in
- debug and troubleshoot a plug-in
- implement business logic by using pre-images and post-images
- perform operations on data by using the Organization service API
- optimize plug-in performance
- register custom assemblies by using the Plug-in Registration Tool
- develop a plug-in that targets a custom action message
Developers who have previously worked with Dynamics 365 or Dynamics CRM should have very little trouble getting to grips with plug-ins, as they have been a mainstay within these applications for well over a decade now. Notwithstanding this, it’s always helpful to get a refresher on even the most familiar of topics 😉. With that in mind, let’s dive in!
As with all posts in this series, the aim is to provide a broad outline of the core areas to keep in mind when tackling the exam, linked to appropriate resources for more focused study. Ideally, your revision should involve a high degree of hands-on testing and familiarity with the platform if you want to do well in this exam. I would also recommend that you have a good general knowledge of working with the C# programming language before approaching plug-in development for the first time. Microsoft has published a whole range of introductory material to help you learn the language quickly.
What is a Plug-in?
In the series so far, we’ve already touched upon the following functional tools that developers may leverage when dealing with complex business requirements:
- Business Rules: These provide an excellent mechanism for handling simple logic, targeting both model-driven app forms and platform-level operations.
- Power Automate: Using flows authored by this tool, you can typically go the extra mile compared with classic workflows, thereby allowing you to integrate multiple systems when processing asynchronous actions.
These are great, but there may be situations where they are unsuitable due to the sheer complexity of the business logic you are trying to implement. Also, some of the above tools do not support the ability to carry out synchronous actions (i.e. ones that happen straight away, as the user creates, updates, deletes etc, rows in the application). Finally, it may be that you need to work with some specific elements of the SDK that are not exposed out straightforwardly via alternative routes. In these situations, a custom-authored plug-in is the only solution you can turn to.
So what are they then? Plug-ins allow developers to write either C# or VB.NET code, which is then registered within the application as a .NET Framework class library (DLL) and executed based on specific conditions you specify within its configuration. For example, when a user creates a Contact, retrieve the details of the parent Account row and update the Contact to match. Developers will use Visual Studio when authoring a plug-in, typically using a class library project template. The Microsoft Dataverse SDK provides several modules that expose various operations that a plug-in can support. Some of the other things that plug-ins support include:
- Both synchronous and asynchronous execution.
- Custom un-secure/secure configuration properties that can modify the behaviour of a plug-in at runtime. For example, by providing credentials to a specific environment to access.
- Pre and Post Table (previously known as Entity) Images, snapshots of how a record and its related attributes looked before and after a platform level operation occurs.
- For specific operations, such as Update, plug-ins can also support filtering attributes – basically, a list of defined columns that, when modified, will cause the plug-in to trigger.
- Being able to specify the execution order for one or multiple plug-in steps. This capability can be helpful when you need to ensure a set of steps execute in your desired order.
These days, thanks to Business Rules and Power Automate, we see the lessening importance of plug-ins, and, typically, you would want to avoid using them straight out the gate. However, they still have a place and, when used appropriately, become the only mechanism you can resort to when working with complicated business logic.
Understanding Messages, the Execution Pipeline & Steps
Before we start diving into building a plug-in for the first time, it’s prudent to provide an overview of the three core concepts that every plug-in developer needs to know:
- Messages: These define a specific, platform level operation that the SDK exposes out. Some of the more commonly used Messages within the application include Create, Update or Delete. Some system tables may have their own set of unique Messages; CalculatePrice is an excellent example of this. From a plug-in perspective, developers essentially “piggyback” onto these operations as they are performed and inject their custom code.
- Execution Pipeline: As a user triggers a particular message, the application processes it using a defined set of stages, known as the execution pipeline. These are discussed in detail in this Microsoft Docs article, but the key ones we need to be aware of are:
- PreValidation: At this stage, the database transaction has not started. Also, the application has not yet performed any appropriate security checks, potentially meaning that the operation may fail if the user does not have the correct security privileges.
- PostValidation: Here, the database transaction has already started. The platform knows at this stage that no security constraints will prevent the operation from completing, but the transaction may still fail for other reasons.
- PostOperation: Although the database transaction has still not completed at this stage, the core operation of the message (executed within the MainOperation stage) will almost certainly commit successfully, assuming no errors occur via a custom plug-in.
- A failure at any of these stages will cause the database transaction to roll back entirely, returning any affected row(s) to their original state. Now, from a plug-in perspective, the execution pipeline is exposed out to developers as stages where your custom code can execute. This can provide developers with a high degree of flexibility and capability when running their custom code. As a general rule of thumb, developers would use each of these stages under the following circumstances:
- PreValidation: Use this stage to perform checks to cancel the operation.
- PostValidation: This stage is handy for modifying any values before they hit the database. Doing this at this stage would also prevent triggering another platform Message.
- PostOperation: Use this stage for when you need to carry out additional logic not related to the current record or potentially provide additional information back to the caller after the platform completes the core operation.
- Developers will typically need to give some thought towards the execution pipeline and the most appropriate one to select, based on the requirements being worked with.
- Steps: Simply writing a plug-in class and deploying out the library is not sufficient to trigger your custom logic. Developers must also provide a set of “instructions”, commonly known as Steps, that tell the application when and where to execute your custom code. It is here where both the Message and Execution Pipeline come into play, and you would always specify this information when creating a Step. Additional info you can include here includes the execution order, whether the plug-in will execute synchronously or not and the display name of the Step when viewed within the application.
We will see shortly how these topics come into play as part of using the Plug-in Registration Tool.
Building Your First Plug-In: Pre-Requisites
Before you start thinking about developing a plug-in, you need to make sure that you have a few things installed onto your development machine:
- Visual Studio: I would recommend using Visual Studio 2019 where possible. If you don’t have a Visual Studio/MSDN subscription, we can use the Community Edition instead.
- A Microsoft Dataverse Environment: Because where else are you going to deploy out and test your plug-in? 😀
- Knowledge of C# / VB.NET: Attempting to write a plug-in for the first time without at least a basic grasp of one of these languages will impede your progress.
- Plug-in Registration Tool: To deploy your plug-in out, you will need access to this tool as well. We’ll cover this off later in the post.
Demo: Creating a Microsoft Dataverse Plug-in using Visual Studio 2019 & C#
The best way to learn how to create a plug-in is to see someone build one from scratch. In the YouTube video below, I talk through how to build a straightforward plug-in using Visual Studio 2019 and C#:
For those who would prefer to read a set of instructions, then this Microsoft Docs article provides a separate tutorial you can follow instead.
Using the Plug-in Registration Tool
Once you’ve written your first plug-in, you then need to consider how to deploy this out. In most cases, you will use the Plug-in Registration Tool to accomplish this. Available on NuGet, this lightweight application supports the following features:
- Varied access options when working with multiple Dataverse environments.
- The ability to register one or multiple plug-in assemblies.
- The registering of plug-in steps and images, including the various settings, discussed earlier.
- Via the tool, you can install the Plug-in Profiler, an essential tool for remote debugging your plug-ins; more on this capability later.
The Plug-In Registration Tool is also necessary for deploying other extensibility components, including Service Endpoints, WebHooks or Custom Data Providers. We will touch upon some of these later on in the series. For this topic area and the exam, you must have a good general awareness of deploying and updating existing plug-in assemblies.
Demo: Deploying a Microsoft Dataverse Plug-in using the Plug-in Registration Tool
In this next video, I’ll show you how to take the plug-in developed as part of the previous video and deploy it out using the Plug-in Registration Tool:
Debugging Options for Plug-ins
Plug-ins deployed out into Microsoft Dataverse must always run within sandbox execution mode. As a result, this imposes a couple of limitations (some of which I’ll highlight in detail later on), the main one being that it greatly hinders the ability to debug deployed plug-ins easily. To get around this, Microsoft has provided two mechanisms that developers can leverage:
- Plug-in Trace Logging: Using this, developers can write out custom log messages into the application at any point in their code. This can be useful in identifying the precise location where a plug-in is not working as expected, as you can output specific values to the log for further inspection. You can also utilise them to provide more accurate error messages for your code that you would not necessarily wish to show users as part of a dialog box. Getting to grips with trace logging is easy – it’s just a few lines of code that you need to add to your project – and you can find out more about how to work with it on the Microsoft Docs site.
- Plug-in Registration Tool & Profiling: While trace logging is undoubtedly useful, there will be situations where you need something more. For example, it may become desirable to breakpoint code, inspect values/properties as they are processed, and determine when your plug-in hits specific conditions or error messages. The Profiler comes into play for these situations by allowing developers to “playback” their code execution using Visual Studio and the Plug-in Registration Tool. We mentioned the Plug-in Profiler tool earlier, which is a mandatory requirement and must be deployed to your instance first. From there, you can generate a profile file that allows you to rewind execution within Visual Studio.
Developers will typically use both of these debugging methods in tandem to figure out issues with their code. The first option provides a friendly, discreet mechanism of inspecting how a plug-in has got on. The Profiler acts as the “nuclear” option when we cannot discern the problem easily via trace logging alone. Understanding the benefits/disadvantages of both and how to use them will be essential as part of your exam preparation. With this in mind, check out the two demo videos below that show you how to work with these features in-depth:
Demo: Debugging a Microsoft Dataverse Plug-in using Trace Logging
Demo: Debugging a Microsoft Dataverse Plug-in Using the Plug-in Registration Tool
General Performance / Optimization Tips
Anyone can build and deploy a plug-in, but it can take some time before you can do this well. Here are a few tips that you should always follow to ensure your plug-ins perform well when deployed out:
- Keep in mind some of the limitations as part of sandbox execution for your plug-ins, including:
- The 2 minute limit on execution time.
- The inability to use specific 3rd party DLL’s, such as Newtonsoft.Json
- Various restrictions around accessing operating system level information, such as directories, system state etc.
- For situations where sandbox limitation will cause issues in executing your business logic, you will need to consider moving away from a plug-in and adopting another solution.
- Filtering attributes provide a great way of ensuring your code only executes when you need it to. You should always use these wherever possible.
- Make sure to disable any plug-in profiling and remove the Profiler solution once you are finished. Active profiles can considerably slow down performance, and the Profile solution can also introduce unintended dependencies on core tables, causing difficulties when moving changes as part of a solution file.
- The Solution Checker provides an excellent mechanism for quality checking your code and can give some constructive recommendations on where your plug-in can be improved. Running this at least once before moving your plug-in out into other environments is highly recommended.
- Read through the following Microsoft Docs article and take care to follow the suggestions it outlines.
Don’t Forget…Custom Actions & Global Discovery Service Endpoint
As Microsoft has included them in the exam specification, it’s worth talking about these two topics briefly. However, only a general awareness of them should be sufficient, and I wouldn’t devote too much of your revision time to them.
We’ve already looked at Messages in-depth and seen how they can act as a “gateway” for developers to bolt on specific logic when an application-level event occurs. Custom Actions allow you to take this a step further by creating SDK/API exposable, custom Messages. For example, you could combine the Create/Update Messages of the Lead/Opportunity tables into a new message called SalesProcess. Plug-ins or operations targeting the Web API could then work with or trigger actions based on this. Custom Actions have been available within the application for many years now, and many developers continually argue them as being one of the most underrated features in Microsoft Dataverse. They are also fully supported for use as part of Power Automate flows too. In short, they can be incredibly useful if you need to group multiple default messages into a single action that can then be called instead of each one.
Finally, it is worth touching upon how developers use the Global Discovery Endpoint from a plug-in standpoint, but we must first outline the concepts of early-binding and late binding. In the video demos above, I wrote all of the code using the late-binding mechanism. This means that instead of declaring an object for the specific table I wanted to work with (such as Contact), I instead used the generic Entity class and told the code which attributes I wanted to work with by specifying their logical names as strings. This is fine and gives me some degree of flexibility, but it ultimately means that I won’t detect any issues with my code (such as an incorrect field name) until runtime. Also, I have no easy way to tell how my table looks using Intellisense; instead, I must continuously refer to the application to view these properties. To get around this, we can use a mechanism known as early-binding, where all of the appropriate table structures are generated within the project and referenced accordingly. The CrmSvcUtil.exe application provides a streamlined means of creating these early-bound classes, and, as you might have guessed, you need to use the Global Discovery Endpoint to generate these classes successfully. There have been many
previous wars and mounds of dead developers debates online regarding which mechanism is better. While early-binding does afford some great benefits, it does add some overhead into your development cycle, as you must continuously run the CrmSvcUtil application each time your tables change within Microsoft Dataverse. All I would recommend is to try both options, identify the one that works best for you and, most importantly, adopt your preferred solution consistently.