Mensajes Salientes del Flujo de Trabajo en Salesforce Procesados en .Net con WCF

Un alto porcentaje de los proyectos de Salesforce requieren de integración con sistemas terceros, como ERPs, bases de datos, o incluso otros CRMs.

Salesforce ofrece muchas opciones para que estas integraciones sean posibles: mensajes salientes de flujo de trabajo (workflow outbound messages), llamadas SOAP/REST, integración mediante cargas masivas con el DataLoader, etc. Por supuesto existe mucha documentación sobre este tema, pero la mayoría está basada en Java y hay poca documentación enfocada en .Net.

Yo soy un desarrollador de .Net, por lo que voy a dar algunos ejemplos de cómo realizar integraciones con Salesforce usando Visual Studio y C#. No puedo cubrir todos los posibles escenarios de integración en esta publicación, por lo que voy a concentrarme en un escenario que considero como mi primera opción al diseñar la arquitectura de una solución que requiere integración: mensajes salientes de flujo de trabajo.

Los Requerimientos

Permite que explique los conceptos usando un ejemplo en concreto: uno de nuestros clientes requiere integrar su organización de Salesforce con su ERP. Tanto Salesforce como el ERP manejan el concepto de cuentas (clientes), pero en este caso se requiere que sea Salesforce el sistema maestro de cuentas. La integración se basa en las siguientes reglas:

  • Las cuentas son creadas en Salesforce (no en el ERP)
  • Cualquier modificación realizada a los datos de cuentas en Salesforce debe ser reflejada en el ERP (siempre y cuando la cuenta exista en el ERP)

Las reglas anteriores se pueden traducir a lo siguiente:

  • Es posible crear un desencadenador (trigger) sobre el objeto de cuentas que realice una llamada saliente a un servicio web en el ERP, pero hay una forma más fácil (menos código): usar un mensaje saliente de flujo de trabajo.
  • Usaremos un mensaje saliente de flujo de trabajo sobre el objeto de cuenta, el cual se disparará cuando una cuenta sea creada o modificada, enviando información de los datos de cuenta al ERP.

¡Veamos el código!

Mensaje Saliente de Flujo de Trabajo en Salesforce

En Salesforce, dirígete a “Configuración->Compilación->Crear->Flujos de trabajo y aprobaciones->Reglas de flujo de trabajo”. Crea una nueva regla, basándola en el objeto Cuenta:

 flujo de trabajo salesforce

Pincha en “Siguiente”. Dale un nombre (yo usaré UpdateAccountOnERP) y para el criterio de evaluación selecciona “cread, y siempre que se modifique para cumplir criterios posteriores”. Seleccionamos esta opción ya que queremos actualizar la cuenta en el ERP cada vez que se realice una modificación en Salesforce. Queremos que este flujo de trabajo se dispare todas las veces, así que para el criterio de la regla escoge “la fórmula es verdadera”, y especifica true en el editor de fórmulas. El flujo de trabajo debería ser como el siguiente:

regla flujo de trabajo salesforce

Pincha en “Guardar y siguiente”. En la pantalla siguiente hay que agregar una acción al flujo de trabajo. Pincha en el combo de “Agregar acción de flujo de trabajo” y selecciona “Nuevo mensaje saliente”. En la pantalla de “Nuevo mensaje saliente”, especifica un nombre y un nombre exclusivo (yo usaré use SendAccountToERP), para el URL de extremo coloca cualquier URL ya que todavía no tenemos nuestro servicio web, y finalmente selecciona todos los campos que debemos enviar al ERP. Al finalizar pincha en “Guardar”. El Sistema te llevará a la pantalla de la regla de flujo de trabajo. Asegúrate de activar el flujo de trabajo (pincha en el botón de “Activar”).

 activar el flujo de trabajo en salesforce

Hemos creado un mensaje saliente que será ejecutado cuando la cuenta sea modificada, pero de momento el mensaje saliente fallará debido a que no hemos creado el servicio web todavía. Para esto necesitamos obtener el WSDL del mensaje saliente. Pincha en la descripción del mensaje saliente para ver la pantalla de detalle del mensaje:

 salesforce crm erp

Pincha en el hipervínculo “Haga click para WSDL”. Se abrirá una nueva ventana con el WSDL. Guarda el WSDL en tu disco local (yo llamé el archivo accountWorkflowOutboundMessage.wsdl), lo vamos a necesitar en Visual Studio.

Servicio Web en Visual Studio (usando WCF)

Ahora que tenemos el WSDL del mensaje saliente, necesitamos crear un servicio web para procesar el mensaje. Este articulo explica cómo crear un servicio web del tipo asmx, pero yo prefiero trabajar con WCF (asmx y algo viejo y WCF se supone que lo reemplaza, ¿no?). Usaré la edición de Visual Studio 2013 Community para esto. Abre Visual Studio un nuevo proyecto del tipo “empty ASP.Net Web Application”:

salesforce crm visual studiosalesforce crm wcf

Para agregar un servicio WCF al Proyecto, pincha con el botón derecho del ratón en el nombre del Proyecto en el Solution Explorer and escoge “Add->New Item”. Selecciona “WCF Service”:

salesforce crm wcf services

Visual Studio agregará los assemblies requeridos al proyecto y creará tres archivos: IAccountNotificationService.cs, AccountNotificationService.svc y el AccountNotificationService.svc.cs. Es necesario reemplazar la definición de la interface para que se corresponda con la definición del mensaje saliente. Agrega el archivo WSDL al proyecto. Abre una línea de comando el en directorio donde reside el WSDL (si tienes instalado la extensión Productivity Power Tools puedes pinchar con el botón derecho del ratón en el Solution Explorer y escoger “Power Commands->Open Command Prompt”). En la línea de comando escribe lo siguiente:

svcutil /noconfig /out:IAccountNotificationService.cs accountWorkflowOutboundMessage.wsdl

 

El comando svcutil crea la interface para la definición del servicio web. Aquí está la salida del comando:

comando svcutil salesforce crm

Nota que el comando reemplazó el contenido del archivo IAccountNotificationService.cs con la definición del servicio web descrita en el WSDL. Tu proyecto debería verse como este:

salesforce crm wsdl

Ahora hay que hacer algunos trucos para que esto funcione. Abre IAccountNotificationService.cs y cambia lo siguiente:

  • Coloca la clase entera dentro del namespace del proyecto (en mi caso es el nombre del proyecto WorkflowNotificationServices)
  • Cambia el nombre de la interface de NotificationPort a IAccountNotificationService.
  • Cambia el parámetro ConfigurationName del atributo ServiceContractAttribute de NotificationPort a AccountNotificationService.
  • Elimina el parámetro ReplyAction=”*” del atributo OperationContractAttribute. Esto es muy importante para que funcione.
  • Al final del archivo, elimina la interface NotificationPortChannel y la clase NotificationPortClient (no necesitamos esto ya que se utiliza para clientes que quieran consumir el servicio web).

La interface debería estar como sigue (las líneas resaltadas son las que han cambiado):

namespace WorkflowNotificationServices
{
 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
 [System.ServiceModel.ServiceContractAttribute(Namespace = "http://soap.sforce.com/2005/09/outbound", ConfigurationName = "AccountNotificationService")]
 public interface IAccountNotificationService
 {

 // CODEGEN: Generating message contract since the operation notifications is neither RPC nor document wrapped.
 [System.ServiceModel.OperationContractAttribute(Action = "")]
 [System.ServiceModel.XmlSerializerFormatAttribute()]
 [System.ServiceModel.ServiceKnownTypeAttribute(typeof(sObject))]
 notificationsResponse1 notifications(notificationsRequest request);
 }

 /// rest of the code below
}

A continuación, abre el archivo AccountNotificationService.svc.cs, elimina el método DoWork e implementa la interface IAccountNotificationService (coloca el cursor en el texto del nombre de la interface y presiona Crtl+. y escoge “Implement interface IAccountNotificationService”).

Finalmente, abre web.config y reemplaza el contenido con el siguiente:

<configuration>
    <connectionStrings>
        <add name="ERP" connectionString="Data Source=localhost;Initial Catalog=ERP;Integrated Security=True" providerName="System.Data.SqlClient"/>
    </connectionStrings>
    <system.web>
        <compilation debug="true" targetFramework="4.5" />
        <httpRuntime targetFramework="4.5" />
        <webServices>
            <protocols>
                <clear/>
                <add name="HttpSoap" />
                <add name="Documentation"/>
            </protocols>
        </webServices>
    </system.web>
    <system.serviceModel>
        <services>
            <service name="WorkflowNotificationServices.AccountNotificationService">
                <endpoint binding="basicHttpBinding" contract="AccountNotificationService"/>
            </service>
        </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="">
                    <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    </system.serviceModel>
</configuration>

Nota que en la línea 3 he creado un connection string a la base de datos del ERP. Esta conexión la usaré luego en la implementación del servicio web. También nota la declaración del servicio web en las líneas 18-20.

Tu proyecto debería compilar en este punto. De igual manera, deberíamos poder ver el WSDL de nuestro servicio web. Ejecuta el proyecto y usando un navegador conéctate al servicio web. Deberías poder ver algo como esto:

salesforce crm compilar

Lo que queda ahora es escribir el código para actualizar el ERP. En este ejemplo hará una llamada a un procedimiento almacenado en una base de datos SQLServer para actualizar el ERP (esto es solo un ejemplo, puedes hacer aquí lo que sea necesario para comunicarse con el sistema tercero). Mi implementación del servicio es la siguiente:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
 
namespace WorkflowNotificationServices
{
    public class AccountNotificationService : IAccountNotificationService
    {
        public notificationsResponse1 notifications(notificationsRequest request)
        {
            notifications notifications1 = request.notifications;
 
            AccountNotification[] accountNotifications = notifications1.Notification;
            foreach (AccountNotification accountNotification in accountNotifications)
            {
                Account account = (Account)accountNotification.sObject;
 
                ConnectionStringSettings connectionString = ConfigurationManager.ConnectionStrings["ERP"];
                using (SqlConnection cn = new SqlConnection(connectionString.ConnectionString))
                {
                    using (SqlCommand command = new SqlCommand("salesforce_updateAccount", cn))
                    {
                        command.CommandType = CommandType.StoredProcedure;
 
                        command.Parameters.Add("@idSalesforce", SqlDbType.VarChar).Value = account.Id;
                        command.Parameters.Add("@name", SqlDbType.VarChar).Value = account.Name;
                        command.Parameters.Add("@number", SqlDbType.VarChar).Value = (object)account.AccountNumber ?? DBNull.Value;
                        command.Parameters.Add("@address", SqlDbType.VarChar).Value = (object)account.BillingStreet ?? DBNull.Value;
                        command.Parameters.Add("@city", SqlDbType.VarChar).Value = (object)account.BillingCity ?? DBNull.Value;
                        command.Parameters.Add("@state", SqlDbType.VarChar).Value = (object)account.BillingState ?? DBNull.Value;
                        command.Parameters.Add("@postalCode", SqlDbType.VarChar).Value = (object)account.BillingPostalCode ?? DBNull.Value;
                        command.Parameters.Add("@country", SqlDbType.VarChar).Value = (object)account.BillingCountry ?? DBNull.Value;
 
                        cn.Open();
                        command.ExecuteNonQuery();
                    }
                }
            }
 
            notificationsResponse response = new notificationsResponse();
            response.Ack = true;
 
            return new notificationsResponse1() { notificationsResponse = response };

Es muy importante fijar la variable Ack de la respuesta a true, de lo contrario Salesforce pensará que ha ocurrido un error y mantendrá el mensaje saliente encolado reintentando el envío a intervalos regulares.

Probando el Mensaje Saliente

Necesitamos publicar nuestro servicio web y hacerlo disponible en internet. La publicación no entro dentro del alcance de este artículo. En mi caso he publicado el servicio en un servidor IIS en nuestra DMZ, y el URL público es http://http.grupolanka.com/Salesforce/WorkflowNotificationServices/AccountNotificationService.svc  (no intentes probarlo, no funcionará)

Ahora necesitamos ir de nuevo en Salesforce y cambiar el URL del mensaje saliente creado anteriormente. En Salesforce dirígete a “Configuración->Compilación->Crear->Flujo de trabajo y aprobaciones->Mensajes salientes”. Edita la definición del mensaje saliente (en el ejemplo es SendAccountToERP) y edita el campo “URL de extremo”:

salesforce crm url de extremo

Guarda el mensaje. Ahora, abre una cuenta y realiza una modificación, Salesforce realizará una llamada a nuestro servicio web pasándole los campos que hemos definido en la creación del mensaje saliente.

¡Y eso es todo! Ya tienes a tu Salesforce “hablándole” a un sistema tercero.

La versión original de este articulo ha sido publicada en http://www.giovannimodica.com/

Grupo Lanka es partner certificado para Salesforce España.