SessionChannelEndpointBehavior

The next step is to create the SessionChannelEndpointBehavior. This topic lists the code to create the SessionChannelEndpointBehavior.

SessionChannelEndpointBehavior

To receive messages from a sessionful queue or subscription, a WCF service needs to implement a session-aware contract interface like IOrderServiceSessionful, as seen in the following code:

// ServiceBus does not support IOutputSessionChannel.
// All senders sending messages to sessionful queue must use a contract which does not enforce SessionMode.Required.
// Sessionful messages are sent by setting the SessionId property of the BrokeredMessageProperty object.
[ServiceContract]
public interface IOrderService
{
    [OperationContract(IsOneWay = true)]
    [ReceiveContextEnabled(ManualControl = true)]
    void ReceiveOrder(Order order);
}

// ServiceBus supports both IInputChannel and IInputSessionChannel. 
// A sessionful service listening to a sessionful queue must have SessionMode.Required in its contract.
[ServiceContract(SessionMode = SessionMode.Required)]
public interface IOrderServiceSessionful : IOrderService
{
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public class OrderService : IOrderServiceSessionful
{
    [OperationBehavior]
    public void ReceiveOrder(Order order)
    {
        ...
    }
}

WCF receive locations are implemented by a singleton instance of a WCF service class called BizTalkServiceInstance; which implements multiple untyped, generic service contracts:

All of these interfaces are marked with the following attribute: [ServiceContract(Namespace = https://www.microsoft.com/biztalk/2006/r2/wcf-adapter, SessionMode = SessionMode.Allowed)]In particular, the SessionMode property of the ServiceContract attribute is set to SessionMode.Allowed. This setting specifies that the contract supports sessions if the incoming binding supports them. In other words, all the service contracts implemented by the BizTalkServiceInstance support, but do not require, a session.

When the following conditions are satisfied, the WCF runtime always selects the IInputChannel over the IInputSessionChannel when it selects the channel to use:

  1. The service contract exposed by a service endpoint allows, but does not requires sessions (SessionMode = SessionMode.Allowed).

  2. The binding used by the service endpoint supports both sessionless and session-based communication (like the NetMessagingBinding).

This rule implies that when you define a WCF-Custom receive location to consume messages from a sessionful queue or subscription, when you enable it, you see an error like the following in the Application Log:

  • The adapter "WCF-Custom" raised an error message. Details "System.InvalidOperationException: It is not possible for an entity that requires sessions to create a non-sessionful message receiver.. TrackingId:9163a41f-d792-406d-acbe-eb93ab2defb8_1_1,TimeStamp:10/3/2011 12:39:02 PM ---> System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]: It is not possible for an entity that requires sessions to create a non-sessionful message receiver.. TrackingId:9163a41f-d792-406d-acbe-eb93ab2defb8_1_1,TimeStamp:10/3/2011 12:39:02 PM

To avoid this problem, when we control the definition of the service contract, simply mark its ServiceContract attribute as requiring sessions. Unfortunately, we cannot change the definition of the service contracts implemented by the BizTalkServiceInstance class. So, how can we force a WCF receive location to use an IInputSessionChannel instead of an IInputChannel when consuming messages from a sessionful queue or subscription? By using a WCF extension.

Create a binding extension element class called SessionChannelBehaviorExtensionElement to register the custom endpoint behavior in the machine.config. At runtime, this component creates an instance of the SessionChannelEndpointBehavior class. The AddBindingParameters method of the custom endpoint behavior replaces the original binding (in our case the NetMessagingBinding) with a CustomBinding that contains the same binding elements and injects an instance of the SessionChannelBindingElement at the top of the new binding. This way, at runtime, the custom binding executes first. Finally, customize the BindingElement.CanBuildChannelListener<TChannel> method to return false when the type of channel is IInputChannel. In a nutshell, this component forces the WCF runtime to skip the IInputChannel. Therefore, you can use it in a WCF-Custom receive location to force the WCF adapter to use the IInputSessionChannel. For convenience, the following code is for the three classes. Later in the whitepaper, we show you how to use this component when defining a WCF-Custom receive location that consumes messages from a sessionful queue or subscription.

public class SessionChannelBehaviorExtensionElement : BehaviorExtensionElement
{
    #region Private Constants
    //***************************
    // Constants
    //***************************

    private const string IsTrackingEnabledName = "isTrackingEnabled";
    private const string IsTrackingEnabledDescription = 
                                              "Gets or sets a value indicating whether tracking is enabled.";
    #endregion

    #region BehaviorExtensionElement Members
    //***************************
    // Protected Methods
    //***************************

    /// <summary>
    /// Creates a behavior extension based on the current configuration settings.
    /// </summary>
    /// <returns>The behavior extension.</returns>
    protected override object CreateBehavior()
    {
        return new SessionChannelEndpointBehavior(IsTrackingEnabled);
    }

    /// <summary>
    /// Gets the type of behavior.
    /// </summary>
    public override Type BehaviorType
    {
        get
        {
            return typeof(SessionChannelEndpointBehavior);
        }
    }      
    #endregion

    #region Public Properties
    /// <summary>
    /// Gets or sets a value indicating whether the message inspector is enabled.
    /// </summary>
    [ConfigurationProperty(IsTrackingEnabledName, DefaultValue = true, IsRequired = false)]
    [SettingsDescription(IsTrackingEnabledDescription)]
    public bool IsTrackingEnabled
    {
        get
        {
            return (bool)base[IsTrackingEnabledName];
        }
        set
        {
            base[IsTrackingEnabledName] = value;
        }
    }
    #endregion
}

public class SessionChannelBindingElement : BindingElement
{
    #region Private Constants
    //***************************
    // Constants
    //***************************

    private const string CanBuildIInputSessionChannel = 
                      "[SessionChannelBindingElement] CanBuildChannelListener returned true for InputSessionChannel.";
    private const string CannotBuildIInputChannel = 
                      "[SessionChannelBindingElement] CanBuildChannelListener returned false for IInputChannel.";
    #endregion

    #region Private Fields
    private readonly bool isTrackingEnabled;
    #endregion

    #region Public Constructor
    /// <summary>
    /// Initializes a new instance of the SessionChannelBindingElement class.
    /// </summary>
    /// <param name="isTrackingEnabled">A boolean value indicating whether tracking is enabled</param>
    public SessionChannelBindingElement(bool isTrackingEnabled)
    {
        this.isTrackingEnabled = isTrackingEnabled;
    }
    #endregion

    #region BindingElement Members
    /// <summary>
    /// returns a copy of the binding element object.
    /// </summary>
    /// <returns></returns>
    public override BindingElement Clone()
    {
        return new SessionChannelBindingElement(isTrackingEnabled);
    }

    /// <summary>
    /// Returns a typed object requested, if present, from the appropriate layer in the binding stack.
    /// </summary>
    /// <typeparam name="T">The typed object for which the method is querying.</typeparam>
    /// <param name="context">The BindingContext for the binding element.</param>
    /// <returns>The typed object T requested if it is present or nullif it is not present.</returns>
    public override T GetProperty<T>(BindingContext context)
    {
        return context.GetInnerProperty<T>();
    }

    /// <summary>
    /// Returns a value that indicates whether the binding element can build 
    /// a channel factory for a specific type of channel.
    /// </summary>
    /// <typeparam name="TChannel">The type of channel the channel factory produces.</typeparam>
    /// <param name="context">The BindingContext that provides context for the binding element.</param>
    /// <returns>true if the IChannelFactory<TChannel/>of type TChannel can be built by 
    ///          the binding element; otherwise, false.</returns>
    public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
    {
        return false;
    }

    /// <summary>
    /// Initializes a channel factory for producing channels of a specified type from the binding context.
    /// </summary>
    /// <typeparam name="TChannel">The type of channel the factory builds.</typeparam>
    /// <param name="context">The BindingContext that provides context for the binding element.</param>
    /// <returns>The IChannelFactory<TChannel/>of type TChannel initialized from the context. </returns>
    public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
    {
        throw new NotSupportedException();
    }

    /// <summary>
    /// Returns a value that indicates whether the binding element can build a 
    /// channel listener for a specific type of channel.
    /// </summary>
    /// <typeparam name="TChannel">The type of channel listener to build.</typeparam>
    /// <param name="context">The BindingContext for the binding element.</param>
    /// <returns>true if a channel listener of the specified type can be built; 
    ///          otherwise, false. The default is false. </returns>
    public override bool CanBuildChannelListener<TChannel>(BindingContext context)
    {
        var ok = typeof(TChannel) != typeof(IInputChannel) && context.CanBuildInnerChannelListener<TChannel>();
        Trace.WriteLineIf(isTrackingEnabled, ok ? CanBuildIInputSessionChannel : CannotBuildIInputChannel);
        return ok;
    }

    /// <summary>
    /// Initializes a channel listener for producing channels of a specified type from the binding context.
    /// </summary>
    /// <typeparam name="TChannel">The type of channel that the listener is built to accept.</typeparam>
    /// <param name="context">The BindingContext for the binding element.</param>
    /// <returns>The IChannelListener<TChannel/>of type IChannel initialized from the context.</returns>
    public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
    {
        return context.BuildInnerChannelListener<TChannel>();
    } 
    #endregion
}

You can enable the component tracking and use DebugView to monitor its runtime behavior.

Next Step

ServiceBusMessageInspector

See Also

Concepts

ListenUriEndpointBehavior
SessionChannelEndpointBehavior
ServiceBusMessageInspector
TokenProviderEndpointBehavior
Create the Endpoint Behaviors