Write a plug-in
You can create plug-ins by using one of the following methods:
Power Platform development tools provide a modern way to create plug-ins. The tools being referred to here are Power Platform Tools for Visual Studio and Power Platform CLI. Both these Power Platform tools generate similar plug-in code so moving from one tooling method to the other is fairly easy and understandable.
Use Power Platform Tools for Visual Studio to quickly create template plug-in code and register (deploy) a plug-in. A quickstart article is available to show you how. Use this tool if you like to work in Visual Studio.
Use Power Platform CLI to create a basic (Visual Studio compatible) plug-in project with template plug-in code using a single pac plugin command. Afterwards, using the pac tool prt command, you interactively use the Plug-in Registration tool to register your creation with Microsoft Dataverse. Use this CLI tool set if you like working in a terminal window or Visual Studio Code.
Manually write code using Visual Studio or your favorite editor. This article focuses on writing code using Dataverse APIs, however the concepts introduced also apply to plug-in development using our tools mentioned previously.
IPlugin interface
A plug-in is a compiled class within an assembly built to target .NET Framework 4.6.2. Each class in a plug-in project that is registered on an event pipeline step must implement the IPlugin interface, which defines a single IPlugin.Execute method.
public class MyPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
throw new NotImplementedException();
}
}
The Execute method accepts a single IServiceProvider parameter. The IServiceProvider
has a single method: GetService. Use this method to get several different types of services that you can use in your code.
More information: Services you can use in your code
Important
When deriving from IPlugin
, the class should be written stateless. This is because the platform caches a class instance and re-uses it for performance reasons. A simple way of thinking about this is that you shouldn't add any properties or methods to the class and everything should be included within the Execute
method.
When using the Power Platform Tools extension or the Power Platform CLI for plug-in creation, the generated PluginBase
class implements the IPlugin
interface.
There are some exceptions to the statement about adding properties or methods in the note above. For example, you can have a property that represents a constant and you can have methods that are called from the Execute
method. The important thing is that you never store any service instance or context data as a property in your class. These values change with every invocation and you don't want that data to be cached and applied to subsequent invocations.
More information: Develop IPlugin implementations as stateless
Pass configuration data to your plug-in
When you register a plug-in, you may optionally specify configuration data to pass to the plug-in at run-time. Configuration data allows you to define how a specific instance of a registered plug-in should behave. This information is passed as string data to parameters in the constructor of your class. There are two parameters named unsecure
and secure
. Use the first unsecure
parameter for data that you don't mind if someone else can see. Use the second secure
parameter for sensitive data.
The following code shows the three possible constructor signatures for a plug-in class named MyPlugin.
public MyPlugin() {}
public MyPlugin(string unsecure) {}
public MyPlugin(string unsecure, string secure) {}
The secure configuration data is stored in a separate table that only system administrators have privileges to read.
More information: Register plug-in step > Set configuration data
PluginBase abstract class
The PluginBase
class implements the IPlugin
interface. We offer this class as it implements a robust programming pattern that has proven itself in commercial solutions. However, use of this class in your plug-in code is optional but recommended.
The class is not available in any SDK assembly, rather you must generate the class using one of our tools. To generate the class, create a plug-in project in the Power Platform Tools extension for Visual Studio or execute the Power Platform CLI command pac plugin init
. You will find a PluginBase.cs file in the generated project.
More information: pac plugin, Quickstart: Create a plug-in using Power Platform Tools
Important
The PluginBase
class generated by the Power Platform Tools extension and the Power Platform CLI have slightly different class member signatures. It is best to choose one or the other and stick with it in all your plug-in code development. Both versions of the class have the same functionality and perform similarly.
This class diagram provides a visual on the key interfaces and classes related to PluginBase
.
The example Plugin1
class (in the diagram) derives from PluginBase
. You would rename this Plugin1
class to your own custom name and add code to implement the ExecuteDataversePlugin
method and class constructor. The LocalPluginContext
class is automatically initialized with the indicated service references and endpoints available to your plug-in. If you were implementing the IPlugin
interface, you would have to write code to initialize all these services and endpoints prior to using them.
Services you can use in your code
Typically, within your plug-in you will:
- Access the contextual data passed to your plug-in to determine information about the entity and message request that caused the event and invoked your plug-in. This data is called the execution context.
- Access the Organization web service using SDK for .NET calls to perform message request operations like query, create, update, delete, and more.
- Write messages to the Tracing service so you can evaluate how your plug-in code is executing.
Note
All Dataverse services your plug-in would typically use and the plug-in execution context are pre-configured and available to your plug-in code when you derive your plug-in from the PluginBase
class.
The IServiceProvider.GetService method provides you with a way to access service references passed in the execution context when needed. To get an instance of a service, you invoke the GetService
method passing the type of service. Read more about this method in the next sections.
Execution context
The execution context contains a wealth of information that a plug-in may need. The context is obtained using the following code.
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
More information: IPluginExecutionContext, Understand the execution context
Organization web service
In addition to the data passed in the execution context, Dataverse table row data can be read or written from plug-in code using SDK calls to the Organization web service. Don't try to use the Web API as it isn't supported in plug-ins. Also, don't authenticate the user before accessing the web services as the user is preauthenticated before plug-in execution.
More information: Table Operations, Use messages
To obtain an object reference to the Organization web service use the following code:
IOrganizationServiceFactory serviceFactory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService orgService = serviceFactory.CreateOrganizationService(context.UserId);
Tracing service
Use the tracing service to write messages to the PluginTraceLog Table so that you can review the logs to understand what occurred when the plug-in ran.
Important
You must first enable trace logging in your environment before you are able to write to or view the logs. More information: Tracing and logging
To write to the Tracelog, you need to get an instance of the Tracing service. The following code shows how to get an instance of the Tracing service using the IServiceProvider.GetService method.
ITracingService tracingService =
(ITracingService)serviceProvider.GetService(typeof(ITracingService));
To write to the trace, use the ITracingService.Trace method.
tracingService.Trace("Write {0} {1}.", "your", "message");
More information: Use Tracing, Tracing and logging.
Other services
When you write a plug-in that uses Azure Service Bus integration, use the notification service that implements the IServiceEndpointNotificationService interface, but this interface won't be described here.
More information: Azure Integration
Putting it all together
Applying the plug-in concepts detailed previously results in plug-in code that looks like the following.
public class MyPlugin : PluginBase
{
// Constructor
public MyPlugin(string unsecureConfiguration, string secureConfiguration)
: base(typeof(MyPlugin))
{ }
protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
{
if (localPluginContext == null)
{
throw new ArgumentNullException(nameof(localPluginContext));
}
var context = localPluginContext.PluginExecutionContext;
var serviceFactory = localPluginContext.OrgSvcFactory;
var tracingService = localPluginContext.TracingService;
try
{
// TODO Plug-in business logic goes here. You can access data in the context,
// and make calls to the Organization web service using the Dataverse SDK.
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException("The following error occurred in MyPlugin.", ex);
}
catch (Exception ex)
{
tracingService.Trace("MyPlugin: error: {0}", ex.ToString());
throw;
}
}
}
You can review these two methods of plug-in implementations in our FollowupPlugin code sample. For more information about handling exceptions in plug-ins see Handle exceptions in plug-ins.
Plug-in design impacts performance
When writing your plug-in, it's critical that it must execute efficiently and quickly. However long your plug-in takes to execute causes the end user that invoked the message operation (which triggered your plug-in) to wait. In addition to processing the message operation, Dataverse executes all registered synchronous plug-ins in the pipeline including your plug-in. When plug-ins take too long to execute, or if too many plug-ins are registered in a pipeline, this performance impact can result in a nonresponsive application UI or worst case a timeout error with pipeline rollback.
Important
Plug-ins must adhere to an execution time limit and resource constraints. More information: Analyze plug-in performance
Using early-bound types in plug-in code
You can optionally use early-bound types within plug-in code. Include the generated types file in your plug-in project. All table types provided in the execution context's InputParameters collection are late-bound types. You would need to convert those late-bound types to early-bound types.
For example, you can do the following when you know the Target
parameter represents an account table. In this example, "Account" is an early-bound type.
Account acct = context.InputParameters["Target"].ToEntity<Account>();
But you should never try to set the value using an early-bound type. Doing so causes an SerializationException to occur.
context.InputParameters["Target"] = new Account() { Name = "MyAccount" }; // WRONG: Do not do this.
See also
Handle exceptions
Register a plug-in
Debug Plug-ins
Tutorial: Write and register a plug-in
Best practices and guidance regarding plug-in and workflow development