Write a plug-in
You can create plug-ins by using one of the following two 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 and register (deploy) plug-ins. 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 your favorite editor or IDE. The rest of the plug-in documentation in this topic and the other related topics is written with the developer writing code in mind, however the concepts introduced apply to all methods of plug-in development.
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 will be 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. You will 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 Power Platform tools for plug-in creation, the generated PluginBase
class is derived from IPlugin
.
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 which only system administrators have privileges to read.
More information: Register plug-in step > Set configuration data
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.
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 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. Do not try to use the Web API as it is not supported in plug-ins. Also, do not authenticate the user before accessing the web services as the user is pre-authenticated prior to 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.
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, you will use a notification service that implements the IServiceEndpointNotificationService interface, but this will not 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 : IPlugin
{
public MyPlugin() {} // Constructor, does nothing
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
// Obtain the Organization service reference
IOrganizationServiceFactory serviceFactory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService orgService = serviceFactory.CreateOrganizationService(context.UserId);
// Obtain the Tracing service reference
ITracingService tracingService =
(ITracingService)serviceProvider.GetService(typeof(ITracingService));
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;
}
}
More information about handling exceptions: Handle exceptions in plug-ins
Plug-in design impacts performance
When writing your plug-in, it is 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 can result in a non-responsive application UI or worst case a timeout error with pipeline rollback.
More information: Anaylyze plug-in performance
Using early-bound types in plug-in code
You can optionally use early-bound types within plug-in code. Simply include the generated types file in your plug-in project. Be aware that 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 will cause an SerializationException to occur.
context.InputParameters["Target"] = new Account() { Name = "MyAccount" }; // WRONG: Do not do this.
Building the plug-in assembly
When building a plug-in project, keep the following output assembly constraints in mind.
Use .NET Framework 4.6.2
Plug-in and custom workflow activity assembly projects must target .NET Framework 4.6.2. While assemblies built using later versions of the Framework should generally work, if the plug-in code uses any features introduced after 4.6.2, an error will occur.
Optimize assembly development
The assembly may include multiple plug-in classes (or types), but can be no larger than 16 MB in size. It is recommended to consolidate plug-ins and workflow assemblies into a single assembly as long as the size remains below 16 MB.
Best practice information: Optimize assembly development
Assemblies must be signed
All assemblies must be signed before they can be registered. This can be done using the Visual Studio Signing tab on the project or by using Sn.exe (Strong Name Tool).
Do not depend on .NET assemblies that interact with low-level Windows APIs
Plug-in assemblies must contain all the necessary logic within the respective DLL. Plug-ins may reference some core .NET assemblies. However, we do not support dependencies on .NET assemblies that interact with low-level Windows APIs, such as the graphics design interface.
Dependency on any other (non-Dataverse) assemblies
Adding the Microsoft.CrmSdk.CoreAssemblies
NuGet package to your project will include the necessary Dataverse assembly references in your project, but you will not upload these assemblies along with your plug-in assembly as these Dataverse assemblies already exist in the server's sandbox run-time.
The dependent assembly capability, currently in a Preview release, can be used to include other .NET compiled assemblies with your plug-in assembly in a single uploadable package.
More information: Dependent Assembly plug-ins.
Important
The dependent assembly capability is so important to plug-in development that you should consider using it from the start even if you do not have an immediate need to do so. Adding support for dependent assemblies to your plug-in project is much more difficult later on in the development cycle.
Since this feature is a Preview release, do not use this feature for production work.
Next steps
Register a plug-in
Debug Plug-ins
See also
Tutorial: Write and register a plug-in
Handle exceptions
Best practices and guidance regarding plug-in and workflow development
Feedback
Submit and view feedback for