Share via


Business Logic Extensions to Microsoft CRM 1.2: Post-Callouts

 

Microsoft Corporation

January 2004

Applies to:
    Microsoft Customer Relationship Management (CRM) version 1.2

Requires:
   Microsoft CRM version 1.2
   Microsoft Visual Studio .NET

Summary: Learn to use the COM-based callout model to implement custom business logic for Microsoft Business Solutions CRM 1.2. Using this model, independent software vendors (ISVs) can perform business logic in response to Create, Delete, and Update events. (15 printed pages)

Download the sample code for this article: PostCallouts.exe.

Contents

Business Logic Pipeline
Callout Interface
Registering Your Callouts
Sample Callout
Creating a CRM Post Callout Object Using Visual Studio .NET
Additional Information

Business Logic Pipeline

Microsoft® Business Solutions Customer Relationship Management (CRM) exposes a very simple extension mechanism for implementing custom platform-based business logic—a COM-based "callout" model. With this model, application developers are not limited to creating custom business logic only in the application or through workflow processes; it's also possible to construct business logic that runs in the context of a call, but in response to a particular event.

This extension mechanism exposes a callout interface that is based on a simple pipeline model. The model allows for both "pre" and "post" interceptions for the most basic data persistence operations. The platform metadata stores information about each entity. This information can be used to track the list of callout handlers, the class names, and whether a given handler is required for an action. For example, the Account object can have several registered handlers. These handlers are stored in call order, which is determined by priority. When an action occurs, for example Account::Create, the platform checks the metadata for registered event handlers. This information is stored in the metadata and is cached on the platform server. If a handler is registered for notification, the platform creates the handler object and calls the correct handler method, passing the original XML document.

Note   Version 1.0 of the Microsoft CRM SDK supports only post-callouts.

The following figure gives an example of a business logic pipeline that uses callouts.

Figure 1. A business logic pipeline that uses callouts

Callout Interface

The callout interface ICRMCallout is implemented by classes that need to handle business logic events. Because this interface is callable by IDispatch, it is possible to implement the interface by using any COM-callable language.

Note   There are no return values for PostCreate, PostUpdate, and PostDelete.

The following file is the Interface Definition Language (IDL) definition of the ICRMCallout interface:

import "oaidl.idl";
import "ocidl.idl";

[
   object,
   GuidAttribute("F4233E5B-17DC-4661-9ABC-6707A9F99215"),
   dual,
   helpstring("ICRMCallout Interface")
]
interface ICRMCallout : IDispatch
{
   HRESULT PostCreate([in] int ObjectType, [in] BSTR ObjectId, [in] BSTR
     OrigObjectXml);
   HRESULT PostUpdate([in] int ObjectType, [in] BSTR ObjectId, [in] BSTR
     OrigObjectXml);
   HRESULT PostDelete([in] int ObjectType, [in] BSTR ObjectId);
};

Registering Your Callouts

Two steps are required to register your callouts:

  1. Adding your callouts to the Microsoft CRM database
  2. Registering your callouts as a COM+ application

Adding Your Callouts to the Microsoft CRM Database

Three tables in Microsoft CRM that must be updated to register your callouts:

  • The Entity table contains an EventMask flag that must be set. The platform will evaluate information in the Subscriber and EntityEventSubscribers tables only if this flag does not equal zero.

  • The Subscriber table contains a record that registers the GUIDs of each class that implements the ICRMCallout interface.

  • The EntityEventSubscribers table contains a record for each entity/event pair you are registering. This record also contains a bit that indicates whether the callout is enabled or disabled.

    Note   Modifying the Microsoft CRM database generally is not supported. However, the procedures in this article have been approved by Microsoft for registering post-callouts. Be careful when you modify these tables because they are used for Microsoft CRM Integration.

    Table 1. Entity Table

Field Description
EventMask A flag indicating that the platform should evaluate the Subscriber table and the EntityEventSubscribers table for the specified object type.

For a complete list of object type codes, see the ObjectType Class topic in the Reference section of the Microsoft CRM SDK.

Table 2. Subscriber Table

Field Description
SubscriberId A new GUID that identifies the subscriber.
CLSIDorProgId The ClassId that identifies the handler to be called. Note that ProgId is not supported in version 1.0 of Microsoft CRM.
Name A descriptive name for the handler that is to be called.
Description A description for the handler that is to be called.

Table 3. EntityEventSubscribers Table

Field Description
EventId An ID that specifies the event. Valid values are:

2 - Post - Create

8 - Post - Update

32 - Post - Delete

EntityId The GUID for an entity in the metabase. This GUID is retrieved by referencing the ObjectTypeCode. The following SQL statement can be used to retrieve the EntityId:
select entityid from entity where objecttypecode = 
  <desired object type code>

For a complete list of object type codes, see the ObjectType Class topic in the Reference section of the Microsoft CRM SDK.

SubscriberId The SubscriberId field from the Subscriber table for the handler that is to be called.
CalloutOrder The order for the callout.
IsEnable A flag that indicates whether the callout is enabled or disabled. Valid values are:

0 – Disabled

1 – Enabled

IsAsyncHandler Reserved for future use. Value must be 0 (zero).

Sample SQL module

The following SQL module can be used to register your callouts in the Microsoft CRM database. After this module is registered, the callout methods are triggered upon the Create, Update and Delete events of the Account, Contact and CustomerAddress objects.

Note   The platform caches information about subscribers only if EventMask != 0. The value for the CLSIDorProgId argument should be changed to the GUID of the class that implements the ICRMCallout interface.

[SQL]
declare @subscriberid uniqueidentifier
set @subscriberid = newid()

-- Insert Subscriber (Sample Module)
insert into Subscriber(SubscriberId, CLSIDorProgId, Name, Description) 
  values
(@subscriberid, N'{AA4AD2AC-97B8-4d24-8E81-388A655DDBE5}', N'My Sample 
  Callout Module', N'')

--Insert into EntityEventSubscribers
declare @entityid uniqueidentifier

--Account(post-create, post-update, post-delete)
select @entityid = EntityId from Entity where ObjectTypeCode = 1
update Entity set EventMask = EventMask | 1 where ObjectTypeCode = 1

insert into EntityEventSubscribers(EventId, EntityId, SubscriberId, 
  CalloutOrder, IsEnable, IsAsyncHandler) values
(2, @entityid, @subscriberid, 0, 1, 0)
insert into EntityEventSubscribers(EventId, EntityId, SubscriberId, 
  CalloutOrder, IsEnable, IsAsyncHandler) values
(8, @entityid, @subscriberid, 0, 1, 0)
insert into EntityEventSubscribers(EventId, EntityId, SubscriberId, 
  CalloutOrder, IsEnable, IsAsyncHandler) values
(32, @entityid, @subscriberid, 0, 1, 0)

--Contact(post-create, post-update, post-delete)
select @entityid = EntityId from Entity where ObjectTypeCode  = 2
update Entity set EventMask = 1 where ObjectTypeCode = 2

insert into EntityEventSubscribers(EventId, EntityId, SubscriberId, 
  CalloutOrder, IsEnable, IsAsyncHandler) values
(2, @entityid, @subscriberid, 0, 1, 0)
insert into EntityEventSubscribers(EventId, EntityId, SubscriberId, 
  CalloutOrder, IsEnable, IsAsyncHandler) values
(8, @entityid, @subscriberid, 0, 1, 0)
insert into EntityEventSubscribers(EventId, EntityId, SubscriberId, 
  CalloutOrder, IsEnable, IsAsyncHandler) values
(32, @entityid, @subscriberid, 0, 1, 0)

--Customer Address(post-create, post-update, post-delete)
select @entityid = EntityId from Entity where ObjectTypeCode  = 1071
update Entity set EventMask = 1 where ObjectTypeCode = 1071

insert into EntityEventSubscribers(EventId, EntityId, SubscriberId, 
  CalloutOrder, IsEnable, IsAsyncHandler) values
(2, @entityid, @subscriberid, 0, 1, 0)
insert into EntityEventSubscribers(EventId, EntityId, SubscriberId, 
  CalloutOrder, IsEnable, IsAsyncHandler) values
(8, @entityid, @subscriberid, 0, 1, 0)
insert into EntityEventSubscribers(EventId, EntityId, SubscriberId, 
  CalloutOrder, IsEnable, IsAsyncHandler) values
(32, @entityid, @subscriberid, 0, 1, 0)

Registering Your Callouts as a COM+ Application

The DLL that contains your callouts must be registered as a COM+ application on the server where you have Microsoft CRM installed.

Add a new COM+ application

  1. In Control Panel, double-click Administrative Tools and then double-click Component Services.
  2. Expand Component Services, expand Computers, expand My Computer, and then expand COM+ Applications.
  3. Right-click COM+ Applications.
  4. Point to New, and then click Application to start the COM Application Install Wizard.

Use the COM Application Install Wizard

  1. On the Welcome to the COM Application Install Wizard page, click Next.

  2. Click the Create an empty application button.

  3. Enter a descriptive name for your application in the text box.

  4. Select Server application, and then click Next.

  5. Select This user, and then enter the user and password information.

    Note   Be sure to use a domain account with the proper privileges instead of the Interactive User. This ensures that the component will continue to operate in the event that there is no one physically logged into the server.

  6. Click Next on the Add Application Roles page.

  7. Click Next on the Add Users to Roles page.

  8. Click Finish.

Install the new COM+ application

  1. In the Component Services window, expand COM+ Applications.
  2. Expand your <new application name>.
  3. Right-click Components.
  4. Point to New, and then click Component to start the COM Component Install Wizard.

Use the COM Component Install Wizard

  1. On the Welcome to the COM Component Install Wizard page, click Next.
  2. Click the Install new component(s) button.
  3. Locate your DLL in the browser window, and then click Open.
  4. On the Install new components page, verify that your DLL is correct.
  5. Click Next, and then click Finish.

Your DLL is now registered as a COM+ application.

Sample Callout

For a component that you want to be notified about changes in Microsoft CRM, implement the ICRMCallout interface and register the class in the metabase. The following is an example of an implementation of the Sample Module component declaration of the ICRMCallOut interface.

Note   The GUID must match the CLSIDorProgId in the Subscriber table. The GUID that is used in this example is for illustrative purposes only; you should use a different value in your code.

/*************************************************************************
 * Sample Module

************************************************************************ #pragma once

[emitidl(true)];

[
    coclass,
    threading("Both"),
    progid("Sample.Module"),
    uuid(AA4AD2AC-97B8-4d24-8E81-388A655DDBE5)
]

class ATL_NO_VTABLE CSampleModule : public ICRMCallout
{
public:
    CSampleModule (){};
    virtual ~CSampleModule (){};

HRESULT PostCreate(/* [in] */ int ObjectType, /* [in] */ BSTR ObjectId, /*
  [in] */ BSTR OrigObjectXml)
{
// Your post-create implementation goes here
}
HRESULT PostUpdate(/* [in] */ int ObjectType, /* [in] */ BSTR ObjectId, /*
  [in] */ BSTR OrigObjectXml)
{
// Your post-update implementation goes here
}
   HRESULT PostDelete(/* [in] */ int ObjectType, /* [in] */ BSTR ObjectId)
{
// Your post-delete implementation goes here
}
};

Creating a CRM Post Callout Object Using Visual Studio .NET

The following steps outline how to create a post callout object for Microsoft CRM using Visual Studio .NET and either Microsoft Visual Basic .NET or Microsoft Visual C# .NET.

  1. Launch Visual Studio.NET.

  2. Click File, click New, and then select Project.

  3. Select either Visual Basic projects or Visual C# projects depending on your preference.

  4. Select the Class Library template.

  5. Assign a name to your project such as CRMCallout and pick a location to save your work.

  6. Click the OK button to create the project.

  7. Add a reference to the System.EnterpriseServices namespace. To do this, right-click your project in the Solution Explorer, then select the Add Reference option from the context menu. Select the System.EnterpriseServices component from the list box and click the Select button. Click the OK button to continue.

  8. Rename the Class1.vb or Class1.cs file in the Solution Explorer to a name more descriptive such as Callout.vb or Callout.cs.

  9. At the top of the code file, add the following statements to specify the namespaces needed:

    [C#]
          using System.EnterpriseServices;
          using System.Runtime.InteropServices;
    
    [Visual Basic .NET]
          Imports System.EnterpriseServices
          Imports System.Runtime.InteropServices
    
  10. Add the following statements before the namespace declaration to set the assembly options.

    [C#]
    [assembly: ApplicationName("Callout Example")]
    [assembly: ApplicationActivation(ActivationOption.Server)]
    [assembly: ApplicationAccessControl
       (false,AccessChecksLevel=AccessChecksLevelOption.ApplicationComponent)]*
    
    [Visual Basic .NET]
    <Assembly: ApplicationName("Callout Example")>
    <Assembly: ApplicationActivation(ActivationOption.Server)> 
    <Assembly: ApplicationAccessControl _
       (False,AccessChecksLevel:=AccessChecksLevelOption.ApplicationComponent)>
    

    These attributes help specify how the .NET assembly will operate with COM+ services. The ApplicationName attribute allows one to specify the name of the COM+ services application that will host the .NET assembly when it is imported into COM+. You can specify any name you would like here. The ApplicationActivation attribute specifies the type of COM+ application being created. This attribute uses an enumeration called ActivationOption which specifies that the application is a Server application. This means that when the component is activated, it will be hosted in a process separate from the process that called it. The ApplicationAccessControl attribute specifies whether the component will perform any access level checks.

    Note   The ApplicationAccessControl attribute is required for Microsoft CRM 1.2 and the .NET Framework version 1.1. Under the .NET Framework 1.1, the COM+ security configuration is enabled by default if this attribute is not present in the assembly. This is a change in the behavior from the .NET Framework version 1.0 where it was disabled by default.

  11. Next, you must implement an interface called ICRMCallout for the object. For specifics on the interface, refer back to the beginning of this article. Add the following code to your code file inside the namespace block to create the interface.

    [C#]
    [GuidAttribute("F4233E5B-17DC-4661-9ABC-6707A9F99215")]
    
    public interface ICRMCallout 
    {
    void PostCreate(int ObjectType, string ObjectId, string OrigObjectXml);
    void PostUpdate(int ObjectType, string ObjectId, string OrigObjectXml);
    void PostDelete(int ObjectType, string ObjectId);
    }
    
    [Visual Basic .NET]
    <GuidAttribute("F4233E5B-17DC-4661-9ABC-6707A9F99215")> 
    Public Interface ICRMCallout
    Sub PostCreate(ByVal ObjectType As Integer, ByVal ObjectID As String, _
      ByVal OrigObjectXML As String)
    Sub PostUpdate(ByVal ObjectType As Integer, ByVal ObjectID As String, _
      ByVal OrigObjectXML As String)
    Sub PostDelete(ByVal ObjectType As Integer, ByVal ObjectID As String)
    End Interface
    
  12. Create a public class in the code file that inherits from ServicedComponent and implements the ICRMCallout interface. An example follows:

    [C#]
    public class CRMCaller : ServicedComponent, ICRMCallout
    
    [Visual Basic .NET]
    Public Class CRMCaller
        Inherits ServicedComponent
        Implements CRMCallout.ICRMCallout
    
  13. At the top of the code file, add the following statements to specify the namespaces needed:

    [C#]
          using System.EnterpriseServices;
          using System.Runtime.InteropServices;
    
    [Visual Basic .NET]
          Imports System.EnterpriseServices
          Imports System.Runtime.InteropServices
    
  14. A globally unique identifier (GUID) for the class must be defined and assigned to the class. You can create your own GUID using the GuidGen utility that is distributed with Visual Studio.NET. This utility creates a GUID that can be copied to the clipboard and then pasted into this code file. You can run it from the Create GUID option of the Tools menu. An example is shown below.

  15. Assign this GUID to the class by inserting it into an attribute like the following just before your class declaration:

    [C#]
    [GuidAttribute("AA4AD2AC-97B8-4d24-8E81-388A655DDBE5")]
    
    [Visual Basic .NET]
    <GuidAttribute("AA4AD2AC-97B8-4d24-8E81-388A655DDBE5")> _
    

    Assigning this GUID attribute to the class causes the class to explicitly use the GUID you have assigned. This is the GUID that will be used to register the component in COM+ services. You must also insert this into the SQL statement outlined earlier in this article.

  16. Add the ClassInterface attribute to your class file just under the GuidInterface attribute as in the following examples:

    [C#]
    [ClassInterface(ClassInterfaceType.AutoDispatch)]
    
    [Visual Basic .NET]
    <GuidAttribute("AA4AD2AC-97B8-4d24-8E81-388A655DDBE5"), _
        ClassInterface(ClassInterfaceType.AutoDispatch)> 
    

    Note that the Visual Basic .NET example simply appends the attribute to the existing GuidAttribute that is assigned to the class. The ClassInterface attribute ensures that an interface is generated by the .NET Common Language Runtime that provides access to the public members of this class for COM clients. The ClassInterfaceType is an enumeration that specifies a value for the type of class interface that gets generated.

  17. Now create three public methods in the class called PostCreate, PostUpdate and PostDelete. Make sure that the signatures for each of the methods match those specified earlier in the ICRMCallout interface. This is the location where custom business logic can be created to respond to each of the events raised in Microsoft CRM. An example is provided here although your specific implementation will be different:

    [C#]
    public void PostCreate(int ObjectType, string ObjectId, string 
      OrigObjectXml)
    {
       FileInfo fi = new FileInfo(@"C:\CSCallout_Insert.txt");
       StreamWriter s = fi.AppendText();
        s.WriteLine("CRM Create Event Occurred...\n");
        s.WriteLine("Object Type: " + ObjectType.ToString());
        s.WriteLine("Object ID: " + ObjectId.ToString());
        s.WriteLine("Object XML String: ");
        s.WriteLine(OrigObjectXml);
        s.WriteLine();
        s.Close();
    }
      public void PostUpdate(int ObjectType, string ObjectId, string 
        OrigObjectXml)
    {
                  FileInfo fi = new FileInfo(@"C:\CSCallout_Update.txt");
                  StreamWriter s = fi.AppendText();
                  s.WriteLine("CRM Update Event Occurred...\n");
                  s.WriteLine("Object Type: " + ObjectType.ToString());
                  s.WriteLine("Object ID: " + ObjectId.ToString());
                  s.WriteLine("Object XML String: ");
                  s.WriteLine(OrigObjectXml);
                  s.WriteLine();
                  s.Close();
           }
           public void PostDelete(int ObjectType, string ObjectId)
           {
                  FileInfo fi = new FileInfo(@"C:\CSCallout_Delete.txt");
                  StreamWriter s = fi.AppendText();
                  s.WriteLine("CRM Delete Event Occurred...\n");
                  s.WriteLine("Object Type: " + ObjectType.ToString());
                  s.WriteLine("Object ID: " + ObjectId.ToString());
                  s.WriteLine();
                  s.Close();
    }
    
    [Visual Basic .NET]
    Sub PostCreate(ByVal ObjectType As Integer, ByVal ObjectID As String, _
      ByVal OrigObjectXML As String) Implements ICRMCallout.PostCreate
            Dim fi As New FileInfo("C:\VBCallout_Insert.txt")
            ' Create the file and output some text to it.
            Dim s As StreamWriter = fi.AppendText()
            s.WriteLine("CRM Create Event Occurred..." + vbNewLine)
            s.WriteLine("Object Type:" + ObjectType.ToString())
            s.WriteLine("Object ID: " + ObjectID.ToString())
            s.WriteLine("Object XML String: ")
            s.WriteLine(OrigObjectXML)
            s.WriteLine()
            s.Close()
    End Sub
    
    Sub PostUpdate(ByVal ObjectType As Integer, ByVal ObjectID As String, _
      ByVal OrigObjectXML As String) Implements ICRMCallout.PostUpdate
            Dim fi As New FileInfo("C:\VBCallout_Update.txt")
            ' Create the file and output some text to it.
            Dim s As StreamWriter = fi.AppendText()
            s.WriteLine("CRM Update Event Occurred..." + vbNewLine)
            s.WriteLine("Object Type:" + ObjectType.ToString())
            s.WriteLine("Object ID: " + ObjectID.ToString())
            s.WriteLine("Object XML String: ")
            s.WriteLine(OrigObjectXML)
            s.WriteLine()
            s.Close()
    End Sub
    Sub PostDelete(ByVal ObjectType As Integer, ByVal ObjectID As String) _
      Implements ICRMCallout.PostDelete
            Dim fi As New FileInfo("C:\VBCallout_Delete.txt")
            ' Create the file and output some text to it.
            Dim s As StreamWriter = fi.AppendText()
            s.WriteLine("CRM Delete Event Occurred..." + vbNewLine)
            s.WriteLine("Object Type:" + ObjectType.ToString())
            s.WriteLine("Object ID: " + ObjectID.ToString())
            s.WriteLine()
            s.Close()
    End Sub
    

    Note   For this specific example, a using statement (C#) or an Imports statement (Visual Basic.NET) needs to be added to the beginning of the code file that includes the System.IO namespace. For example, using System.IO; or Imports System.IO.

    Generate a strong name for the assembly using the .NET Framework Strong Name Utility (sn.exe). This utility generates a public/private key file that will be incorporated into the assembly. To create this key file, go to a command prompt and change directories to the location where the solution is stored. Type in the following command: sn –k callout.snk and then press Enter. This will create the key file.

  18. Add the strong file name to the assembly by adding an attribute in the AssemblyInfo code file that is part of the project. Locate the AssemblyInfo file in Solution Explorer and click it. In the code file, add the following attribute:

    [C#]
    [assembly: AssemblyKeyFile("..\\..\\callout.snk")].  
    
    [Visual Basic .NET]
    <Assembly: AssemblyKeyFile("..\..\callout.snk")>
    

    This attribute looks for the strong name key file that was previously created in the folder that was created for the solution. If there is an AssemblyKeyFile attribute in the AssemblyInfo file, replace it with this one.

  19. Build the solution. You can either do this by going to the Build menu and selecting the Build Solution option in Visual Studio.NET or by right-clicking your project in the Solution Explorer and selecting the Build option.

  20. Make sure that the component is on the same machine that is hosting the Microsoft CRM application.

  21. Run the SQL statement detailed earlier in this article against the Microsoft CRM Metabase database. It is recommended that you make a backup of this database before making any modifications. Care should be taken when modifying the tables outlined in this article because they are used for the Microsoft CRM Integration to Great Plains. Substitute the GUID that was assigned to the class in the GuidAttribute for the CLSIDorProgId in the SQL statement. For this sample. it looks like the following:

    (@subscriberid, N'{AA4AD2AC-97B8-4d24-8E81-388A655DDBE5}',
        N'My Sample Callout Module', N'')
    

    To register the callout object to respond to a particular Microsoft CRM object's events, the SQL statement needs to be modified to include each object type. For a complete list of object type codes, see the ObjectType Class topic in the Reference section of the Microsoft CRM SDK.

  22. Follow the instructions for registering your callout as a COM+ application as outlined earlier in this article. Make sure to name your COM+ application with the same name as you used in the Subscriber table in the database.

    Note   Alternately, you can go to a command prompt and type in RegSvcs AssemblyName where AssemblyName is the name of your assembly. In this example, you would use RegSvcs CRMCallout.dll. This creates a COM+ application with the name specified in the ApplicationName attribute mentioned earlier in this article, and then imports the assembly into it. Using RegSvcs.exe allows for the loading and registration of the assembly, creates a type library for the assembly, and imports that library into a COM+ application. It then uses the metadata inside the DLL to properly configure the COM+ application. If you follow this step, the steps to register the component outlined in the MSDN article are not necessary. After registering the component, be sure to go into Component Services and change the account that the component runs under from the Interactive user to a specific domain account with the proper privileges. This ensures that the component will continue to operate in the event that there is no one physically logged into the server.

  23. Test the component using the Microsoft CRM application to ensure that the Post-callout object responds to the Microsoft CRM object events.

Additional Information

For more information about customizing the Microsoft CRM application, see the Microsoft CRM Software Development Kit (SDK)