Calling Salesforce SOAP APIs from a .NET Core or UWP App

I recently needed to build a Universal Windows Platform (UWP) based app which calls into the Salesforce (or rather Force.com) SOAP-based Metadata API to accomplish some of its features. As it turns out, a couple of limitations in the tooling that generates the WCF client proxy code in projects targeting .NET Core or UWP makes this rather tricky:

  1. The tooling currently lacks support for SOAP headers; the Force.com SOAP APIs make heavy use of these for things like adding authentication tokens to the request.
  2. There is an old and well-known bug in the tooling that makes it confused by the construct around the QuickActionLayoutItem type in the Metadata API WSDL.

Assuming you are using Visual Studio 2015, if you're targeting UWP you have access to the traditional Add Service Reference gesture in Solution Explorer, which has been slightly updated to generate a simpler type of client proxy than those generated for the full .NET Framework. If you're targeting .NET Core this tool is gone, but it is being replaced by the WCF Connected Service extension that provides roughly the same functionality for projects that target .NET Core and .NET Standard Library. Both of these issues exist in both of these tools, and the workarounds described below apply equally to both.

I'll show you how to work around both of these problems.

Adding the SessionHeader SOAP Header

If you have ever used the Add Service Reference tooling to generate a WCF client proxy for any of the Force.com SOAP APIs targeting .NET Framework, you may have noticed that the tooling adds parameters to all operations for the SOAP headers described in the WSDL. The most common one is the SessionHeader which is where you will supply the sessionId value consisting of an OAuth access token used to authenticate your app to the SOAP API. The tooling for .NET Framework simply adds a sessionHeader parameter to each method on the generated proxy class where you can supply the SessionHeader object.

In the tooling provided for .NET Core and UWP projects, the SOAP headers specified in the WSDL are simply ignored, and the generated client proxy does not expose any methods, properties or parameters for supplying them, which makes it impossible to call the API since you are unable to supply any authentication.

However, WCF (even on these platforms) provides several extensibility points that we can use to add the necessary SOAP header to the outgoing requests. For this purpose we can write a custom message inpector to modify the outgoing SOAP messages just before sending. The complete solution consists of four simple classes that you can add to your project.

First we need to define the class to represent the actual SessionHeader SOAP header. This class must inherit from the MessageHeader abstract base class defined by WCF:

using System.ServiceModel.Channels;
using System.Xml;

namespace IDeliverable.ForceClient.Core
{
    internal class SessionHeader : MessageHeader
    {
        public SessionHeader(string sessionId)
        {
            mSessionId = sessionId;
        }

        private string mSessionId;

        public override string Name => "SessionHeader";

        public override string Namespace => "http://soap.sforce.com/2006/04/metadata";

        protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            writer.WriteElementString("sessionId", mSessionId);
        }
    }
}

Note that this class takes the session ID as a constructor parameter and holds on to it.

Next we're going to need an implementation of the IClientMessageInspector interface. This implementation will get invoked by WCF just before sending the outgoing SOAP message, giving us an opportunity to modify it:

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;

namespace IDeliverable.ForceClient.Core
{
    internal class SessionHeaderInspector : IClientMessageInspector
    {
        public SessionHeaderInspector(string sessionId)
        {
            mSessionId = sessionId;
        }

        private string mSessionId;

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            
        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            request.Headers.Add(new SessionHeader(mSessionId));

            return Guid.NewGuid();
        }
    }
}

This class constructs a new SessionHeader object and adds it to the Headers collection on the outgoing SOAP message. Note that this class also takes the session ID as a constructor parameter and passes it into the SessionHeader constructor.

To hook it all up we will also need an implementation of the IEndpointBehavior interface. This implementation will be responsible for adding our message inspector to the runtime:

using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace IDeliverable.ForceClient.Core
{
    internal class SessionHeaderBehavior : IEndpointBehavior
    {
        public SessionHeaderBehavior(string sessionId)
        {
            mSessionId = sessionId;
        }

        private string mSessionId;

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(new SessionHeaderInspector(mSessionId));
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            
        }
    }
}

This class has empty implementations of all methods except ApplyClientBehavior, which is where we add our message inspector to the runtime. Note that we are here also (predictably) taking the session ID as a constructor parameter and passing it along to the SessionHeaderInspector constructor.

Finally, to make the it easier to configure a generated client proxy object with our message inpector behavior at runtime, we can also write a simple extension method on the ServiceEndpoint class:

using System.ServiceModel.Description;

namespace IDeliverable.ForceClient.Core
{
    internal static class SessionHeaderExtensions
    {
        public static void SetSessionId(this ServiceEndpoint endpoint, string sessionId)
        {
            endpoint.EndpointBehaviors.Add(new SessionHeaderBehavior(sessionId));
        }
    }
}

The above extension method gives us a very natural way to configure our SOAP client object to send our session ID and (using the Metadata API as an example here) allows us to write code like this:

var instanceUrl = ""; // Set to the base URL of your Force.com org.
var sessionId = ""; // Set to your OAuth access token or other valid session ID.

var client = new MetadataPortTypeClient();
var metadataEndpointUrl = String.Format("{0}/services/Soap/m/{1:F1}", instanceUrl.ToString(), 38);
client.Endpoint.Address = new EndpointAddress(metadataEndpointUrl);
client.Endpoint.SetSessionId(sessionId); // <-- Use our nifty extension method to configure the client proxy.

// TODO: Call operations on client here.

The code assumes that you already know the instance URL of your org as well as the session ID you want to use for authentication. How to obtain those is beyond the scope of this post, but I may create another post showing how to utilize the WebAuthenticationBroker component in UWP to obtain an OAuth access token that can be used as a session ID to call into a Force.com org.

Fixing the QuickActionLayoutItem Generation Issue

This issue is not specific to .NET Core or UWP; it exists in the tooling for .NET Framework also, and if you do a search you will find other places where this bug is described. But it's hard to find it described in the context of the Force.com SOAP APIs and it's such a tricky little gotcha that I decided to describe the symptom and show you how to work around it.

The symptom first appears at runtime. Adding the service reference and going through the WCF client proxy code generation finishes without any issue, and the generated client proxy compiles without issue. However, as soon as you try to call any operation on the client proxy object, you will be presented with the following exception (except your namespaces will of course likely be different):

System.InvalidOperationException: 'IDeliverable.ForceClient.Metadata.ForceMetadata.QuickActionLayoutItem, IDeliverable.ForceClient.Metadata, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null is not assignable from IDeliverable.ForceClient.Metadata.ForceMetadata.QuickActionLayoutItem[], IDeliverable.ForceClient.Metadata, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.'

The reason this happens is that all versions of the WCF client proxy generation tooling share a bug that makes the tool generate incorrect code when it encounters a particular WSDL construct:

<xsd:complextype name="QuickActionLayoutColumn">
<xsd:sequence>
<xsd:element name="quickActionLayoutItems" minoccurs="0" maxoccurs="unbounded" type="tns:QuickActionLayoutItem"></xsd:element>
</xsd:sequence>
</xsd:complextype>

Namely, a complexType that consists of a sequence that has only one element and that element is both optional and unbounded.

To work around this, we can insert an additional dummy attribute on the complexType into the WSDL, like so:

<xsd:complextype name="QuickActionLayoutColumn">
<xsd:sequence>
<xsd:element name="quickActionLayoutItems" minoccurs="0" maxoccurs="unbounded" type="tns:QuickActionLayoutItem"></xsd:element>
</xsd:sequence>
<xsd:attribute name="tmp" type="xsd:string"></xsd:attribute> <!-- Our dummy attribute. -->
</xsd:complextype>

This dummy attribute will end up as an extra property on the QuickActionLayoutColumn class in the generated client proxy code, but you can just ignore it and everything else will work as it should.

That's it. If you're trying to build a .NET Core or UWP app that integrates with Force.com, I hope these workarounds will help. If this post saves someone else as much time and frustration as it took for me to figure these out these workarounds, then I'm happy. :)

Leave a comment