Contents
The SoapNull
transport is a lightweight connectivity layer between the SoapReceiver
and SoapSender
under the same appDomain. It is a simple bridge that allows using the same connectivity model to encapsulate a business layer from the physical deployment schema (tiers). The transport "wire" between the service and its consumer can be LONG (across the network), SHORT (across appDomains on the same machine) or NULL (within the appDomain) - based on the configuration schema.
This article describes an implementation, concept and usage of the NULL transport for WSE 3.0. I assume the reader is familiar with WSE 3 technology. More details about the WSE messaging stack can be found in my SoapMSMQ transport article.
- Request message pattern (OneWay)
- Request - ReplyTo message pattern (2x OneWay)
- RequestResponse message pattern
- Service and client must be hosted in the same appDomain
- No formatter needed
- SoapEnvelope cloning and dispatching directly to the input channel
The soap.null
transport does not require any special configuration other than adding it into the transports
collection.. The following code snippet shows a host CONFIG file where a <transports>
section has been populated by the soap.null
transport:
<configuration>
<configSections>
<section name="microsoft.web.services3"
type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration,
Microsoft.Web.Services3,Version=3.0.0.0,Culture=neutral,
PublicKeyToken=31bf3856ad364e35"/>
</configSections>
<microsoft.web.services3>
<messaging>
<transports>
<add scheme="soap.null"
type="RKiss.WseTransports.SoapNullTransport, SoapNULL,
Version=3.0.0.0, Culture=neutral,
PublicKeyToken=5c954de69e82238f" />
</transports>
</messaging>
</microsoft.web.services3>
</configuration>
Endpoint transport address
The soap.null
transport URI address format is rather simple and requires schema and host sections only. Note that the host must always be a localhost: soap.null://localhost.
The following examples demonstrate the usage of soap.null
addressing:
- Send a message to the endpoint with address
urn:myReceiver
via a null transport:
EndpointReference epr =
new EndpointReference(new Uri("urn:myReceiver"));
epr.Via = new Uri(@"soap.null://localhost"));
SoapSender Sender = new SoapSender(epr);
Sender.Send(message);
- Send message to the endpoint:
EndpointReference epr =
new EndpointReference(new Uri("soap.null://localhost/myReceiver"));
SoapSender Sender = new SoapSender(epr);
Sender.Send(message);
The concept of the soap.null
transport is based on the fast SoapEnvelope
message delivery to the appropriate SoapInputChannel
. The message travels within the same appDomain, therefore streaming its image via the physical channel is not necessary. Note that the abstract SoapInputChannel
class contains an event driven queue for storing SoapEnvelope
references.
The following picture shows a concept of the soap.null
transport and its position in the messaging infrastructure:
The Level 1 message workflow is started by a SoapSender
that calls the SoapOutputChannel.Send
method transparent to the SoapTransport
. Only the SoapNullOutputChannel
knows how to call its tightly coupled transport. When the call comes to the transport layer, the message is cloned and passed to the dispatcher that is responsible for delivering the message to the appropriate SoapInputChannel
..
The following code snippet shows an implementation of the major transport part as shown in the above picture:
[Editor comment: Line breaks used to avoid scrolling.]
public void Send(SoapEnvelope message,
EndpointReference endpoint)
{
try
{
SoapEnvelope message2 =
new SoapEnvelope(message.SoapVersion);
message2.Encoding = message.Encoding;
message2.GetType().GetField("_body", bindingFlags).
SetValue(message2, null);
message2.Envelope.ParentNode.ReplaceChild(
message2.ImportNode(message.Envelope, true),
message2.Envelope);
message2.PostLoad();
message2.Context.Addressing.SetRequestHeaders(this.Via,
this.Via);
if ((!base.DispatchMessage(message2) && (message2.Fault == null))
&& ((message2.Context.Addressing.RelatesTo == null) ||
(message2.Context.Addressing.RelatesTo.RelationshipType!=
WSAddressing.AttributeValues.Reply)))
{
SoapEnvelope envelope = base.GetFaultMessage(message2,
new AddressingFault(AddressingFault.DestinationUnreachableMessage,
AddressingFault.DestinationUnreachableCode));
if ((envelope.Context.Addressing.Destination != null) &&
!envelope.Context.Addressing.Destination.TransportAddress.
Equals(WSAddressing.AnonymousRole))
{
ISoapOutputChannel channel =
SoapTransport.StaticGetOutputChannel(
envelope.Context.Addressing.Destination);
channel.Send(envelope);
}
}
}
catch (Exception ex)
{
string strError = string.Format(
"[{0}].Send error = {1}, epr={2}",
UriScheme, ex.Message,
endpoint.Address.Value.AbsoluteUri);
throw new Exception(strError);
}
}
There are more ways of cloning the SoapEnvelope
, for instance, using InnerXml
/OuterXml
properties, ImportNode
etc. Based on the cloning mechanism and size of the message payload, the transport layer can significantly increase or decrease the performance of the message exchange.
SoapEnvelope
cloning requires to clone the Header, Body and the Context elements. The WSE 3 messaging namespace has a public
method performing the PostLoad
on the SoapEnvelope
CLR object. Using this "horse" method, we need to clear the empty Body XmlElement
created during the SoapEnvelope
constructor. There is no public
method for this action, therefore we are using a reflection method - see step #3 in the above code snippet.
Once we have a cleaned SoapEnvelope
, the envelope XmlElement
can be replaced by the import node of the original envelope element - step # 4. We finished message processing in the layer by calling the base transport DispatchMessage
method. Of course, there is also a possibility that the dispatcher cannot deliver the message to the endpoint. In this case, the soap.null
transport will create a FaultMessage
based on the message addressing headers.
Note for WSE 2.0 migration: The PostLoad
method in the WSE 2SP3 namespace is not public
; therefore it is necessary to use the following reflection to invoke it:
[Editor comment: Line breaks used to avoid scrolling.]
BindingFlags bindingFlags2 = BindingFlags.InvokeMethod |
BindingFlags.NonPublic | BindingFlags.Instance;
message2.GetType().GetMethod("PostLoad", bindingFlags2).
Invoke(message2, null);
The WSE messaging infrastructure is divided into three layers. I have described these layers in detail in my previous article. Please feel free to review the article here. The top layer - Level 2 is represented by the SoapClient
and SoapService
that are tightly coupled to the business layer using a CLR/Messaging pattern. This model enables encapsulation of the business layer from the physical layer of connectivity in a transparent manner. Based on the transport type, the workflow will work in the same way within the appDomain or between different machines. The only difference is in the message transport time and in the usage of resources such as memory (no deserialization/serialization processing). Using soap.null
transport, we can minimize the transport time and unify the connectivity model (application framework) across the tiers and layers - see for more details about the connectivity model in my article WSE ServiceBus.
The soap.null
transport enables encapsulating layers in a tier. The following examples show the usage of WSE messaging and the position of soap.null
transport in the business workflow:
Example 1 - Caching data
This example shows the service used for caching a data in a data slot loaded from an external source. The client has fast access to the cached data via a soap.null
transport. This solution is suitable for front-end tier:
Example 2 - Async workflow
This example shows encapsulating an asynchronous workflow into the sync and async activities. The sync activity (pre-processor) is hosted under the same appDomain as the SoapClient
. The pre-processor is connected to its consumer via the soap.null
transport. The message exchange pattern can be used in the Request-ReplyTo fashion, where the client will decide how the pre-processor will forward the message to the next service. The picture also shows a layering of the business workflow into three layers such as business client (root of the workflow), pre-processor and processor services. Note that another client based on the business needs can use the pre-processor service.
Example 3 - WS transfer
The WS-transfer spec is a good candidate for using the soap.null
transport. Please feel free to review WS-transfer implementation in my article. It is written for WCF (Indigo) communication model, but the design pattern is common for WSE messaging, as well. As the following picture shows, the Resource Factory can be connected to the client via soap.null
transport, which enables fast creation (factorizing) of a resource. Note that the factory (knowledge base of the resources) can be pre-loaded during appDomain start-up.
The model works in two steps. In the first step, the client is asking the Resource Factory for access to the resource representation (body). Subsequently, the client invokes an operation to obtain a resource representation. The factory is responsible for the operation endpoint included in its transport. In a special case all the three layers can be hosted under the same appDomain.
The SoapNULL
transport can be tested with a simple console program such as a client and server included in the download package. There are tests for all messaging patterns including background processing.
The following screen snap shows the client/server message exchange patterns. Please ensure that your machine has the following resources, before you run the test:
- .NET 2.0
- WSE 3.0 installed
SoapNULL
transport (MSI package)
The soap.null
transport represents a special custom transport with a "null wire" between the service and its consumer. Its features enable efficiently using the same across boundary connectivity model and encapsulating a business workflow into logical layers within the same appDomain.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.