Skip to content

Instantly share code, notes, and snippets.

@NickPl
Last active December 10, 2018 19:21
Show Gist options
  • Save NickPl/5b652b4e99374df7ec84ae3f1b26a603 to your computer and use it in GitHub Desktop.
Save NickPl/5b652b4e99374df7ec84ae3f1b26a603 to your computer and use it in GitHub Desktop.
Blog XrmMtl - Essential Plugin base class
namespace Xrm.Plugins.Sandbox
{
using Microsoft.Xrm.Sdk;
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.ServiceModel;
using System.ComponentModel.Design;
/// <summary>
/// Base class for all Plugins.
/// </summary>
public class Plugin : IPlugin
{
protected class LocalPluginContext
{
internal IServiceProvider ServiceProvider
{
get;
private set;
}
internal IOrganizationService OrganizationService
{
get;
private set;
}
internal IPluginExecutionContext PluginExecutionContext
{
get;
private set;
}
internal ITracingService TracingService
{
get;
private set;
}
internal EntityReference Assignee
{
get
{
if (PluginExecutionContext.InputParameters.Contains("Assignee") && PluginExecutionContext.InputParameters["Assignee"] is EntityReference)
return (EntityReference)PluginExecutionContext.InputParameters["Assignee"];
else
return null;
}
}
internal EntityReference EntityMoniker
{
get
{
if (PluginExecutionContext.InputParameters.Contains("EntityMoniker") && PluginExecutionContext.InputParameters["EntityMoniker"] is EntityReference)
return (EntityReference)PluginExecutionContext.InputParameters["EntityMoniker"];
else
return null;
}
}
/// <summary>
/// To use with the Delete message
/// </summary>
internal EntityReference EntityReference
{
get
{
if (PluginExecutionContext.InputParameters.Contains("Target") && PluginExecutionContext.InputParameters["Target"] is EntityReference)
return (EntityReference)PluginExecutionContext.InputParameters["Target"];
else
return null;
}
}
/// <summary>
/// Can be casted into IncidentResolution
/// </summary>
internal Entity IncidentResolution
{
get
{
if (PluginExecutionContext.InputParameters.Contains("IncidentResolution") && PluginExecutionContext.InputParameters["IncidentResolution"] is Entity)
return (Entity)PluginExecutionContext.InputParameters["IncidentResolution"];
else
return null;
}
}
//internal OpportunityClose OpportunityClose
//{
// get
// {
// if (PluginExecutionContext.InputParameters.Contains("OpportunityClose") && PluginExecutionContext.InputParameters["OpportunityClose"] is Entity)
// return ((Entity)PluginExecutionContext.InputParameters["OpportunityClose"]).ToEntity<OpportunityClose>();
// else
// return null;
// }
//}
internal OptionSetValue State
{
get
{
if (PluginExecutionContext.InputParameters.Contains("State") && PluginExecutionContext.InputParameters["State"] is OptionSetValue)
return PluginExecutionContext.InputParameters["State"] as OptionSetValue;
else if (Target.Attributes.Contains("statecode"))
return Target.Attributes["statecode"] as OptionSetValue;
else
return null;
}
}
internal OptionSetValue Status
{
get
{
if (PluginExecutionContext.InputParameters.Contains("Status") && PluginExecutionContext.InputParameters["Status"] is OptionSetValue)
return PluginExecutionContext.InputParameters["Status"] as OptionSetValue;
else if (Target.Attributes.Contains("statuscode"))
return Target.Attributes["statuscode"] as OptionSetValue;
else
return null;
}
}
internal Entity Target
{
get
{
if (PluginExecutionContext.InputParameters.Contains("Target") && PluginExecutionContext.InputParameters["Target"] is Entity)
return PluginExecutionContext.InputParameters["Target"] as Entity;
else if (PluginExecutionContext.InputParameters.Contains("Target") && PluginExecutionContext.InputParameters["Target"] is EntityReference)
{
// Just to mess with you Microsoft changed the type of the target on an assign.
var targetOnAssign = PluginExecutionContext.InputParameters["Target"] as EntityReference;
return new Entity(targetOnAssign.LogicalName)
{
Id = targetOnAssign.Id
};
}
else
return new Entity(PluginExecutionContext.PrimaryEntityName);
}
}
internal Entity TargetState
{
get
{
var entity = new Entity();
if (EntityMoniker != null)
entity = new Entity()
{
Id = EntityMoniker.Id,
LogicalName = EntityMoniker.LogicalName,
};
//else if (OpportunityClose != null)
// entity = OpportunityClose.OpportunityId.ToEntity();
else throw new Exception("TargetState can't find the type of the entity");
entity.Attributes.Add("statecode", State);
entity.Attributes.Add("statuscode", Status);
return entity;
}
}
/// <summary>
/// Get the first preImage
/// </summary>
internal Entity PreImage
{
get
{
return PluginExecutionContext.PreEntityImages != null && PluginExecutionContext.PreEntityImages.Count >= 1 ? PluginExecutionContext.PreEntityImages.FirstOrDefault().Value : null;
}
}
/// <summary>
/// Get the first postImage
/// </summary>
internal Entity PostImage
{
get
{
return PluginExecutionContext.PostEntityImages != null && PluginExecutionContext.PostEntityImages.Count >= 1 ? PluginExecutionContext.PostEntityImages.FirstOrDefault().Value : null;
}
}
/// <summary>
/// Merge - Id of the entity being merge to the parent entity
/// </summary>
internal Guid? SubordinatedId
{
get
{
if (PluginExecutionContext.InputParameters.Contains("SubordinateId") && PluginExecutionContext.InputParameters["SubordinateId"] is Guid)
return (Guid)PluginExecutionContext.InputParameters["SubordinateId"];
else
return null;
}
}
/// <summary>
/// Merge
/// </summary>
internal Entity UpdateContent
{
get
{
if (PluginExecutionContext.InputParameters.Contains("UpdateContent") && PluginExecutionContext.InputParameters["UpdateContent"] is Entity)
return PluginExecutionContext.InputParameters["UpdateContent"] as Entity;
else
return null;
}
}
/// <summary>
/// Merge
/// </summary>
internal bool? PerformParentingChecks
{
get
{
if (PluginExecutionContext.InputParameters.Contains("PerformParentingChecks") && PluginExecutionContext.InputParameters["PerformParentingChecks"] is bool)
return PluginExecutionContext.InputParameters["PerformParentingChecks"] as bool?;
else
return null;
}
}
private LocalPluginContext()
{
}
internal LocalPluginContext(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new ArgumentNullException("serviceProvider");
}
// Important to be able to impersonify
this.ServiceProvider = 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);
}
internal void Trace(string message)
{
if (string.IsNullOrWhiteSpace(message) || this.TracingService == null)
{
return;
}
if (this.PluginExecutionContext == null)
{
this.TracingService.Trace(message);
}
else
{
this.TracingService.Trace(
"{0}, Correlation Id: {1}, Initiating User: {2}",
message,
this.PluginExecutionContext.CorrelationId,
this.PluginExecutionContext.InitiatingUserId);
}
}
/// <summary>
/// Adds the target values to the existing preImage
/// </summary>
/// <param name="target"></param>
/// <param name="preImage"></param>
/// <returns></returns>
internal Entity Merge(Entity target, Entity preImage)
{
if (preImage == null)
return target;
else if (target == null)
return PreImage;
Entity resultat = preImage;
foreach (var a in target.Attributes)
{
if (resultat.Attributes.Contains(a.Key))
{
resultat.Attributes[a.Key] = a.Value;
}
else
{
resultat.Attributes.Add(a.Key, a.Value);
}
}
return resultat;
}
internal Entity Merge()
{
return Merge(Target, PreImage);
}
/// <summary>
/// Adds the target values to the existing preImage. Changes won't go to step 30. Use the Target for that.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal T Merge<T>() where T : Entity
{
return Merge(Target, PreImage).ToEntity<T>();
}
internal T MergePost<T>() where T : Entity
{
return Merge(Target, PostImage).ToEntity<T>();
}
internal T MergeDelete<T>() where T : Entity
{
var entityRef = EntityReference;
var entity = new Entity()
{
Id = entityRef.Id,
LogicalName = entityRef.LogicalName
};
return Merge(entity, PreImage).ToEntity<T>();
}
internal T MergeState<T>() where T : Entity
{
var entity = new Entity();
if (EntityMoniker != null)
entity = new Entity()
{
Id = EntityMoniker.Id,
LogicalName = EntityMoniker.LogicalName,
};
//else if (OpportunityClose != null)
// entity = OpportunityClose.OpportunityId.ToEntity();
entity.Attributes.Add("statecode", State);
entity.Attributes.Add("statuscode", Status);
return Merge(entity, PreImage).ToEntity<T>();
}
/// <summary>
/// To use when the plugin is triggered on create, update and SetState
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal T MergeOrEntityMoniker<T>() where T : Entity, new()
{
return Merge<T>() ?? MergeState<T>();
}
/// <summary>
/// To use when the plugin is triggered on create, update, SetState and delete
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal T MergeEverything<T>() where T : Entity, new()
{
// in order of likeliness
switch (PluginExecutionContext.MessageName)
{
case "Create":
case "Update":
return Merge<T>();
case "SetState":
case "SetStateDynamicEntity":
return MergeState<T>();
case "Delete":
return MergeDelete<T>();
default:
return null;
}
}
}
private Collection<Tuple<int, string, string, Action<LocalPluginContext>>> registeredEvents;
/// <summary>
/// Gets the List of events that the plug-in should fire for. Each List
/// Item is a <see cref="System.Tuple"/> containing the Pipeline Stage, Message and (optionally) the Primary Entity.
/// In addition, the fourth parameter provide the delegate to invoke on a matching registration.
/// </summary>
protected Collection<Tuple<int, string, string, Action<LocalPluginContext>>> RegisteredEvents
{
get
{
if (this.registeredEvents == null)
{
this.registeredEvents = new Collection<Tuple<int, string, string, Action<LocalPluginContext>>>();
}
return this.registeredEvents;
}
}
/// <summary>
/// Gets or sets the name of the child class.
/// </summary>
/// <value>The name of the child class.</value>
protected string ChildClassName
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="Plugin"/> class.
/// </summary>
/// <param name="childClassName">The <see cref=" cred="Type"/> of the derived class.</param>
internal Plugin(Type childClassName)
{
this.ChildClassName = childClassName.ToString();
}
/// <summary>
/// Executes the plug-in.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <remarks>
/// 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.
/// </remarks>
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<LocalPluginContext> 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 (Exception ex)
{
/*--------------------------------------------------------------------------------------------------
Si c’est vous qui avez lancé une InvalidPluginExecutionException, alors ce n’est pas imprévue et on
ne fait que la relancer à CRM. Si vous voulez lancer une exception qui doit être formatée en
erreur, lancer une System.Exception à la place.
--------------------------------------------------------------------------------------------------*/
if (ex is InvalidPluginExecutionException)
{
throw;
}
else
{
/*--------------------------------------------------------------------------------------------------
Sinon, c’est une exception imprévue. Alors on formate un message.
--------------------------------------------------------------------------------------------------*/
System.IO.StringWriter msg = new System.IO.StringWriter();
msg.WriteLine("An unexpected error occurred: {0}", ex.Message); //Changez le nom du client/projet
/*--------------------------------------------------------------------------------------------------
Ce qui nous intéresse vraiment au-delà du message de l’exception, ce sont les particularités de
certaines exceptions, par exemple l’erreur SOAP suite à un appel de SDK à CRM (maintenant pris en
charge via OrganizationServiceFault). L’exception reçue est peut-être du type recherché mais il
est aussi probable qu’elle soit un niveau plus bas dans la chaine (InnerException).
--------------------------------------------------------------------------------------------------*/
System.ServiceModel.FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> organizationServiceFault = null;
if (ex is System.ServiceModel.FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
organizationServiceFault = (System.ServiceModel.FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)ex;
if (organizationServiceFault == null && ex.InnerException != null && ex.InnerException is System.ServiceModel.FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
organizationServiceFault = (System.ServiceModel.FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)ex.InnerException;
if (organizationServiceFault != null)
{
/*--------------------------------------------------------------------------------------------------
Formater la OrganizationServiceFault trouvée
--------------------------------------------------------------------------------------------------*/
msg.WriteLine("OrganizationServiceFault details:");
msg.WriteLine("Fault Timestamp: {0}", organizationServiceFault.Detail.Timestamp);
msg.WriteLine("Fault Error Code: {0}", organizationServiceFault.Detail.ErrorCode);
msg.WriteLine("Fault Message: {0}", organizationServiceFault.Detail.Message);
if (organizationServiceFault.Detail.InnerFault != null
&& organizationServiceFault.Message != organizationServiceFault.Detail.InnerFault.Message)
{
msg.WriteLine("Inner Fault Timestamp: {0}", organizationServiceFault.Detail.InnerFault.Timestamp);
msg.WriteLine("Inner Fault Error Code: {0}", organizationServiceFault.Detail.InnerFault.ErrorCode);
msg.WriteLine("Inner Fault Message: {0}", organizationServiceFault.Detail.InnerFault.Message);
}
}
/*--------------------------------------------------------------------------------------------------
On ajoute le stack trac à la fin car c’est lui qui prend le plus de place
--------------------------------------------------------------------------------------------------*/
msg.WriteLine("Stack Trace: {0}", ex.StackTrace);
/*--------------------------------------------------------------------------------------------------
On renvoie le tous à CRM via une InvalidPluginExecutionException au statut Failed
--------------------------------------------------------------------------------------------------*/
localcontext.Trace(msg.ToString());
if (localcontext.Target != null)
{
msg.WriteLine("--Target--");
msg.WriteLine(Crunch(localcontext.Target));
msg.WriteLine("");
}
if (localcontext.PreImage != null)
{
msg.WriteLine("--PreImage--");
// Ajouter le contenu du preimage
msg.WriteLine(Crunch(localcontext.PreImage));
msg.WriteLine("");
}
if (localcontext.PostImage != null)
{
msg.WriteLine("--PostImage--");
// Ajouter le contenu du preimage
msg.WriteLine(Crunch(localcontext.PostImage));
msg.WriteLine("");
}
throw new InvalidPluginExecutionException(OperationStatus.Failed, msg.ToString());
}
}
finally
{
localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Exiting {0}.Execute()", this.ChildClassName));
}
}
private System.Text.StringBuilder Crunch(Entity entity)
{
var msg = new System.Text.StringBuilder();
if (entity == null)
return msg;
// Ajouter le contenu du target
foreach (var attrib in entity.Attributes)
{
string value = string.Empty;
if (attrib.Value == null)
value = "null";
else if (attrib.Value is OptionSetValue)
value = ((OptionSetValue)attrib.Value).Value.ToString();
else if (attrib.Value is EntityReference)
value = string.Format("{0}-{1}-{2}", ((EntityReference)attrib.Value).LogicalName, ((EntityReference)attrib.Value).Id, ((EntityReference)attrib.Value).Name);
else if (attrib.Value is Entity)
value = string.Format("{0}-{1}-{2}", ((Entity)attrib.Value).LogicalName, ((Entity)attrib.Value).Id, ((Entity)attrib.Value).ToEntityReference().Name);
else
value = attrib.Value.ToString();
msg.AppendLine(string.Format("{0}: {1}", attrib.Key, value));
}
return msg;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment