Passing SOAP actions to Adapter Inbound Handler for filtering type of listeners

Scenario: How can an Adapter Consumer pass the operation SOAP action to the Adapter Inbound Handler StartListener() method at run-time?

WCF provides a feature called Custom Behaviors that lets us inject common and "cross-cutting" logic into the WCF runtime either at the proxy (i.e., the client) or dispatcher (i.e., the service). You can use attributes or configuration files to add behavior classes to the description tree. You may also consider adding behaviors programmatically when you need to optionally load them depending on some conditional logic that is evaluated at runtime. The following types of behaviors are available (see diagram below to see how these fit into the Service Description tree).

· IServiceBehavior

· IEndpointBehavior

· IContractBehavior

· IOperationBehavior

 

 

My previous posts have talked about an adapter providing inbound support to the Adapter Consumer. In the inbound scenario, a contract is generated by the adapter, but the implementation and hosting facility is provided by the Adapter Consumer. Why? Because, now the back-end application is the client and the WCF Service is provided by the Adapter Consumer to listen to messages from the back-end system. WCF LOB Adapter provides the bridge by translating incoming back-end application messages to WCF messages.

In this post, I want to show you how an Adapter Consumer can use an InboundActionEndpointBehavior to pass a set of actions to the StartListener() method in the Adapter Inbound Handler. The action is a parameter of the SOAP message that identifies the intent of the message. The adapter can then span different logic based on the passed in action at runtime. The InboundActionEndpointBehavior is provided by the WCF LOB Adapter SDK. It enumerates through all the inbound operations and for each message with the direction of input, it adds the action to an inbound action collection, that it uses to pass to the Inbound Handler implementation. An adapter may support 40 operations, but when an Adapter Consumer uses the adapter to generate a (dynamic) contract, that contract will only have operations that the Adapter Consumer selected – let’s say 10. When this contract is used in a ServiceHost to host a service endpoint, at run-time, the inbound action collection will have 10 actions. If the adapter’s inbound handler StartListener() function is generic and doesn’t use the actions, then this collection is irrelevant to the adapter. Otherwise, ensure it is provided at runtime to the adapter. This is necessary as the adapter shouldn’t just start the listener for all the actions supported by it and need only work with actions that are relevant at a point of time depending on the consumer’s usage.

The following code snippet for IInboundHandler-derived class shows how an adapter makes uses of the actions. For example, in this snippet, the adapter is interesting in watching a particular file system, it will only do that for action = “Hello/OnReceiveGreeting”.

public void StartListener(string[] actions, TimeSpan timeout)

{

try

{

foreach (string action in actions)

{

if ("Hello/OnReceiveGreeting".Equals(action))

{

if (inboundWatcher == null)

{

inboundWatcher = new FileSystemWatcher(path);

// Set the callback for the event - when a *.txt is copied into the folder being monitored

inboundWatcher.Created += new FileSystemEventHandler(FileMonitor_Event_Created);

// Begin monitoring

inboundWatcher.EnableRaisingEvents = true;

// Look for files with extension

inboundWatcher.Filter = Connection.ConnectionFactory.Adapter.Inbound_FileFilter;

}

}

}

}

catch (System.ArgumentException e)

{

HelloWorldAdapterUtilities.Trace.Trace(TraceEventType.Error, "HelloWorldAdapterInboundHandler::StartListener", "Error starting the listner." );

throw new AdapterException(e.Message);

}

}

Let's now see how an Adapter Consumer can pass this endpoint behavior using WCF Service Model (configuration & code), BizTalk WCF Adapter and WCF Channel Model. 

 Method 1: Using Service Model - configuration

 

The sample contract, sample service implementation and sample service host program are just shown to provide a complete picture. See the application configuration section below. Since this contract has only 1 operation, the only 1 action will be passed to the adapter inbound handler implementation.

Sample Contract

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]

[System.ServiceModel.ServiceContractAttribute(Namespace="hello://Microsoft.WCF.Samples.Adapters", ConfigurationName="HelloWorld")]

public interface HelloWorld {

   

    [System.ServiceModel.OperationContractAttribute(Action="Hello/OnReceiveGreeting", ReplyAction="Hello/OnReceiveGreeting/response")]

    void OnReceiveGreeting(string name, string data);

}

 

Sample Service Implementation

 

namespace TestHelloWorldAdapter_Service_Inbound

{

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]

    class HelloWorldService : HelloWorld

    {

        public void OnReceiveGreeting(string name, string data)

        {

           Console.WriteLine("Received greeting from " + name + " with " + data);

        }

    }

}

 

Sample Service Host Program

 

    class Program

    {

        static void Main(string[] args)

        {

            // Warning: not catching any exceptions in this snippet

            // Create a ServiceHost for service implementation and

            // use the base address from app.config

            ServiceHost host = new ServiceHost(typeof(HelloWorldService));

            // Open the ServiceHost and start accepting connections

            host.Open();

            Console.WriteLine("The service is ready.");

            Console.WriteLine("Press <ENTER> to terminate service.");

            Console.ReadLine();

            // Close the ServiceHost

            host.Close();

        }

    }

Application Configuration

 

The <behaviorExtension> extension can also be added to the app.config under <system.ServiceModel>/<extensions> section.

App.config

Note: Adapter SDK also supports passing the action through the URI, but it can get quite ugly for large number of actions. Example:

<endpoint address="hello://dummy?action=/Hello/OnReceiveGreeting" binding= . . . />

<?xml version="1.0" encoding="utf-8"?>

<configuration xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0">

  <system.serviceModel>

    <client>

      <endpoint address="hello:/" binding="helloWorldBinding" bindingConfiguration="HelloWorldAdapterBinding"

        contract="HelloWorld" name="HelloWorldAdapterBinding_HelloWorld" />

    </client>

    <services>

      <service name="TestHelloWorldAdapter_Service_Inbound.HelloWorldService">

        <endpoint

            address="hello://dummy"

            binding="helloWorldBinding"

            bindingConfiguration="HelloWorldAdapterBinding"

            behaviorConfiguration = "WithAdapterInboundAction"

            contract="HelloWorld" />

      </service>

    </services>

    <bindings>

      <helloWorldBinding>

        <binding name="HelloWorldAdapterBinding" closeTimeout="00:01:00"

          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

          count="0" overrideWsdlBuilder="false" />

      </helloWorldBinding>

    </bindings>

    < behaviors >

      < endpointBehaviors >

        < behaviorname = "WithAdapterInboundAction">

          < inboundActionEndpointBehavior />

        </ behavior >

      </ endpointBehaviors >

    </ behaviors >

  </system.serviceModel>

</configuration>

The Add Adapter Service Reference Visual Studio Plug-In generates this behavior in the app.config when the consumer has chosen contract type to be inbound. This way an extra step is not required by the Adapter Consumer to add this endpoint behavior. The behaviorExtension can be registerd via app.config or machine.config. If it is done in machine.config, then it doesn't need to be repeated in each app.config.

Machine.config

  <system.serviceModel>

    <extensions>

      <bindingElementExtensions/>

      <bindingExtensions/>

      < behaviorExtensions >

        < add

name = "inboundActionEndpointBehavior"

type = "Microsoft.ServiceModel.Channels.InboundActionElement, Microsoft.ServiceModel.Channels, {Assembly Info}"

        />

      </ behaviorExtensions >

    </extensions>

    <client/>

  </system.serviceModel>

Method 2: Using Service Model – code

 

If you don’t want to use the application configuration, the endpoint behavior can also be added programmatically as well.

    class Program

    {

        static void Main(string[] args)

        {

            ServiceHost host = new ServiceHost(typeof(HelloWorldService));

            HelloWorldAdapterBinding binding = new HelloWorldAdapterBinding();

            ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(HelloWorldService), binding, new Uri(baseUri));

            InboundActionEndpointBehavior behavior = new InboundActionEndpointBehavior();

            endpoint.Behaviors.Add(behavior);

            host.Open();

            Console.WriteLine("The service is ready.");

            Console.WriteLine("Press <ENTER> to terminate service.");

            Console.ReadLine();

            host.Close();

        }

    }

Using BizTalk WCF Adapter in BizTalk Administration Console

 

If the adapter is being used in Microsoft BizTalk Server 2006 R2, the endpoint behavior can be set using the BizTalk WCF Adapter configuration for transport type. Before you can see the behavior in the administrative tool, ensure the behavior is added to the machine.config. (The adapter setup should have made this available.)

1) Ensure endpoint behavior is registered in machine.config

2) Start BizTalk Server Administration Console

3) Select the Receive Location and select WCF-Custom Transport Type

4) Click on Behavior tab

5) Right-click on EndpointBehavior and select “Add Extension”. A “Select Behavior Extension” dialog-box will pop-up.

6) Select the endpoint behavior you are interested in and click OK.

Ideally, the WCF LOB Adapter SDK setup should update the machine.config with this behavior at setup time. The Consume Adapter Service BizTalk Project Add-In should use this endpoint behavior configuration when generating port binding configuration file, that can be imported to create the BizTalk Receive Port / Receive Location with endpoint behavior pre-filled in.

Here is a screen shot:

 

 

 

Using Channel Model

If you are using the “raw” channel level programming to “talk” to the adapter, this is how you can supply the

   class Program

    {

        static void Main(string[] args)

        {

            IChannelListener<IInputChannel> listener = null;

            IInputChannel channel = null;

            try

            {

                string baseUri = "hello://";

                // Create the adapter binding

                HelloWorldAdapterBinding binding = new HelloWorldAdapterBinding();

                binding.Inbound_FileFilter = "*.txt";

                binding.Inbound_FileWatcherPath = @"c:\temp\helloworld";

                // Create a binding parameter collection with a list of SOAP actions to listen on

                InboundActionCollection actions = new InboundActionCollection(new Uri(baseUri));

                actions.Add("Hello/OnReceiveGreeting");

                BindingParameterCollection bpcol = new BindingParameterCollection();

                bpcol.Add(actions);

                // Use the Binding and Binding Parameter Collection to build the channel listener

                listener = binding.BuildChannelListener<IInputChannel>(new Uri(baseUri), bpcol);

                // Listen for messages

                listener.Open();

                // Wait for and accept incoming connections

                bool havechannel = listener.WaitForChannel(TimeSpan.FromMinutes(1));

                channel = listener.AcceptChannel();

                // Open the accepted channel

                channel.Open();

                // Wait for and receive a message from the channel

                     . . . . . .

        }

    }

 ****