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:

2 Comments

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

In a previous post Salesforce Workflow Outbound Messages Handled in .Net with WCF I explained how to create a .Net WCF service to handle workflow outbound messages from Salesforce. In this article I will add a twist to it by making the web service call back to Salesforce to retrieve additional data.

The Requirements

Allow me to explain the concepts by using a concrete example: one of our client required to integrate their Salesforce org with their ERP. The integration is based on the following rule:

  • Accounts (creations and modifications) are maintained in Salesforce
  • Accounts will be created in the ERP only when an opportunity associated with the account is won

The above rules translate to the following

  • We can create a workflow on opportunity that triggers only when the stage of the opportunity is “Closed Won”
  • The workflow will have an outbound message that calls a web service in the ERP
  • Since the outbound message can only send fields of the object it is based on, we cannot send account fields using an outbound message for the opportunity. We can send the account Id (a field in the opportunity) and then have the web service in the ERP to connect back to Salesforce and retrieve the account fields using the account Id.

Let’s dive into code!

The Workflow Outbound Message

Let’s start with the workflow definition. I won’t provide too many details on how to create the workflow since I already talked about this in the article Salesforce Workflow Outbound Messages Handled in .Net with WCF (please read this first if you need more details).

In Salesforce go to “Worflow & Approvals” and create a new workflow rule based on the Opportunity object. Give it a name (I called CreateAccountOnERP) and make sure that the evaluation criteria is set to “created, and any time it’s edited to subsequently meet criteria”. We want this option since we only want to call the web service if the opportunity stage is “Closed Won”. Now specify the rule criteria and make sure that both “Opportunity : Closed” and “Opportunity : Won” fields are true. Your workflow rule should  look like this:

Opportunity workflow rule

Now, add an outbound message action to the workflow. When creating the outbound message, make sure you mark “Send Session Id” (I will explain this later), and also to include the AccountId field as the fields to send. We still don’t have the web service url so put anything in the “Endpoint URL” field:

Opportunity outbound message

Web Service in Visual Studio (using WCF)

Now, get the WSDL for the outbound message and save it locally on disk (I named the file opportunityWorkflowOutboundMessage.wsdl), we will use this later to create our service definition inside Visual Studio.

In Visual Studio, create a new empty ASP.Net Web Application and name it WorkflowNotificationServices (the following steps are basically the same explained in the article Salesforce Workflow Outbound Messages Handled in .Net with WCF). Add the WSDL to the project. Add a new “WCF Service” to the project and name it OpportunityNotificationService.svc. Visual Studio will add three files to your project: IOpportunityNotificationService.cs, OpportunityNotificationService.svc and the implementation OpportunityNotificationService.svc.cs.

Now open a command prompt (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”) and type the following:

svcutil /noconfig /out:IOpportunityNotificationService.cs opportunityWorkflowOutboundMessage.wsdl

Open the IOpportunityNotificationService.cs file and make sure you make these changes:

  • 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 IOpportunityNotificationService.
  • Change the ConfigurationName parameter of the ServiceContractAttribute attribute from NotificationPort to OpportunityNotificationService.
  • Remove the parameter ReplyAction=”*” from the OperationContractAttribute attribute.
  • 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 = "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
}

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

And finally, edit the web.config and replace it with the following:

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

The project should compile at this point and if we run it we should be able to get the WSDL for this web service.

The Call Back to Salesforce

The outbound message we defined earlier for the opportunity will send two important pieces of information: the id of the account, and the session id. We will use these to connect to Salesforce and get the data for the account that is required in the ERP. For this we will use the Salesforce SOAP API.

Go back to Salesforce, and go to “Setup->Build->Develop->API”, you will get the API WSDL screen:

Salesforce API

We need to get the Enterprise WSDL (for a difference between Enterprise and Partner WSDL see this article). Save the WSDL on disk (I named it enterprise.wsdl) and add it to your Visual Studio project. We will create a proxy for this WSDL using Visual Studio.

In the Solution Explorer, right click the References node and choose “Add Service Reference”. Specify the full path to the enterprise WSDL and click Go. Make sure you specify Salesforce as the namespace:

Add service reference

Click OK. Visual Studio will create the proxy to call the Salesforce SOAP API and make the required modifications in web.config. Open the OpportunityNotificationService.cs file and replace the code 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.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;
        }
    }
}

There are a few important things here to notice: In lines 24 and 25 we get the session id and the url that comes from the outbound message from Salesforce. These two parameters are needed to connect back to Salesforce. Remember that when we created the outbound message in Salesforce we marked the “Send Session ID” field. We use this information in lines 54-56 to create a SessionHeader object. In line 58 we build a SOQL query to get the information from account and we use the AccountId field sent in the outbound message. We then use the session header in line 61 to send the query to Salesforce. The rest of the code just process the information we got back from Salesforce and we send this to the ERP (in this example I’m calling a SQLServer stored procedure to simulate the ERP).

Testing the Call Back

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://www.grupolanka.com/Salesforce/WorkflowNotificationServices/OpportunityNotificationService.svc (don’t try it, it won’t work).

Now we need to go back to Salesforce and change the url of the outbound message we created earlier. Edit the outbound message definition (in the example is SendOportunityToERP) and edit the “Endpoint URL” field with the URL.

Now, create a new account and create an opportunity. Change the stage of the opportunity to “Closed Won” and save it. Salesforce will trigger the workflow and call the web service we defined, and this will call back to Salesforce to retrieve the information from the account.

You can get the sample project here:

Everyone is excited about the new Universal Apps. I can now have a new type of project, called a Shared project, where I can put all the common files shared between a Windows 8.1 and Windows Phone 8.1 project. This is a better way around the “Add as a Link” option we had before. But universal apps is more than just a shared project. It seems that the APIs used by both Windows and Windows Phone are converging into one. Granted there still a lot of work to do and I’m not sure if there will be a 100% compatibility between the two frameworks, but this is a great step.

I’m going to talk about how to localize a universal app. In my article Internationalization of a Cross Platform Application I explain how to use the Multilingual App Toolkit in a PCL project to localize an application targeted for multiple platforms (Windows, Windows Phone, Android and iOS). But when I tried to apply the steps in the article to a Universal App I run into some problems. I will still base my localization on resource files, the thing is where do I put these? Should I put them in the new Shared project? or should I keep using PCL projects? Let’s first review our main goals:

  • To have a single place where we can specify strings and have these strings shared by all the platform projects.
  • Strings can be used in the UI as well as programmatically
  • Use the Multilingual App Toolkit to handle all the translations


Resource files in Shared project

When I knew about the new shared project type I thought “great, now I can put my resource files in the shared project”. Well, it turns out you can, but this is not the best approach. To explain why let’s see how our goals stand when we use resource files in shared projects.

As mentioned before, universal apps are basically three projects: a project for Windows 8.1, a project for Windows Phone 8.1 and a Shared project. By putting resource files in the shared project they will be shared by both the Windows and Windows Phone projects. Our first goal is ok.

The convergence of the frameworks between Windows and Windows Phone is also applied to the use of resources. Windows apps (aka Windows Store or WinRT apps) use the .resw resource type. On the other hand, Windows Phone Silverlight apps (the “Silverlight” suffix was added to differentiate them from the new Windows Phone apps which use the WinPRT framework) use .resx resource files. Now, both Windows 8.1 and Windows Phone 8.1 use .resw files..resw files can be used in XAML by specifying the x:Uid attribute in XAML components (this was not possible with .resx files), but these resource files do not have a strong type associated to them (as in the case of .resx resource files). This means that we cannot use resource strings programmatically. You can create a strong type associated to .resw files by using ResW File Code Generator, but it turns out that this extension doesn’t work if the resource is in the shared project. So, our second goal doesn’t stand.

The Multilingual App Toolkit cannot be enabled for shared projects. You have to enable MAT in both Windows and Windows Phone projects. It turns out that the decision of the MAT team regarding universal app was that they “decided to enable MAT based on the specific project instead of the shared project” (see this blog post for details). This means that I would have to do translations twice, one for each project. When you enable MAT in both projects you’re solution will be something like this:

Universal app with MAT

.xlf files are duplicated. So, yes we can use MAT on universal app (our third goal is ok) but since we can’t use it on the shared project our first goal doesn’t stand.

Finally, our goals stand as this:

  • To have a single place where we can specify strings and have these strings shared by all the platform projects: No
  • Strings can be used in the UI as well as programmatically: No
  • Use the Multilingual App Toolkit to handle all the translations: Yes

In summary, having a resource file in the shared project means that I will not be able to use resource strings programmatically and also that I will have to deal with duplicate translations and manual steps to keep these translations up to date. So, resource files in shared projects: not the best approach.


Resource files in PCL project

Our second option is to put resource files in a PCL project. Let’s see how this options stands with our goals. You can perfectly add a PCL project to a universal app solution:

PCL for universal app

When you do this Visual Studio automatically sets the targets for the PCL project to “Windows 8.1” and “Windows Phone 8.1”:

PCL project properties

I suggest to change the “Default namespace” of the PCL project to match the default namespace of the other projects as well. In the PCL project, create a folder structure that is compatible with the project’s default language, in my case “en-US”. Add a new resource (.resw) file to the folder. You should have something like this:

PCL folder structure

(note: you can change the PCL default language by editing the PCL project file. Look for the node DefaultLanguage)

It is required for the resource file to be inside a folder with a name that matches the project default language, otherwise MAT will not find any strings and the .xlf files will not be generated.

Since the targets for the PCL are “Windows 8.1” and “Windows Phone 8.1” the resource files you can add are of type .resw. We already explained that .resw resources do not have an associated strong type and we cannot use strings programmatically. To work around this we will use ResW File Code Generator: in the .resw file properties we will specify values for the properties “Custom Tool” and “Custom Tool Namespace” to be “ReswFileCodeGenerator” and the namespace of our project, respectively:

resw file properties

This will generate a strong type associated with the resource file at compile time.

You can follow the approach explained in How to use the Multilingual App Toolkit (MAT) in a Portable Class Library (PCL) project to see how to use MAT on a PCL:

  • Enable MAT for the PCL project
  • Add translation languages to the project (in my case I’m adding “es” and “it”)

Your PCL project should now look as this:

PCL with MAT

At this point, when you compile the PCL project, all the strings in the default resource file will be copied to the .xlf files by MAT. You can now start translating using MAT.

There are some steps we need to do in order to finish this:

  • Make sure you add the PCL project as a reference to both Windows and Windows Phone projects
  • In the shared folder, create a structure such as the following:

Shared folder structure

Basically you need to create a resource file for each language you support and this resource file needs to be inside a folder with the name of the locale. The .resw files in this shared folder structure can be empty. This is a requirement of both Windows and Windows Phone projects in order to know that the application supports all those languages. At this point you can start using resource strings in the UI as well as programmatically (see more details about how to use PCL resources in cross platform apps by reading the article Internationalization of a Cross Platform Application)

Let’s review if we have accomplished our goals:

  • To have a single place where we can specify strings and have these strings shared by all the platform projects: Yes
  • Strings can be used in the UI as well as programmatically: Yes
  • Use the Multilingual App Toolkit to handle all the translations: Yes


Sample App

You can find a sample VS project here:


Conclusion

In summary, having our resources in a PCL project is still the best option. Shared projects is an excellent idea, but it still lacks support for some other features, as in this case, to be able to handle resource files using MAT and to have .resw files generate a strong type. Let’s hope that the guys in the MAT team provide a way to use MAT in shared projects in the future. In the meantime, let them know that we need this by voting on the user voice idea.