PipeDream.Plugins 1.1.0

dotnet add package PipeDream.Plugins --version 1.1.0
                    
NuGet\Install-Package PipeDream.Plugins -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PipeDream.Plugins" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PipeDream.Plugins" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="PipeDream.Plugins" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PipeDream.Plugins --version 1.1.0
                    
#r "nuget: PipeDream.Plugins, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PipeDream.Plugins@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PipeDream.Plugins&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=PipeDream.Plugins&version=1.1.0
                    
Install as a Cake Tool

PipeDream Logo Banner

GitHub Release NuGet Version Build CodeQL

Custom PluginBase, service extensions, and utilities for building robust Dataverse plugins.

Installation

dotnet add package PipeDream.Plugins

Features

  • Enhanced PluginBase with error handling and context management
  • Structured logging to Plugin Trace Logs and Application Insights
  • Context extensions for images, attributes, stages, and messages
  • Type-safe constants for messages, stages, modes, and image names
  • Access to all IPluginExecutionContext interfaces (1-7)
  • Helper properties for Target, PreImage, PostImage
  • Organization services for initiating user and elevated privileges
  • Create organization services for specific users on-demand

Usage

Basic Plugin

public class MyPlugin : PluginBase
{
    public MyPlugin(string unsecureConfig, string secureConfig) 
        : base(typeof(MyPlugin))
    {
    }

    protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
    {
        var context = localPluginContext.PluginExecutionContext;
        var service = localPluginContext.OrganizationService;
        var elevatedService = localPluginContext.ElevatedOrganizationService;
        
        // Your plugin logic here
    }
}

Context Extensions

Check stage and message:

if (context.IsPreOperation() && context.IsUpdate())
{
    // Handle pre-update logic
}

Get entity images:

var preImage = context.GetPreImage<Account>();
var postImage = context.GetPostImage<Account>();

Get final attribute values:

// Gets from Target first, falls back to PreImage
var name = context.GetFinalAttributeValue<string>("name");

Check attribute changes:

if (context.HasAttributeChanged("statecode"))
{
    var oldValue = context.GetOldAttributeValue<OptionSetValue>("statecode");
    var newValue = context.GetNewAttributeValue<OptionSetValue>("statecode");
}

Constants

Use built-in constants instead of magic strings:

using PipeDream.Plugins.Constants;

// Stage values
if (context.Stage == Stage.PreOperation) { }

// Message names
if (context.MessageName == Message.Update) { }

// Execution modes
if (context.Mode == Mode.Synchronous) { }

// Image names
var image = context.GetPreImage(ImageName.PreImage);

Execution Context Interfaces

The LocalPluginContext provides access to all execution context versions:

IPluginExecutionContext - Base context with:

  • InputParameters, OutputParameters, SharedVariables
  • PreEntityImages, PostEntityImages
  • Stage, Depth, MessageName, PrimaryEntityName

IPluginExecutionContext2 - Adds:

  • IsPortalsClientCall - Detect Power Pages requests
  • PortalsContactId - Get authenticated portal user
  • Azure AD object IDs

IPluginExecutionContext3 - Adds:

  • AuthenticatedUserId - For impersonation scenarios

IPluginExecutionContext4 - Adds:

  • PreEntityImagesCollection, PostEntityImagesCollection - For bulk operations

IPluginExecutionContext5 - Adds:

  • InitiatingUserAgent - Browser/client detection

IPluginExecutionContext6 - Adds:

  • EnvironmentId, TenantId - Multi-tenant scenarios

IPluginExecutionContext7 - Adds:

  • IsApplicationUser - Distinguish service principal vs human user

Usage:

// Check if called from Power Pages
if (localPluginContext.PluginExecutionContext2.IsPortalsClientCall)
{
    var portalContactId = localPluginContext.PluginExecutionContext2.PortalsContactId;
}

// Detect application users (service principals)
if (localPluginContext.PluginExecutionContext7.IsApplicationUser)
{
    // Handle automation differently
}

Organization Services

The context provides convenient access to organization services:

OrganizationService - Service running as the user who triggered the action:

var service = localPluginContext.OrganizationService;
var account = service.Retrieve("account", accountId, new ColumnSet("name"));

ElevatedOrganizationService - Service running as the plugin registration user:

var elevatedService = localPluginContext.ElevatedOrganizationService;
// Use for operations requiring elevated privileges
elevatedService.Update(restrictedEntity);

CreateOrganizationService(userId) - Create a service for a specific user:

// Get service for a specific user (e.g., from a lookup field)
var targetUserId = account.GetAttributeValue<EntityReference>("ownerid").Id;
var targetUserService = localPluginContext.CreateOrganizationService(targetUserId);

// Perform operations as that specific user
targetUserService.Create(entityAsTargetUser);

This is useful for:

  • Impersonation scenarios
  • Operations that need to run as a specific user
  • Creating services for system administrators dynamically

Logging

The context provides structured logging to both Plugin Trace Logs and Application Insights.

Note: All log methods (LogInfo, LogWarning, LogError) automatically write to both the Plugin Trace Log with time deltas and Application Insights. Use Trace() for trace-only logging without Application Insights.

LogInfo(message) - Informational logging:

localPluginContext.LogInfo("Processing started");
// Trace: [+5ms] - [ExecuteDataversePlugin] - Processing started
// Application Insights: Info level telemetry

LogWarning(message) - Warning-level logging:

localPluginContext.LogWarning("Skipping invalid record");
// Trace: [+20ms] - WARNING: [ExecuteDataversePlugin] - Skipping invalid record
// Application Insights: Warning level telemetry

LogError(message) - Error-level logging:

localPluginContext.LogError("Failed to update");
// Trace: [+35ms] - ERROR: [ExecuteDataversePlugin] - Failed to update
// Application Insights: Error level telemetry

LogError(exception, message) - Structured exception logging:

try
{
    // Your code
}
catch (Exception ex)
{
    localPluginContext.LogError(ex, "Operation failed");
    // Trace: Full exception details with stack trace
    // Application Insights: Structured exception with telemetry
    throw;
}

All log methods automatically include the calling method name via [CallerMemberName].

Custom Logging Implementation

Override the CreateLocalPluginContext method to inject your own logging service:

1. Create an ILogger adapter for your custom logging service:

using Microsoft.Xrm.Sdk.PluginTelemetry;

public class CustomLoggerAdapter : ILogger
{
    private readonly ITelemetryHelper _telemetryHelper;

    public CustomLoggerAdapter(ITelemetryHelper telemetryHelper)
    {
        _telemetryHelper = telemetryHelper;
    }

    public void LogInformation(string message)
    {
        _telemetryHelper.Trace(message);
    }

    public void LogWarning(string message)
    {
        _telemetryHelper.Trace($"WARNING: {message}");
    }

    public void LogError(string message)
    {
        _telemetryHelper.Trace($"ERROR: {message}");
    }

    public void LogError(Exception exception, string message)
    {
        _telemetryHelper.Exception(exception, new Dictionary<string, string> 
        { 
            { "message", message } 
        });
    }
}

2. Create a custom base class that injects your logger:

public abstract class CustomPluginBase : PluginBase
{
    protected CustomPluginBase(Type pluginClassName) : base(pluginClassName)
    {
    }

    protected override ILocalPluginContext CreateLocalPluginContext(IServiceProvider serviceProvider)
    {
        // Initialize your custom logging service
        var envVarService = new EnvironmentVariableService(serviceProvider);
        var telemetryHelper = new TelemetryHelper(envVarService);
        var customLogger = new CustomLoggerAdapter(telemetryHelper);

        // Inject custom logger into context
        return new LocalPluginContext(serviceProvider, customLogger);
    }
}

3. Inherit from your custom base class:

public class MyPlugin : CustomPluginBase
{
    public MyPlugin(string unsecureConfig, string secureConfig) 
        : base(typeof(MyPlugin))
    {
    }

    protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
    {
        // Logging automatically uses your custom implementation
        localPluginContext.LogInfo("Using custom logging");
    }
}

Note: If Application Insights is not configured in your environment, the default ILogger from the service provider will be null and logging will only write to Plugin Trace Logs.

Tracing and Performance Diagnostics

The LocalTracingService automatically prefixes traces with time deltas for performance diagnostics:

protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
{
    var tracing = localPluginContext.TracingService;
    
    tracing.Trace("Starting plugin execution");
    // Output: [+0ms] - Starting plugin execution
    
    // Your logic here
    
    tracing.Trace("Completed business logic");
    // Output: [+42ms] - Completed business logic
    
    tracing.Trace("Processing {0} records", recordCount);
    // Output: [+15ms] - Processing 5 records
}

Each trace shows milliseconds elapsed since the previous trace, making it easy to identify performance bottlenecks.

Product Compatible and additional computed target framework versions.
.NET Framework net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 4.6.2

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 295 12/18/2025
1.0.1 287 12/17/2025
1.0.0 291 12/17/2025