Plug-ins

With Plug-ins we can extend or customize the functionality of the Microsoft Dynamics CRM platform by integrating the custom business logic (code). Plug-ins are triggered by the message with which they're registered on the Microsoft Dynamics CRM platform.

For example, we can register a Plug-in to perform some business logic every time a Flight record is created. We can also define whether to run the business logic BEFORE the Flight record is saved in the CRM organization database (Pre-Event) or AFTER the Flight record is saved in the CRM organization database (Post-Event).

Notice that some of the business logic can also be accomplished with JavaScript, which is a Client-Side programming method, such as data validation or user interface design, and so on. The Client-Side script depends on the user's browser and is triggered by the CRM form events. By contrast, the plug-ins are triggered by the platform events—that is, importing bulk Flight records can trigger Plug-in events, but doesn't trigger the form events.

CRM 2011 allows Plug-ins to participate in SQL transactions, and allows them to create traces returned with exceptions. In the previous version of CRM Online, we cannot deploy Plug-ins and custom workflow activities to the environment. The current version of CRM 2011 supports the execution of Plug-ins in an isolated environment. In this isolated environment, also known as a sandbox, a Plug-in can make use of the full power of the CRM SDK to access the Web Services, in order to perform custom business logic. However, placing Plug-ins in a sandbox prevents you from accessing the file systems, system event log, registries, and network resources. However, sandbox Plug-ins do have access to the external endpoints like the Windows Azure cloud.

Event framework

Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online provide the ability to add custom business logic to the event pipeline on the Microsoft Dynamics CRM server. We call this the event framework. The event framework allows developers to create rich solutions on top of Microsoft CRM, and provides the following key features:

  • An improved event processing subsystem that provides a unified method of executing both Plug-ins and workflow activities
  • An event framework API for extending the CRM platform through the development of custom business logic in the form of Plug-ins and workflow activities
  • Enables the deployment of Plug-in and custom workflow activities to the Microsoft Dynamics CRM database
  • Backward compatibility for Microsoft Dynamics CRM 4.0 Plug-ins
  • Synchronous and asynchronous execution of Plug-ins

The Plug-ins execute in the Event Framework based on a message pipeline execution model. This can be registered in either synchronous mode or asynchronous mode. The CRM platform core operation and any Plug-ins registered for synchronous execution are executed immediately (executed in a well-defined order). Plug-ins registered for asynchronous execution are queued with the Asynchronous Service in Microsoft Dynamics CRM, and executed at a later time. The following diagram shows the event execution pipeline for Plug-ins:

Event framework

Developing a Plug-in

On the code level, a Plug-in is a custom class that implements the IPlugin interface. A Plug-in can be written in any .NET Framework 4 CLR-compliant language, such as C# and VB .NET in Microsoft Visual Studio 2010. Typical Plug-ins access the information in the context, perform the required business operations, and handle exceptions.

Let's take a look at the SDK example of the Plug-in code structure in Visual Studio:

using System;
usingSystem.ServiceModel;
usingSystem.Runtime.Serialization;
usingMicrosoft.Xrm.Sdk;

namespacePluginsSample
{
  public class Class1:IPlugin
  {
    public void Execute(IServiceProviderserviceProvider)
    {
      // Obtain the execution context from the service provider.
      IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

      // Get a reference to the organization service.
      IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
      IOrganizationService service = factory.CreateOrganizationService(context.UserId);

      // Get a reference to the tracing service.
      ITracingServicetracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

      try
      {
        // Plug-in business logic goes below this line.
        // Invoke organization service methods.
      }
      catch (FaultException<OrganizationServiceFault> ex)
      {
        // Handle the exception.
      }
    }
  }
}

DLL references

Add Microsoft.Xrm.Sdk.dll and Microsoft.Crm.Sdk.Proxy.dll assembly references to the CRM project, in order to access the CRM context, and then compile the Plug-in code. In addition to these two, you can also reference the following out-of-the-box CRM server DLLs for different purposes:

  • Microsoft.Xrm.Sdk.Workflow.dll
  • Microsoft.Xrm.Sdk.Deployment.dll
  • Microsoft.Crm.Outlook.Sdk.dll
  • Microsoft.Crm.Tools.EmailProviders.dll

    Note

    Note: All available DLLs can be found in the SDKBin folder.

IPluginInterface and the Execute method

The IPlugin is the base interface for all Plug-ins. The Execute method is also a required method for all Plug-ins. The IServiceProvider parameter of the Execute method is a container for several objects that can be accessed within a Plug-in. The serviceProvider is an instance of the IServiceProvider, which contains references to the execution context (IPluginExecutionContext), IOrganizationServiceFactory, ITracingService, and so on.

  • The IPluginExecutionContext interface contains information that describes the run-time environment that the plug-in is executing in, information related to the execution pipeline, and entity business information
  • The IOrganizationService interface provides programmatic access to the metadata and data for an organization
  • The IOrganizationServiceFactory interface represents a factory for creating IOrganizationService instances
  • The ITracingService interface provides a method of logging run-time trace information for Plug-ins

Input and output parameters

The InputParameters property contains the data that is in the request message that triggered the event that caused the Plug-in to execute.

The OutputParameters property contains the data that is in the response message, after the core platform operation has completed.

Note

Note: Only synchronous post-event and asynchronous registered Plug-ins have OutputParameters populated.

// 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"];

Pre and post entity images

PreEntityImages contain the primary entity's attributes (that are set to a value or null) before the core platform operation begins.

PostEntityImages contain the primary entity's attributes (that are set to a value or null) after the core platform operation.

Note

Note: Only synchronous post-event and asynchronous registered Plug-ins have PostEntityImages populated.

Security

Each Plug-in assembly must be signed either by Visual Studio or by the Strong Name tool. As a best practice, do not develop Plug-in code that contains any system logon information, confidential information, or company trade secrets.

A Plug-in example

Now that we have covered the essential knowledge of the Microsoft Dynamics CRM 2011 Server-Side programming. There is a lot of detailed information in the CRM SDK; please refer to it for a more comprehensive understanding.

It's time to start building a Plug-in example for the ACM system; it will be a Sandbox mode assembly, because we are using Microsoft Dynamics CRM 2011 Online.

The requirement is simple; we want to create a compensation record for each Flight Crew member who served on the flight. Because Flight and Flight Crews are connected through the "Connection" entity (by doing that you can set roles to individuals), we can say: create a compensation record for each connection record created for Flight and Flight Crews with the correct connection roles. The following diagram describes the structure of Connection Roles:

A Plug-in example

First of all, create a new Connection Role Category. Go to ACM Solutions | Option Sets | Add Existing, and then select Category (connectionrole_category). Next, go to ACM Solutions | Option Sets, double-click on Category, and then add a new option called "Flight Crew".

Next, create several Connection Roles. Go to the ACM Solutions | Connection Roles, and click the New button to create the following roles in the "Flight Crew" category that we have created.

Name

Record Type

Matching Roles

Connection Role Category

Cockpit Crew

Flight

Captain; First Officer

Flight Crew

Cabin Crew

Flight

Purser; Flight Attendant

Captain

Flight Crew

Cockpit Crew

First Officer

Flight Crew

Cockpit Crew

Purser

Flight Crew

Cabin Crew

Flight Attendant

Flight Crew

Cabin Crew

The following screenshot shows what it should look like in the end:

A Plug-in example

Note

Note: Each connection will create two connection records in the database: a "Connect From" and a "Connect To".

A Flight can have many connections that connect to CrewMembers to different roles. See the following screenshot as an example:

A Plug-in example

The following screenshot describes the relationships between Flight, Crew, and Compensation:

A Plug-in example

The following diagram shows the process flow for this plug-in:

A Plug-in example

Please carry out the following steps to create a CRM 2011 Plug-in and register the Plug-in on the Create, Update, and Delete message of the Connection entity:

  1. Create a new Class Library solution in Visual Studio 2010 called: CompensationGeneration, based on the .NET Framework 4, C#.
  2. Add assemblies references (located in the SDKin folder of the SDK):
    • microsoft.xrm.sdk.dll
    • microsoft.crm.sdk.proxy.dll
  3. Add .Net references:
    • System.Runtime.Serialization
    • System.ServiceModel
  4. Right-click CompensationGeneration on the Solution Explorer, select Property, and then go to Signing | Sign the assembly:
    A Plug-in example
  5. Run the crmsvcutil.exe command to generate strongly-typed classes for the entities in the ACM organization, for early binding:
    sdkin>crmsvcutil.exe /url:http://localhost:5555/ACM/XRMServices/2011/Organization.svc /out:GeneratedCode.cs
  6. Add GeneratedCode.cs into solution.
  7. Create a new Class file called QueryConnection.cs. This does the query work in CRM, listing all flights and their crew members. See the following code:
    using System;
    usingSystem.Collections.Generic;
    usingSystem.Linq;
    usingSystem.Text;
    usingSystem.ServiceModel;
    usingMicrosoft.Xrm.Sdk;
    usingMicrosoft.Xrm.Sdk.Client;
    namespace CompensationGeneration
    {
    
      public class CompensationGenerationClass: IPlugin
      {
        public void Execute(IServiceProviderserviceProvider)
        {
          // Obtain the execution context from the service provider.
          IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
    
          // Extract the tracing service for use in debugging // sandboxed plug-ins.
          ITracingServicetracingService = (ITracingService)serviceProvider.GetService( typeof(ITracingService));
    
          // Obtain the organization service reference.
          IOrganizationServiceFactoryserviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
    
          // Use the context service to create an instance of IOrganizationService.
          IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
    
          try
          {
            // The InputParameters collection contains all the data passed in the message request.
            if (context.InputParameters.Contains("Target") &&context.InputParameters["Target"] is Entity)
            {
              if (context.MessageName == "Create")
              {
                // Obtain the target entity from the input parmameters.
                Entity entity = (Entity)context.InputParameters["Target"];
    
                // Verify that the entity represents a connection.
                if (entity.LogicalName != "connection")
                return;
    
                //Gets the entity as the connection type.
                Connection connection = entity.ToEntity<Connection>();
    
                // If the connection record match the connection role,then create a new compensation.
                if (CheckConnectionRecords(service, connection))
                {
                  CreateCompenstaion(service, connection);
                }
              }
    
              if (context.MessageName == "Update")
              {
                // Gets the properties of the primary entity before the core platform operation has begins.
                if (context.PreEntityImages.Contains("ConnectionImage") && context.PreEntityImages["ConnectionImage"] is Entity)
                {
                  Entity entity = (Entity)context.PreEntityImages["ConnectionImage"];
                  if (entity.LogicalName != "connection")
                  return;
    
                  Connection connection = entity.ToEntity<Connection>();
    
                  // If the old connection record match the connection role, then delete the old compensation.
                  if (CheckConnectionRecords(service, connection))
                  {
                    DeleteCompensation(service, context.PrimaryEntityId);
                  }
                }
    
                // Gets the properties of the primary entity after the core platform operation has been completed. 
                if (context.PostEntityImages.Contains("ConnectionImage") && context.PostEntityImages["ConnectionImage"] is Entity)
                {
                  Entity entity = (Entity)context.PostEntityImages["ConnectionImage"];
                  if (entity.LogicalName != "connection")
                  return;
                  Connection connection = entity.ToEntity<Connection>();
    
                  // If the new connection record match the connection role, then create a new compensation.
                  if (CheckConnectionRecords(service, connection))
                  {
                    CreateCompenstaion(service, connection);
                  }
                }
              }
            }
    
            if (context.InputParameters.Contains("Target") &&context.InputParameters["Target"] is EntityReference)
            {
              if (context.MessageName == "Delete")
              {
                // Identifies a record. The EntityReference class replaces the Moniker class from Microsoft Dynamics CRM 4.0.
                EntityReference entity = (EntityReference)context.InputParameters["Target"];
                if (entity.LogicalName != "connection")
                return;
    
                using (varorgContext = new OrganizationServiceContext(service))
                {
                  // Get the connection record that being deleted.
                  Connection connection = orgContext.CreateQuery<Connection>().Where(c =>c.Id == entity.Id).First();
    
                  // If the connection record match the connection role, then delete the existing compensation.
                  if (CheckConnectionRecords(service, connection))
                  {
                    DeleteCompensation(service, context.PrimaryEntityId);
                  }
                }
              }  
            }
    
          }
    
          catch (FaultException<OrganizationServiceFault> ex)
          {
            throw new InvalidPluginExecutionException("An error occurred in the CompensationGeneration plug-in.", ex);
          }
    
          catch (Exception ex)
          {
            tracingService.Trace("CompensationGeneration: {0}", ex.ToString());
            throw;
          }
        }
    
        privateboolCheckConnectionRecords(IOrganizationService service, Connection connection)
        {
          // Validate the connection record.
          if (connection.Record1Id != null && connection.Record1RoleId != null && connection.Record2Id != null && connection.Record2RoleId != null)
          {
            // Each connection will create two records on each side. We just need to stick on 1 side.
            if (connection.Record1Id.LogicalName == "contact" &&CheckConnectionRoleCategory(service, connection.Record1RoleId.Id)&& connection.Record2Id.LogicalName == "acm_flight" &&CheckConnectionRoleCategory(service, connection.Record2RoleId.Id))
            {
              // Create a new compensation.
              return true;
            }
          }
    
          return false;      
        }
    
        privateboolCheckConnectionRoleCategory(IOrganizationService service, GuidRoleID)
        {
          using (varorgContext = new OrganizationServiceContext(service))
          {
            varconnectionrole = from cr in orgContext.CreateQuery<ConnectionRole>()wherecr.ConnectionRoleId == RoleIDwherecr.Category.Value == 100000000 //Category="Flight Crew"
            selectcr;
            
            if (connectionrole.ToList().Count > 0)
            return true;
            else
            return false;
          }
        }
    
        private void CreateCompenstaion(IOrganizationService service, Connection connection)
        { 
          using (varorgContext = new OrganizationServiceContext(service))
          {
            //Single query to get the crewmember record
            var crewmember = (from c in orgContext.CreateQuery<Contact>()
            wherec.ContactId == connection.Record1Id.Idselect c).Single();
    
            Money HourlyDutyPay = new Money(crewmember.acm_HourlyDutyPay.Value);
            stringBaseCity = crewmember.Address1_City;
            stringBaseCountry = crewmember.Address1_Country;
    
            //Single query to get the flight record
            var flight = (from f in orgContext.CreateQuery<acm_flight>()
            wheref.acm_flightId == connection.Record2Id.Idselect f).Single();
    
            intFlightTime = flight.acm_FlightTime.Value;
            intLayoverTime = flight.acm_LayoverTime.Value;
            OptionSetValueFlightType = new OptionSetValue(flight.acm_FlightType.Value);
    
            //Single query to get the airport record
            var airport = (from fr in orgContext.CreateQuery<acm_flightroute>()
              join a in orgContext.CreateQuery<acm_airport>() on fr.acm_To.Id equals a.acm_airportIdwherefr.acm_flightrouteId == flight.acm_FlightRoute.Id
              select a).Single();
    
            Money PerDiem = new Money(airport.acm_PerDiem.Value);
            stringLayoverCity = airport.acm_City;
            stringLayoverCountry = airport.acm_Country;
    
            //Create compensation record
            Entity compensation = new Entity("acm_compensation");
            compensation["acm_crewmember"] = new EntityReference(connection.Record1Id.LogicalName, connection.Record1Id.Id);
            compensation["acm_flight"] = new EntityReference(connection.Record2Id.LogicalName, connection.Record2Id.Id);
          
            if (FlightTime> 0)
              compensation["acm_flighttime"] = FlightTime;
    
            if (LayoverTime> 0)
              compensation["acm_layovertime"] = LayoverTime;
    
            if (HourlyDutyPay != null)
              compensation["acm_hourlydutypay"] = HourlyDutyPay;
    
            if (FlightType != null)
              compensation["acm_flighttype"] = FlightType;
    
            if (PerDiem != null)
              compensation["acm_perdiem"] = PerDiem;
    
            // Calculate total compensation
            if (FlightTime> 0 &&LayoverTime> 0 &&HourlyDutyPay != null &&FlightType != null &&PerDiem != null)
            {
              Money Compensation;
              decimalOnDutyPay = HourlyDutyPay.Value * FlightTime / 60;
              decimalLayoverPay = PerDiem.Value * LayoverTime / 60;
              if (FlightType.Value != 1)
              {
                OnDutyPay = OnDutyPay * 2;
              }
              if (BaseCity == LayoverCity&&BaseCountry == LayoverCountry)
              {
                Compensation = new Money(OnDutyPay);
              }
              else
              {
                Compensation = new Money(OnDutyPay + LayoverPay);
              }
    
              compensation["acm_compensation"] = Compensation;
              compensation["subject"] = "Compensation created for: " + crewmember.FullName + " on " + flight.acm_name + " by " + connection.Id.ToString();
    
            }
            service.Create(compensation);
          }   
        }
    
        private void DeleteCompensation(IOrganizationService service, GuidConnectionID)
        {
          using (varorgContext = new OrganizationServiceContext(service))
          {
            var compensations = from c in orgContext.CreateQuery<acm_compensation>()wherec.Subject.Contains(ConnectionID.ToString())select c;
    
            foreach (acm_compensation compensation in compensations)
            {
              service.Delete(acm_compensation.EntityLogicalName, compensation.Id);
            }
          }
        }
    
      }
    }
  8. Register the Plug-in:
    1. Run the Plug-in Registration Tool (this can be found in the CRM 2011 SDK folder: sdk oolspluginregistrationinDebugPluginRegistration.exe)
    2. Click the Create New Connection button on the toolbar, provide the connection information, and then click on Connect.

      The discovery URLs for the worldwide Microsoft Dynamics CRM Online data centers are:

    3. Once connected, go to Register | Register New Plugin, and then select the Plug-in DLL file you just built. Select the Sandbox as the isolation mode and Database as the assembly location, and then click the Register selected Plugins button.
    4. Now that the Plug-in assembly has been registered, we need to register three steps for connections: Create, Delete, and Update message. Right-click on the CompensationGeneration assembly, and select Register New Step. The first step is as follows:

      CompensationGeneration Plug-in Steps

      Message

      Create

      Delete

      Update

      Primary Entity

      connection

      connection

      connection

      Filtering Attributes

      All

      All

      record1id, record2id, record1roleid, record2roleid

      Run in User's Context

      Calling User

      Calling User

      Calling User

      Eventing Pipeline

      Post-operation

      Pre-operation

      Post-operation

      Execution Mode

      Synchronous

      Synchronous

      Synchronous

      Deployment

      Server

      Server

      Server

    5. On the Update step, we also need to register an Image to get the primary entity before and after the core platform operation begins. Right-click on the Update step, select Register New Image, and fill in the following information:
      • Image Type: Pre Image, Post Image
      • Name/Entity Alias: ConnectionImage
      • Parameters: record1id, record2id, record1roleid, record2roleid
      A Plug-in example
    6. A completed Plug-in registration should look like the example shown in the following screenshot:
    A Plug-in example

Using the CRM Developer Toolkit to build a Plug-in

It's also recommended to use the CRM Developer Toolkit when building a Plug-in. The Developer's Toolkit for Microsoft Dynamics CRM 2011 is a set of Visual Studio integration tools focused on accelerating the development of custom code for Dynamics CRM 2011. The Toolkit supports the end-to-end creation and deployment of CRM Plug-ins, custom workflows, Silverlight applications, and other Web resources, including JavaScript and HTML. Dynamics CRM developers can write all of their custom code from within Visual Studio, using native tools and build processes, and then automatically deploy them to the CRM Server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset