namespace <%= company %>.<%= customer %>.Crm.Plugins { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.ServiceModel; using System.Linq.Expressions; using Microsoft.Xrm.Sdk; // StepConfig : className, ExecutionStage, EventOperation, LogicalName // ExtendedStepConfig : Deployment, ExecutionMode, Name, ExecutionOrder, FilteredAttributes, UserContext // ImageTuple : Name, EntityAlias, ImageType, Attributes using StepConfig = System.Tuple; using ExtendedStepConfig = System.Tuple; using ImageTuple = System.Tuple; /// /// Base class for all Plugins. /// public class Plugin : IPlugin { protected class LocalPluginContext { internal IServiceProvider ServiceProvider { get; private set; } internal IOrganizationService OrganizationService { get; private set; } // Delegate A/S added: internal IOrganizationService OrganizationAdminService { get; private set; } internal IPluginExecutionContext PluginExecutionContext { get; private set; } internal ITracingService TracingService { get; private set; } private LocalPluginContext() { } internal LocalPluginContext(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new ArgumentNullException("serviceProvider"); } // Obtain the execution context service from the service provider. this.PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); // Obtain the tracing service from the service provider. this.TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); // Obtain the Organization Service factory service from the service provider IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); // Use the factory to generate the Organization Service. this.OrganizationService = factory.CreateOrganizationService(this.PluginExecutionContext.UserId); // Delegate A/S added: Use the factory to generate the Organization Admin Service. this.OrganizationAdminService = factory.CreateOrganizationService(null); } internal void Trace(string message, params object[] args) { if (string.IsNullOrWhiteSpace(message) || this.TracingService == null) { return; } if (this.PluginExecutionContext == null) { this.TracingService.Trace(message, args); } else { this.TracingService.Trace( "{0}, Correlation Id: {1}, Initiating User: {2}", message, this.PluginExecutionContext.CorrelationId, this.PluginExecutionContext.InitiatingUserId); } } } private Collection>> registeredEvents; /// /// Gets the List of events that the plug-in should fire for. Each List /// Item is a containing the Pipeline Stage, Message and (optionally) the Primary Entity. /// In addition, the fourth parameter provide the delegate to invoke on a matching registration. /// protected Collection>> RegisteredEvents { get { if (this.registeredEvents == null) { this.registeredEvents = new Collection>>(); } return this.registeredEvents; } } /// /// Gets or sets the name of the child class. /// /// The name of the child class. protected string ChildClassName { get; private set; } /// /// Initializes a new instance of the class. /// /// The of the derived class. internal Plugin(Type childClassName) { this.ChildClassName = childClassName.ToString(); } /// /// Executes the plug-in. /// /// The service provider. /// /// For improved performance, Microsoft Dynamics CRM caches plug-in instances. /// The plug-in's Execute method should be written to be stateless as the constructor /// is not called for every invocation of the plug-in. Also, multiple system threads /// could execute the plug-in at the same time. All per invocation state information /// is stored in the context. This means that you should not use global variables in plug-ins. /// public void Execute(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new ArgumentNullException("serviceProvider"); } // Construct the Local plug-in context. LocalPluginContext localcontext = new LocalPluginContext(serviceProvider); localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Entered {0}.Execute()", this.ChildClassName)); try { // Iterate over all of the expected registered events to ensure that the plugin // has been invoked by an expected event // For any given plug-in event at an instance in time, we would expect at most 1 result to match. Action entityAction = (from a in this.RegisteredEvents where ( a.Item1 == localcontext.PluginExecutionContext.Stage && a.Item2 == localcontext.PluginExecutionContext.MessageName && (string.IsNullOrWhiteSpace(a.Item3) ? true : a.Item3 == localcontext.PluginExecutionContext.PrimaryEntityName) ) select a.Item4).FirstOrDefault(); if (entityAction != null) { localcontext.Trace(string.Format( CultureInfo.InvariantCulture, "{0} is firing for Entity: {1}, Message: {2}", this.ChildClassName, localcontext.PluginExecutionContext.PrimaryEntityName, localcontext.PluginExecutionContext.MessageName)); entityAction.Invoke(localcontext); // now exit - if the derived plug-in has incorrectly registered overlapping event registrations, // guard against multiple executions. return; } } catch (FaultException e) { localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Exception: {0}", e.ToString())); // Handle the exception. throw; } finally { localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Exiting {0}.Execute()", this.ChildClassName)); } } // Delegate A/S added: /// /// The methods exposes the RegisteredEvents as a collection of tuples /// containing: /// - The full assembly name of the class containing the RegisteredEvents /// - The Pipeline Stage /// - The Event Operation /// - Logical Entity Name (or empty for all) /// This will allow to instantiate each plug-in and iterate through the /// PluginProcessingSteps in order to sync the code repository with /// MS CRM without have to use any extra layer to perform this operation /// /// /// public IEnumerable> PluginProcessingSteps() { var className = this.ChildClassName; foreach (var events in this.RegisteredEvents) { yield return new Tuple (className, events.Item1, events.Item2, events.Item3); } } #region Additional helper methods protected static T GetImage(LocalPluginContext context, ImageType imageType, string name) where T : Entity { EntityImageCollection collection = null; if (imageType == ImageType.PreImage) { collection = context.PluginExecutionContext.PreEntityImages; } else if (imageType == ImageType.PostImage) { collection = context.PluginExecutionContext.PostEntityImages; } Entity entity; if (collection != null && collection.TryGetValue(name, out entity)) { return entity.ToEntity(); } else { return null; } } protected static T GetImage(LocalPluginContext context, ImageType imageType) where T : Entity { return GetImage(context, imageType, imageType.ToString()); } protected static T GetPreImage(LocalPluginContext context, string name = "PreImage") where T : Entity { return GetImage(context, ImageType.PreImage, name); } protected static T GetPostImage(LocalPluginContext context, string name = "PostImage") where T : Entity { return GetImage(context, ImageType.PostImage, name); } #endregion #region PluginStepConfig retrieval /// /// Made by Delegate A/S /// Get the plugin step configurations. /// /// List of steps public IEnumerable>> PluginProcessingStepConfigs() { var className = this.ChildClassName; foreach (var config in this.PluginStepConfigs) { yield return new Tuple>( new StepConfig(className, config._ExecutionStage, config._EventOperation, config._LogicalName), new ExtendedStepConfig(config._Deployment, config._ExecutionMode, config._Name, config._ExecutionOrder, config._FilteredAttributes, config._UserContext.ToString()), config.GetImages()); } } protected PluginStepConfig RegisterPluginStep( EventOperation eventOperation, ExecutionStage executionStage, Action action) where T : Entity { PluginStepConfig stepConfig = new PluginStepConfig(eventOperation, executionStage); this.PluginStepConfigs.Add((IPluginStepConfig)stepConfig); this.RegisteredEvents.Add( new Tuple>( stepConfig._ExecutionStage, stepConfig._EventOperation, stepConfig._LogicalName, new Action(action))); return stepConfig; } private Collection pluginConfigs; private Collection PluginStepConfigs { get { if (this.pluginConfigs == null) { this.pluginConfigs = new Collection(); } return this.pluginConfigs; } } #endregion } #region PluginStepConfig made by Delegate A/S interface IPluginStepConfig { string _LogicalName { get; } string _EventOperation { get; } int _ExecutionStage { get; } string _Name { get; } int _Deployment { get; } int _ExecutionMode { get; } int _ExecutionOrder { get; } string _FilteredAttributes { get; } Guid _UserContext { get; } IEnumerable GetImages(); } /// /// Made by Delegate A/S /// Class to encapsulate the various configurations that can be made /// to a plugin step. /// public class PluginStepConfig : IPluginStepConfig where T : Entity { public string _LogicalName { get; private set; } public string _EventOperation { get; private set; } public int _ExecutionStage { get; private set; } public string _Name { get; private set; } public int _Deployment { get; private set; } public int _ExecutionMode { get; private set; } public int _ExecutionOrder { get; private set; } public Guid _UserContext { get; private set; } public Collection _Images = new Collection(); public Collection _FilteredAttributesCollection = new Collection(); public string _FilteredAttributes { get { if (this._FilteredAttributesCollection.Count == 0) return null; return string.Join(",", this._FilteredAttributesCollection).ToLower(); } } public PluginStepConfig(EventOperation eventOperation, ExecutionStage executionStage) { this._LogicalName = Activator.CreateInstance().LogicalName; this._EventOperation = eventOperation.ToString(); this._ExecutionStage = (int)executionStage; this._Deployment = (int)Deployment.ServerOnly; this._ExecutionMode = (int)ExecutionMode.Synchronous; this._ExecutionOrder = 1; this._UserContext = Guid.Empty; } private PluginStepConfig AddFilteredAttribute(Expression> lambda) { this._FilteredAttributesCollection.Add(GetMemberName(lambda)); return this; } public PluginStepConfig AddFilteredAttributes(params Expression>[] lambdas) { foreach (var lambda in lambdas) this.AddFilteredAttribute(lambda); return this; } public PluginStepConfig SetDeployment(Deployment deployment) { this._Deployment = (int)deployment; return this; } public PluginStepConfig SetExecutionMode(ExecutionMode executionMode) { this._ExecutionMode = (int)executionMode; return this; } public PluginStepConfig SetName(string name) { this._Name = name; return this; } public PluginStepConfig SetExecutionOrder(int executionOrder) { this._ExecutionOrder = executionOrder; return this; } public PluginStepConfig SetUserContext(Guid userContext) { this._UserContext = userContext; return this; } public PluginStepConfig AddImage(ImageType imageType) { return this.AddImage(imageType, null); } public PluginStepConfig AddImage(ImageType imageType, params Expression>[] attributes) { return this.AddImage(imageType.ToString(), imageType.ToString(), imageType, attributes); } public PluginStepConfig AddImage(string name, string entityAlias, ImageType imageType) { return this.AddImage(name, entityAlias, imageType, null); } public PluginStepConfig AddImage(string name, string entityAlias, ImageType imageType, params Expression>[] attributes) { this._Images.Add(new PluginStepImage(name, entityAlias, imageType, attributes)); return this; } public IEnumerable GetImages() { foreach (var image in this._Images) { yield return new ImageTuple(image.Name, image.EntityAlias, image.ImageType, image.Attributes); } } /// /// Container for information about images attached to steps /// public class PluginStepImage { public string Name { get; private set; } public string EntityAlias { get; private set; } public int ImageType { get; private set; } public string Attributes { get; private set; } public PluginStepImage(string name, string entityAlias, ImageType imageType, Expression>[] attributes) { this.Name = name; this.EntityAlias = entityAlias; this.ImageType = (int)imageType; if (attributes != null && attributes.Length > 0) { this.Attributes = string.Join(",", attributes.Select(x => PluginStepConfig.GetMemberName(x))).ToLower(); } else { this.Attributes = null; } } } private static string GetMemberName(Expression> lambda) { MemberExpression body = lambda.Body as MemberExpression; if (body == null) { UnaryExpression ubody = (UnaryExpression)lambda.Body; body = ubody.Operand as MemberExpression; } return body.Member.Name; } } class AnyEntity : Entity { public AnyEntity() : base("") { } } /** * Enums to help setup plugin steps */ public enum ExecutionMode { Synchronous = 0, Asynchronous = 1, } public enum ExecutionStage { PreValidation = 10, PreOperation = 20, PostOperation = 40, } public enum Deployment { ServerOnly = 0, MicrosoftDynamicsCRMClientforOutlookOnly = 1, Both = 2, } // EventOperation based on CRM 2016 public enum EventOperation { AddItem, AddListMembers, AddMember, AddMembers, AddPrincipalToQueue, AddPrivileges, AddProductToKit, AddRecurrence, AddToQueue, AddUserToRecordTeam, ApplyRecordCreationAndUpdateRule, Assign, AssignUserRoles, Associate, BackgroundSend, Book, CalculatePrice, Cancel, CheckIncoming, CheckPromote, Clone, CloneProduct, Close, CopyDynamicListToStatic, CopySystemForm, Create, CreateException, CreateInstance, CreateKnowledgeArticleTranslation, CreateKnowledgeArticleVersion, Delete, DeleteOpenInstances, DeliverIncoming, DeliverPromote, DetachFromQueue, Disassociate, Execute, ExecuteById, Export, ExportAll, ExportCompressed, ExportCompressedAll, GenerateSocialProfile, GetDefaultPriceLevel, GrantAccess, Handle, Import, ImportAll, ImportCompressedAll, ImportCompressedWithProgress, ImportWithProgress, LockInvoicePricing, LockSalesOrderPricing, Lose, Merge, ModifyAccess, PickFromQueue, Publish, PublishAll, PublishTheme, QualifyLead, Recalculate, ReleaseToQueue, RemoveFromQueue, RemoveItem, RemoveMember, RemoveMembers, RemovePrivilege, RemoveProductFromKit, RemoveRelated, RemoveUserFromRecordTeam, RemoveUserRoles, ReplacePrivileges, Reschedule, Retrieve, RetrieveExchangeRate, RetrieveFilteredForms, RetrieveMultiple, RetrievePersonalWall, RetrievePrincipalAccess, RetrieveRecordWall, RetrieveSharedPrincipalsAndAccess, RetrieveUnpublished, RetrieveUnpublishedMultiple, RetrieveUserQueues, RevokeAccess, Route, RouteTo, Send, SendFromTemplate, SetLocLabels, SetRelated, SetState, SetStateDynamicEntity, TriggerServiceEndpointCheck, UnlockInvoicePricing, UnlockSalesOrderPricing, Update, ValidateRecurrenceRule, Win } public enum ImageType { PreImage = 0, PostImage = 1, Both = 2, } #endregion }