Expose BizTalk WCF/Rest endpoint via Azure Service Bus Relays: "'transportClientEndpointBehavior' could not be loaded." error

Unfortunately the official documentation https://social.technet.microsoft.com/wiki/contents/articles/1664.expose-biztalk-applications-on-the-cloud-using-appfabric-connect-for-services.aspx#Exposing_the_BizTalk_Orchestration_as_a_Service_on_the_Cloud

is not sufficient to accomplish the exposing of a BizTalk WCF or Rest endpoint via an Azure Service Bus relay.

If you are trying to do that and struggling with error like

The type 'Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' registered for extension 'transportClientEndpointBehavior' could not be loaded.

the following steps might be helpful for you:

  1. If you use Service Bus Namespace as described in the official documentation you'll need to install additional tool named "Azure Service Bus Explorer" to be able to see the automatically created relay. To avoid this issue, simple create a Relay service in Azure rather then creating Service Bus Namespace. Basically Service Bus Namespace and Relay services are providing the same features for relays, but the Relay service is specialized for Relay feature only, while Service Bus Namespace is providing further features like Queues and Topics.

  2. After publishing a WCF service by using "BizTalk WCF Service Publishing Wizard" as described in the official documentation (or similar way if you published a Rest service) you need to correct the version of the assembly Microsoft.ServiceBus.dll delivered by wizard. Otherwise you'll get the error "'transportClientEndpointBehavior' could not be loaded." mentioned above, when trying to call the WCF/Rest endpoint locally on the BizTalk. This error is preventing the web service to create required Relay items in the Azure (which is done in the service start), so you'll not see any services there until you fix this error.

    To get rid of this error, go to the IIS folder holding the web application created by WCF Publishing wizard (you can find the path of the location in the completion view in the wizard, for usually something like "C:\inetpub\wwwroot\YourWebAppName"), navigate to the subfolder App_Data/bin and finally replace the assembly Microsoft.ServiceBus.dll in this folder with the latest version of this assembly which can be downloaded from https://www.nuget.org/packages/WindowsAzure.ServiceBus.

  3. Use VS command prompt with elevated permissions to register the new version of "Microsoft.Servicebus.dll" into GAC:

    gacutil -i C:\inetpub\wwwroot\YourWebAppName\App_Data\bin\Microsoft.ServiceBus.dll

  4. Create new application pool in IIS Management console. You are free to use any allowed name for this app pool. As identity of the new pool use the same account used by BizTalkServerIsolatedHost.

  5. Use IIS management console to assign the newly created app pool to your web application previously created by "BizTalk WCF Service Publishing Wizard".

  6. If you don't want to use Windows App Fabric as described in the official documentation to get the web app automatically started, you can simply activate the "Application Initialization" feature in the Windows Server features:

    Server Management -> Manage -> Add Roles and Features -> Activate Web Server (IIS) – Web Server – Application Development – Application Initializiation

    Afterward add the following element to the web.config file of your WCF/Rest web application.

<system.webServer>
     <applicationInitialization doAppInitAfterRestart="true">
       <add initializationPage="/Service1.svc" />
     </applicationInitialization>
</system.webServer>

You can find here more details regarding the settings required to have the web application always running. This is required, since the relay in Azure will exist only if the web app is running.

  1. Use IISRESET from elevated command promt to restart the IIS.

Having all this done, you should be able to open the your BizTalk Endpoint in the browser (localy, on BizTalk server) without any errors.

If you take a look into Azure Portal (or in Azure Service Bus Explorer), you'll see new WCF Relay item automatically created in your Azure Relay Service or Service Bus Namespace Service.

Now to get the BizTalk WCF or Rest endpoint called from anywhere you can use the Uri of the created Azure relay, something like that:

https://yourrelay.servicebus.windows.net/YourWebAppName/Service1.svc/YourServiceOperation

"YourServiceOperation" needs to be configured in the adapter setting of the BizTalk receive Location assigned to your IIS web application.

You need to put following HTTP Header to your request for authentication purposes:

Authorization: SharedAccessSignature [sasToken]

where [sasToken] is to be replaced with a time limited Shared Access Signature token, generated from the Shared Access Policy name and key value for particular resource Uri.

Example:

Authorization: SharedAccessSignature sr=https%3a%2f%2fyourAzureRelay.servicebus.windows.net%2fyourWcfRelay&sig=ZYJkLRqivWHsHxPIUUlUL5LXIEIEFRoEVyDVU%2bxFljs%3d&se=1612453185&skn=yourSasPolicyName

Here is sample code showing how to generate a Sas Token with expiration time 1 hour:

string CreateSASToken(string resourceUri, string keyName, string keyValue)
{
    TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
    var expiry =  Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);

    string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
    HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(keyValue));

    var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
    var sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
                HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
   return sasToken;
}

where:

resourceUri = "https://yourAzureRelay.servicebus.windows.net/yourWcfRelay";

keyName = "your SAS Policy name created for Relay in Azure Portal"

keyValue = "value of the primary or secondary key for the SAS Policy, which is automaticaly generated in Azure Portal on SAS Token creation"

You can use Azure Portal to create Shared Access policy for your Relay or Service Bus Namespace.

Consider that a policy used by the BizTalk Endpoint consumer should be permitted for Sending messages only.

And finally, here is sample code how to call Azure Relay endpoint by using HttpClient:

using(var httpClient = new HttpClient())
{
    var url = string.Format("{0}", RelayAddress);
                                         httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization",    sasToken);
                        
    httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type",   "application/json");
   
   var content = new StringContent("{'Property': 'Value'}");
   var response = await httpClient.PostAsync(resourceUri, content);
   var responseText = await response.Content.ReadAsStringAsync();

}

The approach described worked for me fine on both, BizTalk Server 2016 and BizTalk 2020.

Edit:

If a proxy server is required to be configured on your BizTalk server for outgoing HTTP connections, you will determine that the WCF-WebHttp Receive Adapter used in your Receive location generated by WCF Publisher Wizard doesn't provide configuration option for that.

The easiest way to get Proxy Server configured is to insert the following configuration section to end of the web.config file of your IIS web application:

  ...
  <system.net>
    <defaultProxy>
      <proxy proxyaddress="http://yourProxyServer:port" />
    </defaultProxy>
  </system.net>
</configuration>

Consider "proxyaddress" to be written in lowercase. Trying to apply camelcase ("proxyAddress" what I did) will not work here. Instead of getting an error saying "invalid property" or similar, the proxy setting will simply not be applied.

Theoretically, it should be also possible to apply the proxy server on the webHttpRelayBinding configuration element by using proxyAddress attribute like here:

...
<bindings>
      <webHttpRelayBinding>
        <!-- For some reasons, this doesn't work -->
        <binding name="RelayEndpointConfig" useDefaultWebProxy="false" proxyAddress="http://yourProxyServer:port"> 
          <security relayClientAuthenticationType="RelayAccessToken" mode="Transport" />
        </binding>
      </webHttpRelayBinding>
    </bindings>
...

For some reasons this didn't work for me. The proxy server defined here, was simply ignored. But in contrast to the camelcase behavior mentioned above for "proxyaddress" attribute in "defaultProxy" element, here you'll get an "invalid property" error if you don't use camelcase and write "proxyaddress" instead of "proxyAddress".