using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Extensions; using Microsoft.Xrm.Sdk.PluginTelemetry; using System; using System.Runtime.CompilerServices; using System.ServiceModel; namespace $safeprojectname$ { /// /// Base class for all plug-in classes. /// Plugin development guide: https://docs.microsoft.com/powerapps/developer/common-data-service/plug-ins /// Best practices and guidance: https://docs.microsoft.com/powerapps/developer/common-data-service/best-practices/business-logic/ /// public abstract class PluginBase : IPlugin { protected string PluginClassName { get; } /// /// Initializes a new instance of the class. /// /// The of the plugin class. internal PluginBase(Type pluginClassName) { PluginClassName = pluginClassName.ToString(); } /// /// Main entry point for he business logic that the plug-in is to execute. /// /// The service provider. /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Execute")] public void Execute(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new InvalidPluginExecutionException(nameof(serviceProvider)); } // Construct the local plug-in context. var localPluginContext = new LocalPluginContext(serviceProvider); localPluginContext.Trace($"Entered {PluginClassName}.Execute() " + $"Correlation Id: {localPluginContext.PluginExecutionContext.CorrelationId}, " + $"Initiating User: {localPluginContext.PluginExecutionContext.InitiatingUserId}"); try { // Invoke the custom implementation ExecuteDataversePlugin(localPluginContext); // Now exit - if the derived plugin has incorrectly registered overlapping event registrations, guard against multiple executions. return; } catch (FaultException orgServiceFault) { localPluginContext.Trace($"Exception: {orgServiceFault.ToString()}"); throw new InvalidPluginExecutionException($"OrganizationServiceFault: {orgServiceFault.Message}", orgServiceFault); } finally { localPluginContext.Trace($"Exiting {PluginClassName}.Execute()"); } } /// /// Placeholder for a custom plug-in implementation. /// /// Context for the current plug-in. protected virtual void ExecuteDataversePlugin(ILocalPluginContext localPluginContext) { // Do nothing. } } /// /// This interface provides an abstraction on top of IServiceProvider for commonly used PowerPlatform Dataverse Plugin development constructs /// public interface ILocalPluginContext { /// /// The PowerPlatform Dataverse organization service for the Current Executing user. /// IOrganizationService InitiatingUserService { get; } /// /// The PowerPlatform Dataverse organization service for the Account that was registered to run this plugin, This could be the same user as InitiatingUserService. /// IOrganizationService PluginUserService { get; } /// /// IPluginExecutionContext contains information that describes the run-time environment in which the plug-in executes, information related to the execution pipeline, and entity business information. /// IPluginExecutionContext PluginExecutionContext { get; } /// /// Synchronous registered plug-ins can post the execution context to the Microsoft Azure Service Bus.
/// It is through this notification service that synchronous plug-ins can send brokered messages to the Microsoft Azure Service Bus. ///
IServiceEndpointNotificationService NotificationService { get; } /// /// Provides logging run-time trace information for plug-ins. /// ITracingService TracingService { get; } /// /// General Service Provide for things not accounted for in the base class. /// IServiceProvider ServiceProvider { get; } /// /// OrganizationService Factory for creating connection for other then current user and system. /// IOrganizationServiceFactory OrgSvcFactory { get; } /// /// ILogger for this plugin. /// ILogger Logger { get; } /// /// Writes a trace message to the trace log. /// /// Message name to trace. void Trace(string message, [CallerMemberName] string method = null); } /// /// Plug-in context object. /// public class LocalPluginContext : ILocalPluginContext { /// /// The PowerPlatform Dataverse organization service for the Current Executing user. /// public IOrganizationService InitiatingUserService { get; } /// /// The PowerPlatform Dataverse organization service for the Account that was registered to run this plugin, This could be the same user as InitiatingUserService. /// public IOrganizationService PluginUserService { get; } /// /// IPluginExecutionContext contains information that describes the run-time environment in which the plug-in executes, information related to the execution pipeline, and entity business information. /// public IPluginExecutionContext PluginExecutionContext { get; } /// /// Synchronous registered plug-ins can post the execution context to the Microsoft Azure Service Bus.
/// It is through this notification service that synchronous plug-ins can send brokered messages to the Microsoft Azure Service Bus. ///
public IServiceEndpointNotificationService NotificationService { get; } /// /// Provides logging run-time trace information for plug-ins. /// public ITracingService TracingService { get; } /// /// General Service Provider for things not accounted for in the base class. /// public IServiceProvider ServiceProvider { get; } /// /// OrganizationService Factory for creating connection for other then current user and system. /// public IOrganizationServiceFactory OrgSvcFactory { get; } /// /// ILogger for this plugin. /// public ILogger Logger { get; } /// /// Helper object that stores the services available in this plug-in. /// /// public LocalPluginContext(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new InvalidPluginExecutionException(nameof(serviceProvider)); } ServiceProvider = serviceProvider; Logger = serviceProvider.Get(); PluginExecutionContext = serviceProvider.Get(); TracingService = new LocalTracingService(serviceProvider); NotificationService = serviceProvider.Get(); OrgSvcFactory = serviceProvider.Get(); PluginUserService = serviceProvider.GetOrganizationService(PluginExecutionContext.UserId); // User that the plugin is registered to run as, Could be same as current user. InitiatingUserService = serviceProvider.GetOrganizationService(PluginExecutionContext.InitiatingUserId); //User who's action called the plugin. } /// /// Writes a trace message to the trace log. /// /// Message name to trace. public void Trace(string message, [CallerMemberName] string method = null) { if (string.IsNullOrWhiteSpace(message) || TracingService == null) { return; } if (method != null) TracingService.Trace($"[{method}] - {message}"); else TracingService.Trace($"{message}"); } } /// /// Specialized ITracingService implementation that prefixes all traced messages with a time delta for Plugin performance diagnostics /// public class LocalTracingService : ITracingService { private readonly ITracingService _tracingService; private DateTime _previousTraceTime; public LocalTracingService(IServiceProvider serviceProvider) { DateTime utcNow = DateTime.UtcNow; var context = (IExecutionContext)serviceProvider.GetService(typeof(IExecutionContext)); DateTime initialTimestamp = context.OperationCreatedOn; if (initialTimestamp > utcNow) { initialTimestamp = utcNow; } _tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); _previousTraceTime = initialTimestamp; } public void Trace(string message, params object[] args) { var utcNow = DateTime.UtcNow; // The duration since the last trace. var deltaMilliseconds = utcNow.Subtract(_previousTraceTime).TotalMilliseconds; try { if (args == null || args.Length == 0) _tracingService.Trace($"[+{deltaMilliseconds:N0}ms] - {message}"); else _tracingService.Trace($"[+{deltaMilliseconds:N0}ms] - {string.Format(message, args)}"); } catch (FormatException ex) { throw new InvalidPluginExecutionException($"Failed to write trace message due to error {ex.Message}", ex); } _previousTraceTime = utcNow; } } }