Time to write real code. This chapter walks through creating a working plugin project from an empty folder to a compiled DLL, then builds one complete, original example: a plugin that creates a follow-up Task whenever a new Contact is created. Registering it against a live environment is covered in Chapter 7 — this chapter is about the code itself.
- Plugins are built as a Class Library project targeting .NET Framework
- The SDK types come from the Microsoft.CrmSdk.CoreAssemblies NuGet package
- Your class must implement IPlugin and its single
Executemethod - Both the execution context and the organization service come from the
IServiceProviderparameter
Step by step
- Create a Class Library project targeting .NET Framework. Plugins run inside the Dataverse sandbox, which currently loads .NET Framework assemblies — not .NET (Core) class libraries. In Visual Studio, choose "Class Library (.NET Framework)" when creating the project.
- Add NuGet references to the Dataverse SDK assemblies. Install the
Microsoft.CrmSdk.CoreAssembliespackage, which brings in the types your code needs:IPlugin,IPluginExecutionContext,IOrganizationService, and the rest of theMicrosoft.Xrm.Sdknamespace. - Implement IPlugin. Create a public class, have it implement
IPlugin, and add the requiredExecute(IServiceProvider serviceProvider)method — the platform calls this method every time your registered step fires. - Obtain the execution context and organization service from IServiceProvider. Inside
Execute, callGetServiceto pull out the pieces you need — described below. - Write the business logic. Read whatever data you need off the context, decide what to do, and make any calls back to Dataverse through the organization service.
- Build the project. A successful build produces a DLL in the
binfolder — that DLL is what gets signed and registered in Chapter 7.
Two services worth knowing by name
Two objects do almost all the work in a typical plugin, and both come out of the IServiceProvider passed into Execute:
- IPluginExecutionContext — described in Chapter 2. Tells you what's happening: which record, which message, which stage.
- IOrganizationServiceFactory — a factory you use to obtain an IOrganizationService, the interface your code uses to talk back to Dataverse: creating, updating, retrieving, or deleting records. You typically call
CreateOrganizationService, passing the user ID whose security privileges the call should run under — oftencontext.UserId, so your plugin respects the same permissions as the person who triggered it.
Create message of the Contact table, at the post-operation stage (Chapter 2), running synchronously (Chapter 3). Once a new Contact is saved, it creates a follow-up Task pointed back at that Contact.
using System;
using Microsoft.Xrm.Sdk;
namespace VitalDynamics365.Plugins
{
public class CreateFollowUpTaskOnContact : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
var tracing = (ITracingService)
serviceProvider.GetService(typeof(ITracingService));
var serviceFactory = (IOrganizationServiceFactory)
serviceProvider.GetService(typeof(IOrganizationServiceFactory));
// Only proceed if this really is a Contact being created
if (!context.InputParameters.Contains("Target") ||
!(context.InputParameters["Target"] is Entity target) ||
target.LogicalName != "contact")
{
return;
}
IOrganizationService service =
serviceFactory.CreateOrganizationService(context.UserId);
var followUp = new Entity("task");
followUp["subject"] = "Welcome call for new contact";
followUp["regardingobjectid"] = new EntityReference(
"contact", target.Id);
followUp["scheduledend"] = DateTime.UtcNow.AddDays(2);
service.Create(followUp);
tracing.Trace(
"CreateFollowUpTaskOnContact: task created for contact {0}",
target.Id);
}
}
}
Read it in order: get the context, get a tracing handle for logging (covered fully in Chapter 8), get a service factory, confirm this really is a Contact being created, build an organization service, then create a Task record pointing back at the new Contact. Each piece traces directly back to something explained earlier in this course.
| Piece | Where it comes from | What it's for |
|---|---|---|
| IPluginExecutionContext | serviceProvider.GetService(...) | What's happening — the record, the message, the stage |
| ITracingService | serviceProvider.GetService(...) | Writing log messages for later troubleshooting |
| IOrganizationServiceFactory | serviceProvider.GetService(...) | Producing an IOrganizationService bound to a specific user |
| IOrganizationService | serviceFactory.CreateOrganizationService(...) | Create, Update, Retrieve, and Delete calls back to Dataverse |
A habit worth starting now
Always check what you're working with before acting on it — target's LogicalName, whether a key exists in InputParameters, whether an expected attribute is actually present on the record. A plugin registered too broadly, or one that assumes data is there when it isn't, is one of the most common sources of a plugin throwing an unhandled exception on a save that should have worked fine.
Microsoft.CrmSdk.CoreAssemblies, implementing IPlugin. Inside Execute, pull the execution context and an organization service out of IServiceProvider, guard your logic with a few checks, then read or write data through that service.