2 Comments

(Puedes ver este artículo en español aquí)

A high percentage of the Salesforce projects I’ve seen require some kind of integration with backends systems, such as ERPs, databases, or even other CRMs. Salesforce offers a lot of options to make these integrations possible: workflow outbound messages, SOAP/REST callouts, batch integration with DataLoader, etc. Of course there’s lot of documentations on the topic, but most is based on Java and there is little documentation on using .Net. I’m a .Net developer, so I will provide a few examples on how to integrate with Salesforce using Visual Studio and C#. I can’t cover all the possible integration scenarios in just one article, so I will focus on one scenario that I consider as my first option when I design the architecture of a solution that requires integration: workflow outbound messages.

The Requirements

Allow me explain the concepts by using a concrete example: one of our clients required to integrate their Salesforce org with their ERP. Both Salesforce and the ERP manage accounts, but they required Salesforce to be the master system for accounts. The integration was based on the following rules:

  • Accounts are created in Salesforce (not in the ERP)
  • Any modification to account data in Salesforce needs to be reflected on the ERP (if the account exists in the ERP)

The above rules translate to the following:

  • We can create a trigger on account that does a callout to a web service in the ERP, but there’s an easier way (less code): use a workflow outbound message.
  • We will use a workflow outbound message in account to fire when the account is created or modified and send some of the fields we need to our ERP.

Let’s dive into code!

Workflow Outbound Message in Salesforce

In Salesforce, go to “Setup->Build->Create->Workflow & Approvals->Workflow Rules”. Create a new rule, and base it on the Account object:

Workflow account rule

Click next. Give it a name (I will use UpdateAccountOnERP) and for the evaluation criteria specify “created, and every time it’s edited”. We want this option since we would like to update the account on the ERP every time a modification is made in Salesforce. We want this workflow to be triggered every time, so for the rule criteria, choose “formula evaluates to true”, and specify true as the formula. The workflow should look as this:

Workflow account rule details

Click save and next. On the next screen you will add a workflow action. Click on the “Add Workflow Action” dropdown and select “New Outbound Message”. On the New Outbound Message screen, give it a name and unique name (I will use SendAccountToERP), for the endpoint put any url since we don’t have our web service yet, and finally select all the fields that you would like to send to the ERP. When finished, click Done. You will be taken to the workflow rule screen. Make sure you activate the workflow (click on the “Activate” button).

Account workflow rule summary

We have created an outbound message that will be triggered when an account is modified, but the outbound message will fail since we have not created a web service to handle it. For this we need to get the WSDL of the outbound message. Click on the outbound message description and you will be taken to Workflow Outbound Message Detail:

Account outbound message detail

Click on the hyperlink “Click for WSDL”. A new window with the WSDL will be opened. Save the WSDL to disc (I named it accountWorkflowOutboundMessage.wsdl), we will need it in Visual Studio.

Web Service in Visual Studio (using WCF)

Now that you have the WSDL for the outbound message, we need to create a web service to handle the message. This article gives you an overview of how you can create an asmx web service, but I rather work with WCF instead (asmx is kind of old and WCF is supposed to replace it, isn’t?). Also, I will use Visual Studio 2013 Community edition for this. Open Visual Studio and create a new empty ASP.Net Web Application:

New Asp.Net projectEmpty Asp.Net project



We will add a WCF service to the project. Right click on the project name in Solution Explorer and choose “Add->New Item”. Select “WCF Service”:

New WCF web wervice

Visual Studio will add the required assemblies to the project and will create three files: IAccountNotificationService.cs, AccountNotificationService.svc and the underlying AccountNotificationService.svc.cs. We will need to replace the interface definition to match the definition of the outbound message. Add the WSDL file to your project. Open a command prompt on the directory where the WSDL resides (if you have the Productivity Power Tools extension you can right click on the project in Solution Explorer and choose “Power Commands->Open Command Prompt”). At the command prompt type the following:

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

The svcutil command creates the interface for the web service definition. Here is the output of the command:

svcutil output

Notice that the command replaced the contents of the file IAccountNotificationService.cs with the web service definition of the WSDL. You project should now look like this:

Solution explorer

Now, there are some tricks to make this work. Open IAccountNotificationService.cs and change the following:

  • Put the whole class inside the namespace of the project (in my case is the name of the project WorkflowNotificationServices)
  • Change the name of the interface from NotificationPort to IAccountNotificationService.
  • Change the ConfigurationName parameter of the ServiceContractAttribute attribute from NotificationPort to AccountNotificationService.
  • Remove the parameter ReplyAction=”*” from the OperationContractAttribute attribute. This is important in order to make things work.
  • At the end of the file, remove the interface NotificationPortChannel and the class NotificationPortClient (we don’t need these since they are used by a client consuming the web service).

The interface should now look as the following (highlighted lines are the one that changed):

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
}

Next, open the file AccountNotificationService.svc.cs, remove the DoWork method and implement the IAccountNotificationService interface (place the cursor on text for the name of the interface and press Crtl+. and choose “Implement interface IAccountNotificationService”).

Finally, open web.config and replace it with the following:

<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>

Notice in line 3 I’ve created a connection string to the database for the ERP. I will use this later in the implementation of the web service. Also notice the declaration of the web service in lines 18-20.

You project should compile at this point. Also, we should be able to get the WSDL for our web service. Run the project and using a browser connect to the web service. You should see something like this:

Account web service

All we have left is to write the code to update our ERP. In this example I will just call a stored procedure on a SQLServer database to update the ERP (is just an example, you can do here whatever you need to communicate with the backend). My service implementation looks like this:

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 };
        }
    }
}

It is important to set the Ack variable of the response to true, otherwise Salesforce will think there was an error and will keep the outbound message in the queue and retry to send it at regular intervals.

Testing the Outbound Message

We need to publish our web service and make it available on the internet. The publishing is outside of the scope of this article. In my case I published it on an IIS server in our DMZ, and the public url is http://http.grupolanka.com/Salesforce/WorkflowNotificationServices/AccountNotificationService.svc (don’t try it, it won’t work)

Now I need to go back to Salesforce and change the url of the outbound message I created earlier. In Salesforce go to “Setup->Build->Create->Workflow & Approvals->Outbound Messages”. Edit the outbound message definition (in the example is SendAccountToERP) and edit the “Endpoint URL” field:

Account outbound message url

Save it. Open an account and make a modification, Salesforce will now call the web service passing the fields specified in the outbound message definition.

And that’s it! You can now have Salesforce to “talk” to your backend. In a next article I will complicate things a bit and let the web service call back to Salesforce for additional data. You can get the sample project here:

4 Comments

(Puedes ver este artículo en español aquí)

Salesforce allows to access external data sources using OData protocol. This feature is actually one of the most useful feature when designing and integration strategy with backend systems. Instead of using web services or the DataLoader to put the data into Salesforce, we can get up-to-date data directly from the third-party system by using an external data source. By using an external data source, and the external object it creates, we can access information located outside Salesforce as if it were stored inside Salesforce. The only disadvantage at the moment is that an external data source is read only, so we cannot use it to update data on third-party system. But still, this opens a whole lot of integration scenarios for us.

Let’s see an example of when this might come useful. I recently worked on a project where an integration between Salesforce and the ERP was required. Sales executives needed to see Account administrative alerts (e.g. “client has pending bills”, or “client has a claim on its latest bill”, etc.) in Salesforce. These alerts were generated in the ERP by the administrative users. Clearly we needed to bring those alerts to Salesforce.

One approach would be to use the DataLoader to upload alerts to Salesforce in batch, with a predefined frequency (once a day for example). This solution is quick and easy, but requires a lot of steps: creating the Alerts object in Salesforce, setting up the DataLoader command line mode to retrieve the data directly from the ERP and do an upsert in Salesforce, and using a scheduler program to do execute the DataLoader job at regular intervals. But it turns out there is an easier way! Well, easier if you read this post :-)

Instead of putting the data into Salesforce, we will let Salesforce retrieve the data dynamically from the ERP. For this we will use an external data source and create an external object. You can create an external object based in OData (unfortunately only version 2.0, although at the time of writing this OData is at version 4.0). Let’s see how we can use Visual Studio and .Net to expose the data from the ERP.

Setting up the Example

I will use a pretty simple example. I will simulate the ERP as a SQLServer database. We have an account table and an alert table that defines the alerts for our accounts. Our ERP will look like this:

Database

Use the following script to create the database and the tables:

/****** Object:  Database [ERP]    Script Date: 12/30/2014 17:22:48 ******/
CREATE DATABASE [ERP] ON  PRIMARY 
( NAME = N'ERP', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\ERP.mdf' , SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'ERP_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\ERP_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO

USE [ERP]

/****** Object:  Table [dbo].[Account]    Script Date: 12/30/2014 17:22:49 ******/
CREATE TABLE [dbo].[Account](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [name] [varchar](50) NOT NULL,
 CONSTRAINT [PK_Account] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object:  Table [dbo].[Alert]    Script Date: 12/30/2014 17:22:49 ******/
CREATE TABLE [dbo].[Alert](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [accountId] [int] NOT NULL,
    [alertText] [varchar](50) NULL,
 CONSTRAINT [PK_Alert] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
/****** Object:  ForeignKey [FK_Alert_Account]    Script Date: 12/30/2014 17:22:49 ******/
ALTER TABLE [dbo].[Alert]  WITH CHECK ADD  CONSTRAINT [FK_Alert_Account] FOREIGN KEY([accountId])
REFERENCES [dbo].[Account] ([id])
GO
ALTER TABLE [dbo].[Alert] CHECK CONSTRAINT [FK_Alert_Account]
GO

And populate the database with the following script:

insert into Account values('ModicaTech')
insert into Account values('Microsoft')
insert into Account values('Salesforce')
insert into Alert values(1,'Client requesting support')
insert into Alert values(2,'Client has pending receipts')

Creating the OData Web Service

Using Visual Studio 2013 Community edition, create a new empty ASP.Net Web project. We will add an Entity Framework data model to this project. Right click on the project name and choose “Add New Item”. Select “ADO.ET Entity Data Model” and name it ErpModel.

New ADO.Net data model

On the Entity Data Model Wizard, select “EF Designer from database” and click Next. Create a new connection to the SQLServer and the ERP database:

New connectionERP connection

Click Next and make sure you select “Entity Framework 5.0” as the version of Entity Framework to use, and click Next. Select both tables:

EF select tables

Click Finish. The Wizard will generate the code to connect to the database. We will now add the OData service to the project. Right click on the project name and choose “Add New Item…". Select “WCF Data Service 5.6” and name it ErpService.svc:

New OData service

The wizard will create the ErpService.svc.cs file and open it on the editor. Replace the text in the TODO for the name of the entity data model. The file should look like this:

using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;

namespace ErpODataService
{
    public class ErpService : DataService<ERPEntities>
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }
}

Notice line 11 where we specify the name of our entity data model. Line 16 specifies read access for all our entities. Finally, line 18 sets the version of OData to 2.0 (the one required by Salesforce). If you run the project you should see something like this:

OData service

Publish the web service to a public internet URL. I have published in an IIS in our DMZ with the URL http://www.grupolanka.com/Salesforce/ErpODataService/ErpService.svc (don’t try, it wont work)

Creating the External Data Source in Salesforce

Go to Salesforce, then go to “Setup->Build->Develop->External Data Sources” and click on the “New External Data Source” button. Specify a label and name and make sure you select “Lighting Connect: OData 2.0” as the type. In the server url put the url to access the OData service we created, and in the format select AtomPub. Specify anonymous as the authentication since we haven’t specify any security on the OData service (for simplicity). The external data source should like like the following:

 External data source

When you save it, you will click on “Validate and Sync” button:

Validate and sync external data source

Select the Alerts object and click the Sync button. Salesforce will create the Alerts__x object. Click on it to see its properties:

Alerts object

We will now create a relationship between this external object and the Account object to link alerts to accounts. But first we need to create a custom field on Account and mark it as an external id in order to make the relationship. Go to “Setup->Build->Customize->Accounts->Fields“ and in the “Account Custom Fields & Relationships” create a new custom field called External_ID. Make sure to mark it as “Unique” and “External ID”:

External id

Go to back to the Alerts external object (“Setup->Build->Develop->External Objects”). On the “Custom Fields & Relationships” section click on New. Choose “Indirect Lookup Relationship” as the field data type. Next select Accounts in the “Related To” field:

Related to

Click Next. In the target field select custom field for account we created before (External_ID__c)

Target field

Click Next. Enter a label and name for the field. Make sure you specify accountId as the “External Column Name”. This is the field in the external data source that contains the foreign key to account.

Indirect lookup relationship properties

Click Next on the rest of the Wizard.

We now need to create a Tab for the new object and add it to an application. Go to “Setup->Build->Create->Tabs” and create a new tab for the Alerts object. Select a theme. Then assign the tab to an application. You should now see something like this:

Alerts tab

Click on one of the records to see its details:

Alert detail


Seeing the External Data on a Salesforce Object

Now, let’s open an account (any account). Specify a value for the field “External Id”. Use 1 as the value so we will get all the alerts associated to the account with id 1

accountDetail

Notice that you will need to edit the layout for the related list to show the alertText field.

And this is it! We can dynamically see the data from our ERP in Salesforce. Let’s do a test. Insert a new alert for the account with id 1:

insert into Alert values(1,'This thing works!')

Refresh the account in Salesforce, you should see the new alert:

New alert


You can find the Visual Studio project here:


0 Comments

(Puedes ver este artículo en español aquí)

In a previous article, Call a .Net WCF Service from Salesforce, I explained how to make an external call in a Salesforce trigger to get data from a third party system by using a custom web service created in .Net WCF. This approach was using SOAP as the messaging protocol between Salesforce and our web service. We all know the advantages and disadvantages of SOAP compared to REST, but the truth is that REST is the preferred choice when it comes to creating web services. So, in this article I will modify the example presented in the previous post to use REST instead.

Allow me to refresh the problem we were trying to solve: our client wants to create sales quotes in Salesforce, but wants the price of products to come from the ERP. Our solution was to create a web service (a SOAP web service) in .Net WCF that given a product identifier returned the price of the product, we then called this service from a trigger defined in the QuoteLineItem object in Salesforce. We will keep the same architecture to the solution (the trigger and the web service) but this time we will use REST. I suggest you read the previous article to get a better understanding of what we would like to accomplish.

The WebAPI Service

Let’s start by creating our REST service. We will use an MVC WebAPI project in Visual Studio for this. I will use Visual Studio Community Edition to do this. Open Visual Studio and create a new “ASP.Net Web Application” name ErpApiService. Select “Web API” as the template and make sure you select “No Authentication” from the “Change Authentication” button (for simplicity we will not deal with security in this article).

New Asp.Net projectNew Web API project

Visual Studio will create a sample Web API project with a sample controller. Go to the Controllers folder and rename the file ValuesController.cs to ProductController.cs. If Visual Studio asks you to apply the rename to all references in code select yes. Replace the contents of the ProductController.cs file with the following:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace ErpApiService.Controllers
{
    public class ProductController : ApiController
    {
        // GET api/product/getPriceForCustomer?id={id}
        public decimal GetPriceForCustomer(string id)
        {
            try
            {
                ConnectionStringSettings connectionString = ConfigurationManager.ConnectionStrings["ERP"];
                using (SqlConnection cn = new SqlConnection(connectionString.ConnectionString))
                {
                    using (SqlCommand command = new SqlCommand("salesforce_getProductPrice", cn))
                    {
                        command.CommandType = CommandType.StoredProcedure;

                        command.Parameters.Add("@productId", SqlDbType.VarChar).Value = (object)id ?? DBNull.Value;
                        SqlParameter priceParameter = command.Parameters.Add("@price", SqlDbType.Money);
                        priceParameter.Direction = ParameterDirection.Output;

                        cn.Open();
                        command.ExecuteNonQuery();

                        return (decimal)command.Parameters["@price"].Value;
                    }
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.Message);
                return 0;
            }
        }
    }
}

The highlighted line is the most important in this example, it defines the signature of the REST resource. By convention, if a method starts with Get (as in our example) the WebAPI engine will use the HTTP GET method to call it. The rest of the code is just ADO.Net boilerplate code to call a stored procedure in a database (in this case a SQLServer database).

Publish this web service to a public URL. In my case I published it to an IIS server in the DMZ and the URL is the following: http://www.grupolanka.com/Salesforce/ErpApiService/api/Product?id={id} (don’t try it, it won’t work). Now, we are ready to consume this REST service from Salesforce.

Calling the REST Service from Salesforce

Go to Salesforce and using the Developer Console let’s create an anonymous code to test our service. Type the following APEX code:

String url = 'http://www.grupolanka.com/Salesforce/ErpApiService/api/Product?id=PADAP20001';

Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod('GET');
HttpResponse res = h.send(req);

system.debug('Price: ' + res.getBody());

You should be able to see the price sent from the ERP in the log.

Now let’s change the class we had from our previous article to do the REST call instead of SOAP. On the Developer Console, open the class QuoteLineItemProcesses class and replace the code with the following:

global with sharing class QuoteLineItemProcesses {
    @future (callout = true)
    public static void updateLinePrice(List<Id> lineItemIds) {
        Boolean changed = false;
        
        QuoteLineItem[] lineItems = [select QuoteId,Product2Id,UnitPrice from QuoteLineItem where Id in :lineItemIds];
        for(QuoteLineItem lineItem : lineItems) {
            Quote quote = [select q.AccountId from Quote q where Id = :lineItem.QuoteId];
            Product2 product = [select External_Id__c from Product2 where Id = :lineItem.Product2Id];
            
            String productCode = product.External_Id__c;    
            
            Decimal price = GetPrice(productCode);
            
            if(price > 0)
            {
                lineItem.UnitPrice = price;
                changed = true;
            }    
        }
        
        if(changed) update lineItems;
    }
    
    private static Decimal GetPrice(String productCode) {
        String url = 'http://www.grupolanka.com/Salesforce/ErpApiService/api/Product?id=' + productCode;

        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint(url);
        req.setMethod('GET');
        HttpResponse res = h.send(req);
        
        return Decimal.valueOf(res.getBody());
    }
}

The callout to a REST service from a trigger follows the same principles we outlined in our previous article: it needs to be called from a class marked with the @future tag (for asynchronous call), it needs to be a static void method, and the trigger needs to be a after insert trigger.

You can now test the call by adding a new line item to an existing quote, Salesforce should call the REST service and assign the product price from the ERP to the line item.

You can get the sample code from here:

1 Comments

(Puedes ver este artículo en español aquí)

In a previous article, A .Net WCF Service Handler for Salesforce Workflow Outbound Messages that Calls Back to Salesforce using SOAP API, I explained how to create a .Net WCF service to handle an outbound message and how to get additional data from Salesforce using the SOAP API. In this article I use the same example but instead of using the SOAP API I will use the REST API. You might wonder why do I need to change my SOAP code to use REST instead, and the answer is simple: you might have the SOAP API disabled (because of the Salesforce edition you have) in your organization and only have the REST API available.

Allow me to refresh what we would like to accomplish: our client wants to integrate accounts in Salesforce with accounts in their ERP, so every time a new opportunity is marked as “Closed Won” in Salesforce the account is created on the ERP. I suggest you to read the previous article which explains how to set things up for the workflow and the Visual Studio project to create the WCF service that handles the workflow outbound message.

Salesforce is able to expose its metadata as a REST service. As we did in the case of SOAP, we could use the REST API to query the account information, but this could be simplified by exposing an APEX class as a REST service. Salesforce makes this very simple. You can follow the steps explained in the Force.com Apex Code Developer's Guide for an overview on how to do this. In this article I will expose an APEX class as a REST service and then I will consume this service from our message handler created in .Net

Exposing the REST Service in Salesforce

This is actually very simple, all you need to do is open the Salesforce Developer Console and from the menu select “File->New->Apex Class”. Name it AccountRestService and replace the code with the following:

@RestResource(urlMapping='/Account/*')
global with sharing class AccountRestService {
    @HttpGet
    global static Account doGet() {
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;
        String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
        Account result = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode FROM Account WHERE Id = :accountId];
        return result;
    }
}

Notice in line 1 that we use the special @RestResource to tell Salesforce that this is actually a REST service. In line 3 we specify that the doGet method will be called by HTTP GET. The URL for this service will be the following:

https://{instanceName}.salesforce.com/services/apexrest/Account/{accountId}

The https://{instanceName}.salesforce.com/services/apexrest is the URL base address for all REST services, and the /Account/{accountId} is specified by our class definition in the urlMapping parameter of the @RestResource tag. For example you could use the following URL to get the details for a specific account:

https://eu5.salesforce.com/services/apexrest/Account/00124000002uzps

There is one thing we haven’t considered yet: security. If you put the above URL into a browser you will get an INVALID_SESSION_ID error. If you read the documentation you will learn that the HTTP request issued against the REST service needs an Authorization HTTP header. You could create a connected app and use OAuth to call the login REST service and get a session id, but this is actually complex (I will explain it in a future article) but in our case, since we are calling this service from an outbound message handler, we already have the session id. Remember from the previous article that we marked the outbound message to “Send Session ID”:

Outbound message

So, all we need to do is to build the right HTTP request from our WCF message handler to call our REST service.


Calling the REST Service from Visual Studio

Open the Visual Studio project you created in the previous article (you can get a sample from here). We will use RestSharp as our REST client to call the service. Using NuGet, add the RestSharp package to your project. RestSharp can automatically transform the JSON text returned from a REST service to a strong typed object. Let’s create a model to encapsulate the data returned from the REST service: in Visual Studio, create a Model folder and add a class named Account to it. Replace the code with the following:

namespace WorkflowNotificationServices.Model
{
    public class Account
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string AccountNumber { get; set; }
        public string BillingStreet { get; set; }
        public string BillingCity { get; set; }
        public string BillingState { get; set; }
        public string BillingPostalCode { get; set; }
        public string BillingCountry { get; set; }
    }
}

We have defined an account class that encapsulates the data from the account object in Salesforce. Notice that for simplicity we have named the properties the same as the object fields in Salesforce (you don’t have to name things the same, you could use JSON.Net to get around it, but is not a topic we would like to do in this article).

Now, open the file OpportunityNotificationService.svc.cs and change the method CreateAccount with the following code:

private bool CreateAccount(string url, string sessionId, WorkflowNotificationServices.Opportunity opportunity)
{
    int recordsAffected = 0;

    Uri uri = new Uri(url);
    RestClient restClient = new RestClient(new Uri(String.Format("https://{0}", uri.Host)));
    RestRequest request = new RestRequest("services/apexrest/Account/{id}");
    request.AddUrlSegment("id", opportunity.AccountId);
    request.AddHeader("Authorization", String.Format("Bearer {0}", sessionId));
    IRestResponse<Model.Account> response = restClient.Execute<Model.Account>(request);

    Model.Account account = response.Data;

    ConnectionStringSettings connectionString = ConfigurationManager.ConnectionStrings["ERP"];
    using (SqlConnection cn = new SqlConnection(connectionString.ConnectionString))
    {
        using (SqlCommand command = new SqlCommand("salesforce_crearCliente", 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;
}

The lines highlighted are the important ones. Notice in line 6 how we use the URL we got from the SOAP message sent by Salesforce to our WCF service and we get the host (and thus the instance name) we need to send the HTTP request to. In line 7 we create an HTTP request using RestSharp and specify the endpoint of our REST service, as explained before. In line 8 we specify the account Id we got from the SOAP of the outbound message on opportunity. In line 9 we set the security part we need to make this work. We need to set the Authorization header to the value Bearer {sessionId}. The session id we get it again from the SOAP of the outbound message sent by Salesforce (remember we marked the “Send session ID” field in the outbound message definition). Finally, in line 12 we make the HTTP call and tell RestSharp to convert the result to our Account object we created before. The rest of the code is just the same, using the values returned by the REST service (now strongly typed into a class) to call a stored procedure on the ERP.


Testing the Call Back

To test our web service we just follow the same steps outlined in the previous article. Notice that we only changed the API used to obtain data from Salesforce: we were using SOAP before and now we use REST

You can get the sample project here:

2 Comments

(Puedes ver este artículo en español aquí)

In the web you can find information on how to call a web service from Salesforce, but there is little information on how to call web service created in .Net. The examples you find are based on asmx web services mostly. In this article I will explain how to call a .Net WCF web service created with Visual Studio 2013 Community.

To make things more exciting I will call this web service from a trigger and explain how to code things in Salesforce to make this possible.

The WCF Service

Let’s start by creating the web service. We will simulate the following scenario: when we create a quote in Salesforce and add products to it we want the product price to be dynamically assigned by the ERP and not by Salesforce (Salesforce manages Price Books for this, but we don’t want this in this example).

Start Visual Studio and create a new empty ASP.Net Web Application and call it ErpService. Add a WCF Service to it and call it ProductService.svc. Visual Studio will add the required assemblies to the project and create three files: IProductService.cs, ProductService.svc and ProductService.svc.cs.

Modify your web.config file to add the service definition:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <connectionStrings>
        <add name="ERP" connectionString="Data Source=localhost;Initial Catalog=ERP;Integrated Security=True" providerName="System.Data.SqlClient"/>
    </connectionStrings>
    <system.web>
        <webServices>
            <protocols>
                <clear/>
                <add name="HttpSoap" />
                <add name="Documentation"/>
            </protocols>
        </webServices>
        <compilation debug="true" targetFramework="4.5" />
        <httpRuntime targetFramework="4.5" />
    </system.web>
    <system.serviceModel>
        <services>
            <service name="ErpService.ProductService">
                <endpoint binding="basicHttpBinding" name="Product" contract="ErpService.IProductService"/>
            </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>

Open IProductService.cs and replace it with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace ErpService
{
    [ServiceContract]
    public interface IProductService
    {
        [OperationContract]
        decimal GetPriceForCustomer(string productId);
    }
}

Our web service exposes just one method to get the price of a product given its Id. Open ProductService.svc.cs and replace it with the following code:

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;

namespace ErpService
{
    public class ProductService : IProductService
    {
        public decimal GetPriceForCustomer(string productId)
        {
            try
            {
                ConnectionStringSettings connectionString = ConfigurationManager.ConnectionStrings["ERP"];
                using (SqlConnection cn = new SqlConnection(connectionString.ConnectionString))
                {
                    using (SqlCommand command = new SqlCommand("salesforce_getProductPrice", cn))
                    {
                        command.CommandType = CommandType.StoredProcedure;

                        command.Parameters.Add("@productId", SqlDbType.VarChar).Value = (object)productId ?? DBNull.Value;
                        SqlParameter priceParameter = command.Parameters.Add("@price", SqlDbType.Money);
                        priceParameter.Direction = ParameterDirection.Output;

                        cn.Open();
                        command.ExecuteNonQuery();

                        return (decimal)command.Parameters["@price"].Value;
                    }
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.Message);
                return 0;
            }
        }
    }
}

The implementation of our web service is pretty straightforward: it uses ADO.Net to connect to our ERP (a SQLServer database) and call a stored procedure passing the id of the product.

Our web service is ready. You need to publish this web service on the internet for Salesforce to see. The publication is outside the scope of this article. In my case I published it on an IIS server in our DMZ and the URL to reach it is: http://www.grupolanka.com/Salesforce/ErpService/ProductService.svc (don’t try it, it won’t work).

We need the WSDL for the web service. Go to the web service and click on the link for the singleWsdl:

Web service

I saved the WSDL locally with the name of productService.wsdl.

Now that we have our web service, let’s go back to Salesforce to create a class to call it.

Adding the Web Service in Salesforce

In Salesforce go to “Setup->Build->Develop->Apex Classes”, and click on the button “Generate from WSDL”. Salesforce will ask you for the WSDL file. Click on the “Choose File” button and select the productService.wsdl file and then click on “Parse WSDL”. You will get the following error:

Parse WSDL error

Here comes the tricky part, we need to modify our WSDL for Salesforce to parse it without errors. Using an XML editor (I use Notepad++ with the XML tools plugin), locate and remove the following chunck of XML:

<xs:schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/">
            <xs:element name="anyType" nillable="true" type="xs:anyType"/>
            <xs:element name="anyURI" nillable="true" type="xs:anyURI"/>
            <xs:element name="base64Binary" nillable="true" type="xs:base64Binary"/>
            <xs:element name="boolean" nillable="true" type="xs:boolean"/>
            <xs:element name="byte" nillable="true" type="xs:byte"/>
            <xs:element name="dateTime" nillable="true" type="xs:dateTime"/>
            <xs:element name="decimal" nillable="true" type="xs:decimal"/>
            <xs:element name="double" nillable="true" type="xs:double"/>
            <xs:element name="float" nillable="true" type="xs:float"/>
            <xs:element name="int" nillable="true" type="xs:int"/>
            <xs:element name="long" nillable="true" type="xs:long"/>
            <xs:element name="QName" nillable="true" type="xs:QName"/>
            <xs:element name="short" nillable="true" type="xs:short"/>
            <xs:element name="string" nillable="true" type="xs:string"/>
            <xs:element name="unsignedByte" nillable="true" type="xs:unsignedByte"/>
            <xs:element name="unsignedInt" nillable="true" type="xs:unsignedInt"/>
            <xs:element name="unsignedLong" nillable="true" type="xs:unsignedLong"/>
            <xs:element name="unsignedShort" nillable="true" type="xs:unsignedShort"/>
            <xs:element name="char" nillable="true" type="tns:char"/>
            <xs:simpleType name="char">
                <xs:restriction base="xs:int"/>
            </xs:simpleType>
            <xs:element name="duration" nillable="true" type="tns:duration"/>
            <xs:simpleType name="duration">
                <xs:restriction base="xs:duration">
                    <xs:pattern value="\-?P(\d*D)?(T(\d*H)?(\d*M)?(\d*(\.\d*)?S)?)?"/>
                    <xs:minInclusive value="-P10675199DT2H48M5.4775808S"/>
                    <xs:maxInclusive value="P10675199DT2H48M5.4775807S"/>
                </xs:restriction>
            </xs:simpleType>
            <xs:element name="guid" nillable="true" type="tns:guid"/>
            <xs:simpleType name="guid">
                <xs:restriction base="xs:string">
                    <xs:pattern value="[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}"/>
                </xs:restriction>
            </xs:simpleType>
            <xs:attribute name="FactoryType" type="xs:QName"/>
            <xs:attribute name="Id" type="xs:ID"/>
            <xs:attribute name="Ref" type="xs:IDREF"/>
        </xs:schema>

This seems to confuse the Salesforce parser so we will remove it (don’t worry, it will work even without this). Save the WSDL and try to parse it wit Salesforce again. Now you should get something like this:

Parsed WSDL

Specify a valid name for the Apex Class (I used ProductService as the class name). Click on “Generate Apex code” button. Salesforce should create the apex class with no errors. Now let’s test it before we continue. Open the Developer Console and press Crtl+E to open the Execute Anonymous window. Enter the following code:

ProductService.Product productService = new ProductService.Product();
Decimal price = productService.GetPrice('PADAP20001');
system.debug(price);

We’re calling our web service and if you open the log and watch for debug messages you should see the price we got back from the ERP.

Creating the Trigger with a Web Service Callback

Now we now that Salesforce can call our web service. Having a trigger to call it might not be obvious. Although you can find information on the web on how to do this, I will explain it here for completeness.

A trigger cannot directly call a web service. This is a way for Salesforce to guarantee that a trigger will not get stuck in some external call (for which they’re not responsible) and thus compromise the execution of the trigger. To avoid this a trigger can call a web service in an asynchronous way. For a trigger to call a web service you need to create a class with a static method marked with the special @future tag. Using the Developer Console in Salesforce, create a new Apex Class and call it QuoteLineItemProcesses. Enter the following code:

global with sharing class QuoteLineItemProcesses {
    @future (callout = true)
    public static void updateLinePrice(List<Id> lineItemIds) {
        Boolean changed = false;
        
        QuoteLineItem[] lineItems = [select QuoteId,Product2Id,UnitPrice from QuoteLineItem where Id in :lineItemIds];
        for(QuoteLineItem lineItem : lineItems) {
            Product2 product = [select ProductCode from Product2 where Id = :lineItem.Product2Id];
            
            String productCode = product.ProductCode;    
            
            ProductService.Product productService = new ProductService.Product();
            Decimal price = productService.GetPrice(productCode);
            
            if(price > 0)
            {
                lineItem.UnitPrice = price;
                changed = true;
            }    
        }
        
        if(changed) update lineItems;
    }        
}

Notice line 2 where we specify the @future tag for the method and we tell that the method will do a callout. The method needs to be a static void method. Lines 12 and 13 are doing the callout (just as we did when testing the web service). The method takes a list of Ids as a parameter, corresponding to the Ids that are being processed in the trigger. For each Id we get the line item and the product code for the line and then do the callout.

For the trigger, create a new trigger associated to QuoteLineItem and call it OnQuoteLineItemAdded:

trigger OnQuoteLineItemAdded on QuoteLineItem (after insert) {
    List<Id> quoteLineItemIds = new List<Id>();

    for (QuoteLineItem quoteLineItem: Trigger.new) {
        quoteLineItemIds.add(quoteLineItem.Id);
    }
    
    if (quoteLineItemIds.size() > 0) {
        QuoteLineItemProcesses.updateLinePrice(quoteLineItemIds);
    }        
}

The trigger needs to be an after insert trigger since we need the Id of the record in order to do the asynchronous update after the callout. Notice how we use the best practices to process batch records. We create an array of Ids and pass it to the class we created above.

And this is it! we are now ready to test it: create an opportunity and add a quote to it, then add a line item to the quote. After adding the line, refresh the quote (remember that it is asynchronous) you should now see the price from the ERP.

Here you can find the project I used to create the web service: