CRM 3.0 callouts: pre and post events before plug-ins

How CRM 3.0 ran custom server code. .NET classes derived from CrmCalloutBase, registered in callout.config.xml on the server, with pre-callouts that could change or stop an operation and post-callouts that ran after the data was committed.

Answer first

A CRM 3.0 callout is a .NET class, derived from CrmCalloutBase, that the server calls when something happens to a record. It’s registered in an XML file, callout.config.xml, in the server’s assembly folder. There’s no registration tool and no database record.

The model has two kinds of callout:

  • Pre-callouts run before the operation. They receive the record as XML, can change it, and can let the operation continue, stop it quietly, or abort it with an error for the user.
  • Post-callouts run after the change has been committed. They can see before and after values of the attributes you ask for, but can’t undo anything.

Both kinds run synchronously: the user’s request waits for them.

Source for this page: the Server Programming Guide in Microsoft’s CRM 3.0 SDK help, and its sample code.

The events

The SDK exposes seven operations, most as a pre and post pair:

Operation Before After
Create PreCreate PostCreate
Update PreUpdate PostUpdate
Delete PreDelete PostDelete
Assign to a new owner PreAssign PostAssign
Change state PreSetState PostSetState
Merge two records PreMerge PostMerge
E-mail PreSend (before sending) PostDeliver (after delivery)

Not every entity supports every event. The SDK has a separate table of which entities and user actions raise which events, and says the 3.0 model covers fewer entities than the 1.x model did.

Building one

  • Derive from CrmCalloutBase in the Microsoft.Crm.Callout namespace, and override the methods you need. Any .NET language works. Unlike 1.x callouts, nothing is registered in COM+.
  • Reference Microsoft.Crm.Platform.Callout.Base.dll from the server CD’s bin\assembly folder. You don’t deploy it with your callout; the server already has it in the global assembly cache.
  • One method per entity and event per class. A class can have only one PreCreate for account. Two separate PreCreate handlers for account need two classes, or one method that does both jobs.
  • The signatures are fixed. A pre-callout receives the user context, the entity context, the entity as XML by ref, and an error message by ref, and returns a PreCalloutReturnValue. For example, PreCreate is PreCreate(CalloutUserContext userContext, CalloutEntityContext entityContext, ref string entityXml, ref string errorMessage). Post-callouts return nothing and receive pre-image and post-image XML instead.
  • The XML is a serialized DynamicEntity, which the SDK shows deserializing with XmlSerializer against the http://schemas.microsoft.com/crm/2006/WebServices namespace.
  • To call the web services from a callout, add a web reference to CrmService, as for any other client. See the CRM 3.0 web services.

Pre-callouts

  • They run serially, in the order they appear in the configuration file. Each gets the attribute values as the previous callout left them. If three callouts handle PreCreate for account, the third sees whatever the first two changed.

  • The full set of attributes is passed in, and the callout can hand a modified set back through entityXml.

  • The return value decides what happens next:

    PreCalloutReturnValue Later callouts The operation
    Continue (the default) Run Goes ahead
    Stop Don’t run Doesn’t happen, and no error is shown
    Abort Don’t run Doesn’t happen, and errorMessage is shown to the user
  • The error message only shows on Abort. It’s ignored for Stop and Continue. The SDK also advises checking whether errorMessage already holds text from an earlier callout, and appending rather than overwriting.

Post-callouts

  • The data is already committed. If the platform operation itself fails, post-callouts aren’t called at all.
  • They still run synchronously, inside the system method call, so a slow post-callout is a slow save.
  • You choose which values they see. In the configuration file, prevalue lists the attributes to capture before the operation, and postvalue those after it. @all passes every attribute that isn’t null.

callout.config.xml

The configuration file and your callout assemblies go together in:

<installdir>\Program Files\Microsoft CRM\server\bin\assembly\

The SDK says the folder’s permissions should allow local administrators only. Installing or removing a callout therefore needs an administrator.

Each registration is a callout element naming an entity and an event, containing a subscription:

Setting What it does
assembly Required. The DLL containing the callout.
class Required. The class that implements the handler.
onerror What happens when the callout throws: abort (the default) stops the operation and shows the exception message; ignore carries on. Either way the exception is written to the Application event log.
timeout Seconds before a single call counts as failed. Default 60, maximum 600.
prevalue, postvalue Attributes for the pre-image and post-image. Post-callouts only.

Things about this file that caused real trouble:

  • Order is everything. Callouts for the same entity and event run in file order. An add-on that depends on running first or last has to say so, and installers were told to back up the existing file and add their own nodes rather than replace it.
  • Changes need restarts. After changing the file or a callout DLL: reset IIS, restart the Workflow Service, and restart the Bulk E-mail Service.
  • Some mistakes fail silently. A registration for a method that doesn’t exist, or for an event the entity doesn’t support, does nothing, and nothing appears in the web application. Loading the file does write warnings to the event log, one per problem.
  • The SDK contradicts itself on details. The configuration reference says the version attribute should always be 3.0, but its own sample files use 1.0 and 2.0. It also calls the xmlns on the root element required, while its reference example leaves it out. Compare against a working file rather than the prose.

Whose account it runs as

A callout runs under the identity of the CRMAppPool application pool in IIS, Network Service by default. To act as the user who triggered the event, a callout that calls CrmService sets the CallerIdValue header to the UserId from its user context. That impersonation only works if the account the code runs under is a member of the PrivUserGroup Active Directory group.

Traps the SDK warns about

  • Infinite loops. A PostUpdate callout that updates the same record fires itself again. The SDK’s advice:
    • Change the record in a pre-callout instead.
    • Otherwise, make the second update recognisable: a dedicated user, or a flag field that users can’t set.
    • Or hand the work to Microsoft Message Queuing (MSMQ).
  • Deadlocks when a post-callout tries to change the record that raised the event.
  • No console. Callouts mustn’t write to the system console, directly or through Win32 console functions.
  • Nothing calls Dispose. Free resources yourself, for example in a finally block.
  • Error handling is on you. Wrap everything in try/catch without swallowing errors, log enough context to the event log, and fail closed. If a privilege check can’t be completed, assume the user doesn’t have it.
  • Tracing of callout calls, return codes and error messages is switched on through the LogPerfData registry setting. Verbose tracing adds parameter values.

Coming from CRM 1.2

The SDK describes the 1.x callout model as deprecated in 3.0 but still supported. Existing 1.x post-callouts upgrade and keep working, as long as they don’t use classes 3.0 dropped, such as the old activity objects.

What came next

Sources: Microsoft’s CRM 4.0 and CRM 2011 SDKs.

CRM 4.0 replaced callouts with plug-ins, registered as records rather than listed in a file. See CRM 4.0 plug-ins, and callouts to plug-ins for a side-by-side comparison and how to port a callout.

3.0 callouts still ran on CRM 4.0, which the 4.0 SDK calls a deprecated model, but not quite as before:

  • Plug-ins run first. In any stage, registered plug-ins run before callouts, so a callout’s Stop or Abort can’t prevent them.
  • Stop behaves like Abort, but the user sees a standard error instead of your message.
  • onerror is ignored. An exception from a callout is always shown to the user, even with onerror="ignore".
  • Callouts are cached and shared across threads. 3.0 created a new instance for every call; 4.0 reuses them, so class-level variables can corrupt data. The SDK’s fixes are to hold no class-level state, or to register a proxy class that creates a fresh callout for each call.
  • Some state changes raise different messages. Closing a case, for example, raises CloseIncident in 4.0 rather than a set-state event, so an upgraded callout has to be registered against the new message.
  • Multiple organizations. Legacy callouts and workflow assemblies are directed to the default organization.
  • No loop detection. Callouts don’t get 4.0’s infinite loop detection, and the SDK warns that a plug-in and a callout can trigger each other forever.

CRM 2011 doesn’t run plug-ins created for CRM 3.0. Both the 4.0 and 2011 SDKs still allow plug-in assemblies on disk in the same server\bin\assembly folder, and describe that as backward compatibility with callouts.

Modern equivalent

A plug-in registered as a step on a numbered pipeline stage. The event pipeline stages page covers which stages run inside the database transaction.

Sources

  1. Microsoft CRM 3.0 SDK (compiled help, crmsdk3_0.chm, 2007 edition) Microsoft · Primary, archived Private archive: CRM-SDK-Rescue/3.0-SDK/crmsdk3_0.chm Microsoft’s SDK help for CRM 3.0: the Server Programming Guide’s callout model, method signatures, callout.config.xml reference, error handling and tracing, and the CrmService and MetadataService web services at /mscrmservices/2006/, including security, impersonation, DynamicEntity, FetchXML and QueryExpression. The owner’s copy came from a third-party software archive; it is a valid help file whose 6,937 pages all carry Microsoft’s 2007 copyright. Read as documents only, never executed. Quote sparingly and cite; do not republish.
  2. Microsoft CRM 3.0 SDK sample code Microsoft · Primary, archived Private archive: CRM-SDK-Rescue/3.0-SDK/sdk samples Microsoft’s sample projects shipped with the 3.0 SDK (C# and VB .NET): callout samples with their callout.config.xml files, CrmService and MetadataService reference samples, and ISV readiness samples. Read only; not built or run.
  3. Microsoft Dynamics CRM 4.0 SDK (compiled help, CrmSdk4.chm, version 4.0.13, November 2010) Microsoft · Primary, archived Private archive: CRM-SDK-Rescue/4.0-SDK/sdk/crmsdk4.chm Microsoft's SDK help for CRM 4.0 Update Rollup 13 and CRM Online: plug-in development (IPlugin, the execution context, the unsecure and secure constructor strings), the event execution pipeline and its parent and child pipelines, registration and deployment, impersonation, error handling, offline plug-ins, execution of CRM 3.0 callouts, the 2006 endpoint, and what is new in the 4.0 web services. Extracted without running anything from CrmSdk4.exe, whose Microsoft Authenticode signature is valid and which the Internet Archive captured from download.microsoft.com. All 12,022 pages carry Microsoft's copyright. Quote sparingly and cite; do not republish.
  4. Microsoft Dynamics CRM 2011 SDK (compiled help, CrmSdk2011.chm) Microsoft · Primary, archived Private archive: CRM-SDK-Rescue/2011-SDK_from_desk/SDK/CrmSdk2011.chm Microsoft's own SDK help for the 2011 release, from an owner-held copy. Static reading only; the package is never executed and is not part of this repository. Quote sparingly and cite; do not republish.