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.

Quick facts
  • 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 Execute method
  • Both the execution context and the organization service come from the IServiceProvider parameter

Step by step

  1. 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.
  2. Add NuGet references to the Dataverse SDK assemblies. Install the Microsoft.CrmSdk.CoreAssemblies package, which brings in the types your code needs: IPlugin, IPluginExecutionContext, IOrganizationService, and the rest of the Microsoft.Xrm.Sdk namespace.
  3. Implement IPlugin. Create a public class, have it implement IPlugin, and add the required Execute(IServiceProvider serviceProvider) method — the platform calls this method every time your registered step fires.
  4. Obtain the execution context and organization service from IServiceProvider. Inside Execute, call GetService to pull out the pieces you need — described below.
  5. 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.
  6. Build the project. A successful build produces a DLL in the bin folder — 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 — often context.UserId, so your plugin respects the same permissions as the person who triggered it.
Example The plugin below is registered on the 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.

PieceWhere it comes fromWhat it's for
IPluginExecutionContextserviceProvider.GetService(...)What's happening — the record, the message, the stage
ITracingServiceserviceProvider.GetService(...)Writing log messages for later troubleshooting
IOrganizationServiceFactoryserviceProvider.GetService(...)Producing an IOrganizationService bound to a specific user
IOrganizationServiceserviceFactory.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.

Key takeaway: A plugin starts life as a .NET Framework Class Library referencing 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.