Salesforce Integration with .Net - Callouts

April 8, 2011

Introduction to Salesforce Integration with .Net: Callouts

In the first two articles of the Salesforce Integration with .Net series, Web Services SOAP API and Custom Web Services, we looked at .Net calling into Salesforce to manipulate data and utilize business logic. While calling Salesforce is beneficial, we may need to have Salesforce consume functionality of an existing service. These services can be based on SOAP or REST and hosted on an external infrastructure. For example, you could send documents to Amazon Web Service S3 or post a Tweet. In our example, we are going to examine calling a .Net SOAP Web Service and the ASP.Net MVC REST interface to retrieve data and perform actions.

Build the .Net SOAP Web Service

The first callout we are going to review is consuming a .Net SOAP Web Service from Salesforce. This example will involve creating a button to display a page of vacation packages. The source of those packages will come from this web service. Follow the steps below to build the web service and generate the WSDL:

Create a new ASP.Net MVC web application project.

Add a folder to the project called Services and create a new web service in it called PackageService.asmx.

Create a class named Package.cs to hold the vacation package information we want to return. The Package class will need to have the Serializable attribute added to the class declaration. This will allow it to be serialized in the response SOAP message.

As illustrated in the GetPackages method below, we are returning a list of packages to the calling application. An XmlInclude for the list of packages is required so it will be represented in the generated WSDL.

The namespace in the web service will need to be updated to reflect your organization.

PackageService.asmx .Net Web Service

using System.Collections.Generic;

using System.Web.Services;

using System.Xml.Serialization;

using SalesforceToNet.Models;

namespace SalesforceToNet.Services

{

[WebService(Namespace = "http://businesssharp.com/packageservice/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.ComponentModel.ToolboxItem(false)]

public class PackageService : System.Web.Services.WebService

{

[XmlInclude(typeof(List<Package>))]

[WebMethod(Description = "Returns a list of available packages.")]

public List<Package> GetPackages()

{

return new List<Package>() {

new Package() {

PackageID = "PKG-3256",

Title = "Hawaii Surfing Vacation",

Description = "Surf big waves in Hawaii.",

Cost = 1599.99

},

new Package() {

PackageID = "PKG-3289",

Title = "Somalia Pirate Cruise",

Description = "Horn of Africa pirate races.",

Cost = 3259.15

},

new Package() {

PackageID = "PKG-2991",

Title = "Chupacabra Safari",

Description = "Track the elusive Chupacabra.",

Cost = 1005.97

}

};

}

}

}

Package.cs .Net Class

using System;

namespace SalesforceToNet.Models

{

[Serializable]

public class Package

{

public string PackageID { get; set; }

public string Title { get; set; }

public string Description { get; set; }

public double Cost { get; set; }

}

}

After the service and package data classes are setup, right click on the PackageService.asmx file and select “View in Browser.” This will load the package service’s discovery page as shown in Figure 1.

Click on the Service Description hyperlink to download the WSDL for the service. Save the WSDL as ProjectService.wsdl into a folder of the project root called Wsdls.

Figure 1 - Package Service Discovery Page

As depicted below, you will need to comment out the ProjectServiceSoap12 binding and corresponding port. If left in place, these will cause issues when importing the WSDL into Salesforce.

The final step in preparing the WSDL for Salesforce is to set the SOAP address location to your IP address or a domain accessible from the Internet. If you are performing this at home behind a standard router/firewall setup, then make sure to route the web traffic to your computer and open up any necessary ports on the firewall.

Generate Apex Class from .Net Web Service WSDL

Now that the WSDL has been generated and prepared, it can be imported into Salesforce. An Apex class will be generated during the importation of the WSDL to aid in making the connection and exercising the methods. This is achieved by using a Salesforce Apex feature called “Generate from WSDL.” Follow the steps below to build the Apex service class:

On that page, select the “Generate from WSDL” command button. This will start the generation wizard.

On the Choose WSDL Document page, browse to the PackageService.wsdl file and click the Parse WSDL button (refer to Figure 2).

Figure 2 - Choose WSDL Document Page

Once the parsing has been completed, the Specify Class Names page will be displayed. As illustrated in Figure 3, it will show the parsing results, namespace information, and allow for the entry of the generated class’s name. Enter PackageService and click on the “Generate Apex code” button.

Figure 3 - Specify Class Names Page

The Apex class representing our .Net Web Service will be generated and available for reference in our Salesforce applications. Refer to the Apex code below to view the generated class. As you can see, Salesforce has provided a mechanism that allows for tying external web services into Apex without large amounts of time dedicated to development. Now that the class has been created, we can look at consuming it.

Consuming the SOAP Web Service

Calling an external SOAP web service is not difficult from Apex once we have our WSDL defined class. Only a minor amount of Apex code is needed to utilize the SOAP service. To demonstrate this, we’re going to build a VisualForce page which will be displayed when a custom button is clicked. The first step of the process is to create our controller that will be used by the VisualForce page.

To create the controller, we need to declare the class with sharing and a constructor that accepts the StandardController class as an argument (refer to the PackageManager.cls code below). The controller object will have the current record ID on a Contact sObject. We will grab the fields we need using this ID and store them in a class level variable. In the code below, you will notice the PackageItem subclass. It is used to store the results from the call to the .Net web service we created above. The method getPackageList will be called from our VisualForce page. Inside of this method, the PackageService is instantiated and the GetPackages function on the .Net web service. The results from that call are bound to the internal PackageItem class and returned to the page.

PackageManager.cls Apex Class

public with sharing class PackageManager {

public Contact currentContact;

public class PackageItem {

public string PackageID {get; set;}

public string Title {get; set;}

public string Description {get; set;}

public double Cost {get; set;}

public string ContactName {get; set;}

public string ContactEmail {get; set;}

public string SignupUrl {

get {

return

'http://[YOUR IP ADDRESS HERE]/Package/Signup' +

'?soldBy=' + userinfo.getName() +

'&name=' + this.ContactName +

'&email=' + this.ContactEmail +

'&packageID=' + this.PackageID;

}

}

}

public PackageManager(ApexPages.StandardController controller) {

this.currentContact = (Contact)controller.getRecord();

this.currentContact =

[SELECT Name, Email, Id

FROM Contact

WHERE Id = :currentContact.Id];

}

public List<PackageItem> packages = new List<PackageItem>();

public List<PackageItem> getPackageList() {

PackageService.PackageServiceSoap service =

new PackageService.PackageServiceSoap();

PackageService.ArrayOfPackage packageList =

service.GetPackages();

for (PackageService.Package_x p : packageList.Package_x){

PackageItem item = new PackageItem();

item.PackageID = p.PackageID;

item.Title = p.Title;

item.Description = p.Description;

item.Cost = p.Cost;

item.ContactEmail = currentContact.Email;

item.ContactName = currentContact.Name;

packages.Add(item);

}

return packages;

}

}

Now the controller is built, we need to construct our VisualForce page. Since we plan to call this page from the Contact page, we’ll set the standard controller to Contact. The extensions value will have our controller name. This will cause the controller to be called during the generation of the page. To display the list of packages on the page, the table value is set to PackageList. This will result in the getPackageList method being bound to the table.

The “Sign up” link in the table provides an example of how to redirect to our ASP.Net MVC web application. The URL is generated on the PackageItem internal class. While doing this extends the functionality of the cloud application, you will lose the Salesforce context. However, as seen in the previous articles, Web Services SOAP API and Custom Web Services, you can integrate with Salesforce from .Net.

With the controller and page now properly configured, we can proceed to creating a button to display the VisualForce page. To do this, traverse to Setup > Customize > Contacts > Buttons and Links. Create a new custom button called packages. Refer to Figure 4 for an illustration of how to setup the button. After the button has been created, add it to the Contact Page Layout.

Figure 4 - Create Custom Button or Link

When a contact is selected, the button will be displayed in the page view. After the button has been clicked, the Packages page will be displayed in a popup window. The resulting page will look like the screenshot below.

Figure 5 - Packages VisualForce Page

Building the ASP.Net MVC REST Interface

Not only can we retrieve records using .Net via SOAP Web Services, REST callouts to .Net can also be performed. To do this we are going to take advantage of the built-in routing functionality in the ASP.Net MVC platform. To demonstrate this, we will create a controller to handle newsletter subscriptions.

In the NewsletterController code listed below, the subscription state will be stored in a static variable. Normally, this sort of information would be stored in a persisted manner. Three methods will be added to the controller. The first method “Status,” will return the subscription status for the specified id. The id will be the email address of the contact. The id variable name is used so the default route will map correctly to this method (thus not requiring any updates to the routes collection). The Subscribe and Unsubscribe methods will perform those actions accordingly. All three views will return a plain text string to the calling Salesforce class.

NewsletterController.cs .Net Class

using System.Collections.Generic;

using System.Web.Mvc;

namespace SalesforceToNet.Controllers

{

public class NewsletterController : Controller

{

public static List<string> subscriptions =

new List<string>();

public ActionResult Status(string id)

{

string statusMessage =

(subscriptions.Contains(id))

? "Subscription current."

: "No subscription.";

return View((object)statusMessage);

}

public ActionResult Subscribe(string id)

{

if (!subscriptions.Contains(id))

subscriptions.Add(id);

return View();

}

public ActionResult Unsubscribe(string id)

{

subscriptions.Remove(id);

return View();

}

}

}

With the ASP.Net MVC interface in place, we can build another VisualForce Page to make REST callouts.

Consuming the REST Interface

We are going to need an Apex class to make the REST callouts. As in the previous example, we will start by making the controller which will facilitate this. Again we need to add “with sharing” in the class declaration and the constructor that accepts the StandardController as a parameter. The getSubscriptionStatus will make a call to the ASP.Net MVC application and retrieve the subscription status. The method starts by creating an HttpRequest. After the request object is declared, it has the method and endpoint set. The request is sent to the REST API and a string response is returned. The method completes by returning the status to the VisualForce Page.

NewsletterManager.cls Apex Class

public with sharing class NewsletterManager {

public Contact currentContact;

public Id id;

public NewsletterManager(ApexPages.StandardController controller) {

this.currentContact = (Contact)controller.getRecord();

this.currentContact =

[SELECT Name, Email, Id

FROM Contact

WHERE Id = :currentContact.Id];

id = ApexPages.currentPage().getParameters().get('id');

}

public String getSubscriptionStatus() {

HttpRequest request = new HttpRequest();

request.setMethod('GET');

request.setEndpoint(

'http://[YOUR IP ADDRESS HERE]/Newsletter/Status/' +

currentContact.Email);

Http http = new Http();

HTTPResponse response = http.send(request);

return response.getBody();

}

public PageReference Subscribe() {

SubscriptionRestCall('Subscribe');

return null;

}

public PageReference Unsubscribe() {

SubscriptionRestCall('Unsubscribe');

return null;

}

private void SubscriptionRestCall(string action) {

HttpRequest request = new HttpRequest();

request.setMethod('GET');

request.setEndpoint(

'http://[YOUR IP ADDRESS HERE]/Newsletter/' +

action + '/' +

currentContact.Email);

Http http = new Http();

HTTPResponse response = http.send(request);

}

}

The setup of the Subscribe and Unsubscribe methods is similar to the retrieval of the status. However, there are a couple of differences. First, the methods are not bound to the display, but are invoked by the clicking of command buttons. The second difference is that the methods return a null value. This will cause the page to be reloaded which will grab the updated status. You can see the final rendering of the page in the screenshot below.

Conclusion

In the Salesforce integration with .Net series, we have performed calls using the SOAP API, made requests to custom Salesforce web services, and call outs from Salesforce to a .Net web service and REST interface. Having these abilities will allow for seamless transitions between both environments maximizing the reuse of logic and access to data. Salesforce has provided the tools necessary to interact with external SOAP and REST interfaces. While this series has covered the most common approaches to integrating the environments, there are still additional methods that allow the platforms to be meshed together. This series has given you the necessary tools to solve the Salesforce integration tasks you may encounter.