Uso de servicios WCF en .Net para procesar los mensajes de los flujos de trabajo de Salesforce (incluye llamadas a Salesforce utilizando API SOAP)
En una publicación previa Mensajes Salientes de Flujo de Trabajo en Salesforce Procesados en .Net con WCF explique cómo crear un servicio WCF en .Net para procesar mensajes salientes de flujo de trabajo de Salesforce.
En esta nueva publicación voy a profundizar el desarrollo haciendo que el servicio web haga una llamada a Salesforce para obtener datos adicionales.
Los Requerimientos
Permitidme explicar los conceptos usando un ejemplo concreto: el cliente requiere integrar su Organización Salesforce con el ERP. La integración debe considerar las siguientes reglas:
- Las cuentas (creaciones y modificaciones) se mantienen en Salesforce.
- Las cuentas se crearán en el ERP sólo cuando la oportunidad de ventas (asociada a la cuenta) se gana.
Las reglas anteriores se traducen en lo siguiente:
- Podemos crear un flujo de trabajo en la oportunidad que se dispare cuando la etapa de esta oportunidad sea ganada (“Closed Won”).
- El flujo de trabajo tendrá un mensaje saliente que llamará a un servicio web en el ERP.
- Como el mensaje saliente sólo puede enviar campos del objeto en el cual está basado, no es posible enviar campos de la cuenta en un mensaje saliente asociado a oportunidad. Podemos enviar el Id de la cuenta (un campo que tenemos en la oportunidad) y luego hacer que el servicio web del ERP se conecte a Salesforce para obtener los campos de la cuenta usando este Id.
¡Veamos el código!
El Mensaje Saliente del Flujo de Trabajo
Comencemos con la definición del flujo de trabajo. No voy a dar muchos detalles de cómo crear el flujo ya que ya he hablado al respecto en el artículo Mensajes Salientes de Flujo de Trabajo en Salesforce Procesados en .Net con WCF.
En Salesforce, utiliza la opción “Flujos de trabajo y aprobaciones” para crear una nueva regla de flujo de trabajo basada en el objeto Oportunidad. Dale un nombre (yo llamé la mía CreateAccountOnERP) y asegúrate de que el criterio de evaluación sea “creado, y siempre que se modifique para cumplir criterios posteriores”. Seleccionamos esta opción porque queremos llamar al servicio web sólo cuando la etapa de la oportunidad esté ganada. Especifica el criterio de la regla y asegúrate que tanto los campos “Oportunidad: Cerrado” y “Oportunidad: Ganadas” estén en verdadero.
La regla quedará configurada así:
A continuación, agrega una acción de mensaje saliente al flujo de trabajo. Al crear el mensaje saliente asegúrate de marcar “Enviar Id. de sesión” (explicaré esto más adelante), y asegúrate también de incluir el campo AccountId en la lista de campos a enviar.
Todavía no tenemos el servicio web así que en el campo “URL de extreme” coloca cualquier valor:
El Servicio Web en Visual Studio (usando WCF)
A continuación obtenemos el WSDL del mensaje saliente y lo guardamos en nuestro disco local (yo llamé el archivo opportunityWorkflowOutboundMessage.wsdl), usaremos esto luego para crear la definición del servicio web en Visual Studio.
En Visual Studio, crea una nueva “empty ASP.Net Web Application” y llámala WorkflowNotificationServices (los siguientes pasos son basicamente los mismos explicados en Mensajes Salientes de Flujo de Trabajo en Salesforce Procesados en .Net con WCF). Agrega el WSDL al proyecto. ). Agrega el WSDL al proyecto. Agrega un nuevo “WCF Service” al proyecto y llámalo OpportunityNotificationService.svc. Visual Studio agregará tres archivos alproyecto: IOpportunityNotificationService.cs, OpportunityNotificationService.svc y la implementación OpportunityNotificationService.svc.cs.
Ahora abre una línea de comando (si tienes instalado la extensión Productivity Power Tools puedes pinchar con el botón derecho del ratón en el Solution Explorer y seleccionar “Power Commands->Open Command Prompt”) y escribe lo siguiente:
Abre el archivo IOpportunityNotificationService.cs y asegúrate de realizar los siguientes cambios:
- Coloca la clase entera en el namespace del proyecto (en mi caso es el nombre del proyecto WorkflowNotificationServices)
- Cambia el nombre de la interface de NotificationPort a IOpportunityNotificationService.
- Cambia el parámetro ConfigurationName del atributo ServiceContractAttribute de NotificationPort a OpportunityNotificationService.
- Elimina el parámetro ReplyAction=”*” del atributo OperationContractAttribute.
- Al final del archivo, elimina la interface NotificationPortChannel y la clase NotificationPortClient (no necesitaremos esto ya que se usan por clientes que consuman el servicio web).
La interface debería ser como sigue (he resaltado las líneas 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 = "OpportunityNotificationService")] public interface IOpportunityNotificationService { [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 OpportunityNotificationService.svc.cs, elimina el método DoWork e implementa la interface IOpportunityNotificationService (coloca el cursor en el texto del nombre de la interface y presiona Crtl+. y selecciona “Implement interface IOpportunityNotificationService”).
Finalmente, edita el archivo web.config y reemplaza su contenido con lo siguiente:
<configuration> <connectionStrings> <add name="ERP" connectionString="Data Source=localhost;Initial Catalog=Hiperantena;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.OpportunityNotificationService"> <endpoint binding="basicHttpBinding" contract="OpportunityNotificationService"/> </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>
El proyecto debería compilar en este punto y deberíamos poder obtener el WSDL del servicio web.
La Llamada a Salesforce
El mensaje saliente que definimos anteriormente para la oportunidad enviará dos datos importantes: el id de la cuenta, y el id de sesión. Ambos los usaremos para conectarnos a Salesforce y obtener los datos de la cuenta requeridos en el ERP. Para esto usaremos el API SOAP de Salesforce.
En Salesforce, dirígete a “Configuración->Compilación->Desarrollo->API”, donde veremos la pantalla API WSDL:
Necesitamos obtener el Enterprise WSDL (para saber las diferencias entre Enterprise y el Partner WSDL lee este artículo). Guarda el WSDL en tu disco local (yo llamé el archivo enterprise.wsdl) y agrégalo al proyecto de Visual Studio. Vamos a crear un proxy para este WSDL usando Visual Studio.
En el Solution Explorer, pincha con el botón derecho del ratón en el nodo “References” y selecciona “Add Service Reference”. Especifica la ruta completa al archivo WSDL obtenido anteriormente y pincha en “Go”. Asegúrate de especificar Salesforce como el namespace:
Pincha en OK. Visual Studio creará el proxy para llamar al API SOAP de Salesforce y realizará las modificaciones requeridas en web.config. Abre el archivo OpportunityNotificationService.cs reemplaza el código con el siguiente:
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using WorkflowNotificationServices.Salesforce; namespace WorkflowNotificationServices { public class OpportunityNotificationService : IOpportunityNotificationService { public notificationsResponse1 notifications(notificationsRequest request) { bool result = true; notifications notifications1 = request.notifications; string sessionId = notifications1.SessionId; string url = notifications1.EnterpriseUrl; OpportunityNotification[] opportunityNotifications = notifications1.Notification; foreach (OpportunityNotification opportunityNotification in opportunityNotifications) { WorkflowNotificationServices.Opportunity opportunity = (WorkflowNotificationServices.Opportunity)opportunityNotification.sObject; try { if (!CreateAccount(url, sessionId, opportunity)) result = false; } catch (Exception e) { Trace.TraceError(e.Message); result = false; } } notificationsResponse response = new notificationsResponse(); response.Ack = result; return new notificationsResponse1() { notificationsResponse = response }; } private bool CreateAccount(string url, string sessionId, WorkflowNotificationServices.Opportunity opportunity) { int recordsAffected = 0; EndpointAddress address = new EndpointAddress(url); SoapClient soapClient = new SoapClient("Soap", address); SessionHeader session = new SessionHeader() { sessionId = sessionId }; string query = String.Format("select Id, Name, AccountNumber, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry from account where id = '{0}'", opportunity.AccountId); QueryResult result; soapClient.query(session, null, null, null, query, out result); if (result.size > 0) { Account account = result.records[0] as Account; ConnectionStringSettings connectionString = ConfigurationManager.ConnectionStrings["ERP"]; using (SqlConnection cn = new SqlConnection(connectionString.ConnectionString)) { using (SqlCommand command = new SqlCommand("salesforce_createAccount", 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(); recordsAffected = command.ExecuteNonQuery(); } } } return recordsAffected > 0; } } }
Hay varias cosas importantes aquí: en las líneas 24 y 25 obtenemos el id de sesión y el URL que viene del mensaje saliente de Salesforce. Estos dos parámetros son necesarios para conectarse a Salesforce. Recuerda que cuando creamos el mensaje saliente en Salesforce marcamos el campo “Enviar Id. de sesión”. Estos datos los usamos en las líneas 54-56 para crear un objeto SessionHeader. En la línea 58 construimos una consulta SOQL para obtener la información de la cuenta y usamos el campo AccountId enviado en el mensaje saliente. Luego usamos el encabezado de sesión en la línea 61 para enviar la consulta a Salesforce. El resto del código simplemente procesa la información retornada por Salesforce y envía esta al ERP (en este ejemplo estoy llamado a un procedimiento almacenado en SQLServer para simular el ERP).
Probando la Llamada
Necesitamos publicar el servicio web y hacerlo disponible en internet. La publicación no entra 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 https://grupolanka.com/Salesforce/WorkflowNotificationServices/OpportunityNotificationService.svc (no intentes probarlo, no funcionará).
Ahora nos vamos a Salesforce y cambiamos el URL del mensaje de salida creado anteriormente. Edita la definición del mensaje saliente (en este ejemplo es SendOportunityToERP) y edita el campo “URL de extremo” con el URL del servicio web.
A continuación, crea una nueva cuenta y crea una oportunidad asociada. Cambia la etapa de la oportunidad a ganada (“Closed Won”) y guarda el registro. Salesforce disparará el flujo de trabajo y llamará a nuestro servicio web, y este llamará a Salesforce para obtener la información de la cuenta.
Puedes obtener el proyecto de ejemplo aquí:
La versión original de este articulo ha sido publicada en http://www.giovannimodica.com/
Grupo Lanka es partner certificado para Salesforce España.