What’s passed into a plugin
The CRM 2013 SDK is a great document but a bit dry and difficult to get you started but once you are up and running there are lots of great explanations of how things work, this page is very useful for people writing plugins
It explains what is actually passed into the plugin, the ServiceProvider has lots of goodies in like
IPluginExecutionContext
This has lots of data in like the user who triggered the plugin
IOrganizationService
Use this to access CRM and Create, Read, Update and Delete CRM records
context.InputParameters and context.OutPutParameters
The input parameters hold a collection and in that collect is the Target entity object, this has the values changed from the account update
there is also the tracing object
In the example I am going to use all three of these item and in most plugins you will use them but the page also has this table which explains what objects are passed into the context.InputParameters because it’s not always the Target object you should look for. The picture below is a screenshot from the page
So what you should understand from the table above is, when a record or action triggers a plugin then usually either the entity is passed into the context.InputParameters or it’s an EntityReference. You usually retrieve and cast this object out and then pass this to your plugin logic.
The one that used to catch me out was SetStateRequest passing the EntityMoniker. What happened to me was I copied my boilerplate plugin code and I couldn’t understand why it wasn’t going into the code and running. The reason was I was checking to see if the Target existed and if it did casting it to an entity and passing that into another method to do the work but it never went into my code.
So I had to debug the code to find there was no Target! finally I found the oddly named EntityMoniker and I worked out what to do from there.
Plugin Stages
In the first plugin I couldn’t remember what the stages of a plugin were and their significance, all I could remember was they were linked to the database transaction and something about numbers going up in 10.
So I have done my homework and once again found some great information in the CRM 2013 SDK
The page has an interesting overview of the whole CRM architecture and it simplifies the stages
PRE-EVENT
PLATFORM CORE
POST-EVENT
The picture below is a screen from the Event Execution Pipeline page
As you know from registering plugins we can only register plugins
Pre-Validation – 10
Pre-Event – 20
Post-Event -40
The actually database transaction finishes in step 30 – Platform core operation and you can interfere with this, this is where the system plugins will run and kick off and updating system values and triggering other things.
An interesting point is the Pre-Validation, this is triggered before security checks have happened, so before CRM has checked the user can update the record maybe, basically before CRM checks the security roles and the PrincipalObjectAccess table (where all the security stuff is stored for users) is checked.
So that is the end of my quick plugin theory lesson and back onto the code, So along with the changing the plugin filter records (which you can see the RegisterFile.crmregister) I changed the code.
I now want to trigger the update on the change of AccountRating and I am going to read the value, which those of you who watched the video will know, OptionSetValue’s in CRM are held as ints. CRM also holds Metadata on the OptionSet which holds the text.
My code is going to run based on the Int value because I think maybe in the future people might change the text in the OptionSet from Gold, Silver, Bronze to 1, 2, 3 or Great, OK, Rubbish.
I also get the guid of the user (entity SystemUser)who triggered the plugin by updating the AccountRating field. This value is passed in with the plugin context and can be retrieved
Guid userId = context.InitiatingUserId;
There are two different user guid passed into the context and you should know which is which
context.initiatinguserid: the systemuser GUID who actually triggered the plugin
context.userid : gets the impersonated systemuser GUID
context.userid : gets the impersonated systemuser GUID
In my case I want the user who triggered the plugin
I then lookup the fullname and domainname of the user because this is more useful than a guid to end users but the guid is great for retrieving more data about that record. I use the IOrganisationService to retrieve more fields from the record. To do this all I need is the entity logical name, guid and the fields I want to retrieve
Entity userName = service.Retrieve(“systemuser”, userId, newMicrosoft.Xrm.Sdk.Query.ColumnSet(new[] { “domainname”, “fullname” }));
Then it’s a case of concatenting them into string and adding this to the account entity being updated.
The full code is below
// <copyright file="PreAccountUpdate.cs" company="Microsoft">
// Copyright (c) 2014 All Rights Reserved
// </copyright>
// <author>Microsoft</author>
// <date>4/3/2014 12:04:45 AM</date>
// <summary>Implements the PreAccountUpdate Plugin.</summary>
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.1
// </auto-generated>
namespace HoskCRMDev2013.Plugins
{
using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
// Copyright (c) 2014 All Rights Reserved
// </copyright>
// <author>Microsoft</author>
// <date>4/3/2014 12:04:45 AM</date>
// <summary>Implements the PreAccountUpdate Plugin.</summary>
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.1
// </auto-generated>
namespace HoskCRMDev2013.Plugins
{
using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
/// <summary>
/// PreAccountUpdate Plugin.
/// Fires when the following attributes are updated:
/// All Attributes
/// </summary>
public class PreAccountUpdate: Plugin
{
/// <summary>
/// Initializes a new instance of the <see cref="PreAccountUpdate"/> class.
/// </summary>
public PreAccountUpdate()
: base(typeof(PreAccountUpdate))
{
base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(20, "Update", "account", new Action<LocalPluginContext>(ExecutePreAccountUpdate)));
/// PreAccountUpdate Plugin.
/// Fires when the following attributes are updated:
/// All Attributes
/// </summary>
public class PreAccountUpdate: Plugin
{
/// <summary>
/// Initializes a new instance of the <see cref="PreAccountUpdate"/> class.
/// </summary>
public PreAccountUpdate()
: base(typeof(PreAccountUpdate))
{
base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(20, "Update", "account", new Action<LocalPluginContext>(ExecutePreAccountUpdate)));
// Note : you can register for more events here if this plugin is not specific to an individual entity and message combination.
// You may also need to update your RegisterFile.crmregister plug-in registration file to reflect any change.
}
// You may also need to update your RegisterFile.crmregister plug-in registration file to reflect any change.
}
/// <summary>
/// Executes the plug-in.
/// </summary>
/// <param name="localContext">The <see cref="LocalPluginContext"/> which contains the
/// <see cref="IPluginExecutionContext"/>,
/// <see cref="IOrganizationService"/>
/// and <see cref="ITracingService"/>
/// </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>
protected void ExecutePreAccountUpdate(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
/// Executes the plug-in.
/// </summary>
/// <param name="localContext">The <see cref="LocalPluginContext"/> which contains the
/// <see cref="IPluginExecutionContext"/>,
/// <see cref="IOrganizationService"/>
/// and <see cref="ITracingService"/>
/// </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>
protected void ExecutePreAccountUpdate(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
// TODO: Implement your custom Plug-in business logic.
// Obtain the execution context from the service provider.
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
// Obtain the execution context from the service provider.
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
// Obtain the target entity from the input parmameters.
Entity entity = (Entity)context.InputParameters["Target"];
string entityDescription = "HOSK CRM DEV FIRST PLUGIN HAS RUN AND UPDATED THE DESCRIPTION";
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
// Obtain the target entity from the input parmameters.
Entity entity = (Entity)context.InputParameters["Target"];
string entityDescription = "HOSK CRM DEV FIRST PLUGIN HAS RUN AND UPDATED THE DESCRIPTION";
//accountratingcode
if (entity.LogicalName == "account")
{
if (entity.LogicalName == "account")
{
try
{
if (entity.Attributes.Contains("accountratingcode"))
{
Guid userId = context.InitiatingUserId;
Entity userName = service.Retrieve("systemuser", userId, newMicrosoft.Xrm.Sdk.Query.ColumnSet(new[] { "domainname", "fullname" }));
string fullname = (string)userName.Attributes["fullname"];
string domainName = (string)userName.Attributes["domainname"];
entityDescription = "user " + fullname + "has the domain name of " + domainName;
entityDescription = entityDescription + Environment.NewLine + DateTime.UtcNow.ToString();
OptionSetValue accountRatingCode = (OptionSetValue)entity.Attributes["accountratingcode"];
if (accountRatingCode.Value == 1)
{
entityDescription = entityDescription + Environment.NewLine + "account rating = gold";
}
else if (accountRatingCode.Value == 100000000)
{
entityDescription = entityDescription + Environment.NewLine + "account rating = silver";
} else {
entityDescription = entityDescription + Environment.NewLine + "account rating = bronze";
}
{
if (entity.Attributes.Contains("accountratingcode"))
{
Guid userId = context.InitiatingUserId;
Entity userName = service.Retrieve("systemuser", userId, newMicrosoft.Xrm.Sdk.Query.ColumnSet(new[] { "domainname", "fullname" }));
string fullname = (string)userName.Attributes["fullname"];
string domainName = (string)userName.Attributes["domainname"];
entityDescription = "user " + fullname + "has the domain name of " + domainName;
entityDescription = entityDescription + Environment.NewLine + DateTime.UtcNow.ToString();
OptionSetValue accountRatingCode = (OptionSetValue)entity.Attributes["accountratingcode"];
if (accountRatingCode.Value == 1)
{
entityDescription = entityDescription + Environment.NewLine + "account rating = gold";
}
else if (accountRatingCode.Value == 100000000)
{
entityDescription = entityDescription + Environment.NewLine + "account rating = silver";
} else {
entityDescription = entityDescription + Environment.NewLine + "account rating = bronze";
}
if (entity.Attributes.Contains("description"))
{
//string entityDescription = (string)entity.Attributes["description"];
//entityDescription = "HOSK CRM DEV FIRST PLUGIN HAS RUN AND UPDATED THE DESCRIPTION";
entity.Attributes["description"] = entityDescription;
}
else
{
//entityDescription = "HOSK CRM DEV FIRST PLUGIN HAS RUN AND UPDATED THE DESCRIPTION";
entity.Attributes.Add("descriptiontityDescription);
}
{
//string entityDescription = (string)entity.Attributes["description"];
//entityDescription = "HOSK CRM DEV FIRST PLUGIN HAS RUN AND UPDATED THE DESCRIPTION";
entity.Attributes["description"] = entityDescription;
}
else
{
//entityDescription = "HOSK CRM DEV FIRST PLUGIN HAS RUN AND UPDATED THE DESCRIPTION";
entity.Attributes.Add("descriptiontityDescription);
}
}
}
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
}
}
}
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
}
}
}