|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
ContentsNote: The Workflow test case requires installation of the .NET Framework 3.5 (Orcas) Beta 2 version. Features
IntroductionThe Microsoft Windows Communication Foundation (WCF) represents a logical model of connectivity, where the connectivity between the service and its consumer is abstracted to the stack of the channels on top of the physical transport layer. Based on the binding stack elements and physical transport on both ends of the connectivity, we can define how the message can flow between the business layers in terms of the message exchange pattern (MEP). Note, the business layers do not need to know about each other, such as where they are hosted, located and connected, therefore we can say, the business layers are logical connected. The business connected layers represented by the client (proxy) and service can live in the same appDomain or on the different appDomains in the same process, out of the process or across the machine. Having the logical connectivity model in the application architecture enables it to encapsulate the business layers from the connectivity driven by the metadata. The WCF model introduces several common transports and bindings to create a sync and async connectivity with a variant of the message exchange patterns such as Input/Output, Request/Reply, Session, Duplex, etc. over the different physical transports such as TCP, HTTP, MSMQ, Pipe, etc. It is a great open connectivity paradigm with the ability to customize and extend all elements based on the business needs. Based on the physical transport, the business can live in the same tier or can be decoupled across an Enterprise Network using the principle of the Service Oriented Architecture (SOA) tenets. This article is focused on the WCF connectivity model within the same appDomain. The current version of the .netfx 3.0 and also upcoming version 3.5 (Orcas) uses the named pipe for in-process communication between the service and its consumer. The following picture shows a
The logical connectivity between the business layers is mapped to the WCF model described by their service contract. This description represents the metadata of the connectivity such as Address, Binding and Contract (ABC). Basically, the client and service using the same channel stack banded by Protocol, Encoder and Transport layers. The client channel is connected to the business layer by Proxy (using the transparent proxy pattern) and on the service side, the Dispatch will invoke a service operation method. More details about this great communication paradigm can be found in the MSDN documentation and .NET forum. I assume that you already read the documentation and have some working knowledge and experience with WCF programming. As I mentioned earlier, this article focuses on the in-process communication, therefore let's continue with this focus. When the client invokes the service operation on the transparent CLR proxy, the operation is mapped into the message object by the Protocol layer (depends on the message exchange pattern) and passing the message to the Encoder for its serialization. The Transport layer will take this binary stream or formatted text over the appDomain using a specific physical media, in our case it is a local Named Pipe running in the kernel mode, which can be reachable by any process on the same machine. The service's Encoder must decode the incoming stream/text into the message object and pass it to the Protocol channels. At the end, the Dispatch's Operation Invoker will invoke a method on the service instance. So far so good, the The point is described in the following picture; if the client and service are located in the same appDomain. Do we need to go out and back into the same appDomain to handle the internal domain communications via inter-process kernel resources? The following picture can answer for in-process connectivity:
As you can see, the physical layer of the connectivity represented by NULL Transport will take a clone message object and pass it directly to the service protocol layer. This logic is similar to the router, when the output channel is routing a message to the correct Input channel. Note, this NULL connectivity is fully transparent between the service and client and there is no restriction on the service to have multiple endpoints with a different ABC for binding. For example: The service with two endpoints, let's say the first one is Another example of the The following picture shows the connectivity within the appDomain for WCF and
The That's all for the introduction of the Null Transport, I hope you captured having this transport in-process communication model when the correct business layers in the tier can talk to each other using the same communication model as between the tiers. I assume you have knowledge of WCF and its extensibility; therefore I will focus more on the concept and implementation of the transport layer than on the hierarchy of the custom built channels. Ok, let's start with the concept and follow up its implementation. Concept and ImplementationThe concept of the The following picture shows the concept of the
When the The following code snippet shows these parts of the public NullInputChannelListener(NullTransportBindingElement element,
BindingContext context): base(context.Binding)
{
_element = element;
_context = context;
Uri listenUri = new Uri(context.ListenUriBaseAddress,
context.ListenUriRelativeAddress);
_localAddress = new EndpointAddress(listenUri);
_filter = new EndpointAddressMessageFilter(_localAddress);
}
protected override IInputChannel OnAcceptChannel(TimeSpan timeout)
{
if (base.State == CommunicationState.Opened)
{
if (_currentChannel != null)
{
// we are supporting only one channel in the listener
_waitChannel.WaitOne(int.MaxValue, true);
lock (ThisLock)
{
// re-open channel
if (_currentChannel.State == CommunicationState.Closed &&
base.State == CommunicationState.Opened)
{
_currentChannel =
new NullInputChannel(this, _localAddress);
_currentChannel.Closed +=
new EventHandler(OnCurrentChannelClosed);
}
}
}
else
{
lock (ThisLock)
{
// open channel at first time
_currentChannel = new NullInputChannel(this, _localAddress);
_currentChannel.Closed +=
new EventHandler(OnCurrentChannelClosed);
NullListeners.Current.Add(_filter, this);
}
}
}
return _currentChannel;
}
The Once the public void Send(Message message, TimeSpan timeout)
{
// double check
base.ThrowIfDisposedOrNotOpen();
// add remote address to the message header
base.RemoteAddress.ApplyTo(message);
// create buffered copy
MessageBuffer buffer = message.CreateBufferedCopy(int.MaxValue);
Message message2 = buffer.CreateMessage();
// dispatch to the listener
ThreadPool.QueueUserWorkItem
(new WaitCallback(this.DispatchToListener), message2);
}
protected virtual void DispatchToListener(object state)
{
try
{
NullListeners.Current.Dispatch<NullInputChannelListener>
(state as Message);
}
catch (Exception ex)
{
(state as Message).Close();
}
}
As you can see, the cloned message is sent to the dispatcher in an async manner without waiting for its response. This is the design for fire & forget message exchange pattern represented by the Input and Output channel. Building the custom WCF channel/transport is a straightforward task divided into the several layers with the interface plumbing patterns and some common behavior located in the The following picture shows a basic boilerplate for custom
The first part of this boilerplate is related to the plug-in of the custom transport to the binding collection. The The The following picture shows a class diagram for
The simple MEP, such as one directional sending a message to the input channel can be completed directly by enqueueing of the cloned message object into the input channel queue in the fire & forget manner - see the above code snippets. The plumbing layers are represented by To receive a response on the request described in the Request/Reply MEP, the plumbing layers must be driven by The
The The following code snippet shows the public IAsyncResult BeginRequest(Message message,
AsyncCallback callback, object state)
{
return this.BeginRequest
(message, base.DefaultSendTimeout, callback, state);
}
public IAsyncResult BeginRequest(Message message, TimeSpan timeout,
AsyncCallback callback, object state)
{
base.ThrowIfDisposedOrNotOpen();
base.RemoteAddress.ApplyTo(message);
NullAsyncRequestContext request =
new NullAsyncRequestContext(message,timeout,callback,state);
this.DispatchToListener(request);
base.PendingRequests.Add(request);
return request;
}
public Message EndRequest(IAsyncResult result)
{
return NullAsyncRequestContext.End(result);
}
public Message Request(Message message)
{
return this.Request(message, base.DefaultSendTimeout);
}
public virtual Message Request(Message message, TimeSpan timeout)
{
base.ThrowIfDisposedOrNotOpen();
base.RemoteAddress.ApplyTo(message);
NullAsyncRequestContext request =
new NullAsyncRequestContext(message, timeout);
base.PendingRequests.Add(request);
try
{
ThreadPool.QueueUserWorkItem
(new WaitCallback(this.DispatchToListener), request);
return request.WaitForReply();
}
finally
{
base.PendingRequests.Close(request);
}
}
protected virtual void DispatchToListener(object state)
{
try
{
NullListeners.Current.Dispatch<NullReplyChannelListener>
(state as NullAsyncRequestContext);
}
catch (Exception ex)
{
(state as NullAsyncRequestContext).Abort(ex);
}
}
In the case of public override void Reply(Message message, TimeSpan timeout)
{
lock (ThisLock)
{
ThrowIfInvalidReply();
if (message != null)
{
MessageBuffer buffer = message.CreateBufferedCopy(int.MaxValue);
_response = buffer.CreateMessage();
}
else
{
_response = null;
}
_replySent = true;
if (_waitForReply != null)
{
_waitForReply.Set();
}
}
if (_callback != null)
{
// call callback for reply is done
_callback(this);
}
}
The responsibility of the above method is to clone the message object and invoke an async callback to signal that the operation is done and the response is ready in the The following picture shows a request and reply message captured by
TestThe following picture shows the solutions for NULL Transport implementation including test projects for .netfx 3.0 and 3.5 versions. Note, the solution for .netfx 3.5 is only for the As you can see, the
For test purposes, each test console application has its own config file. The following picture shows an example of the configuration for
The message exchange patterns via the custom NULL Transport can be tested individually using the test projects such as Note, the config file contains a diagnostics configuration for tracing the messages across the WCF model. Please find the patch for this tracing in the config file and use the \Microsoft SDKs\Windows\V6.1\Bin\SvcTraceViewer.exe utility to display the trace log messages. Test_WorkflowI selected this test to describe the capability of the NULL Transport within the same AppDomain where the WCF model is used to connect the business objects located in the different layers. The console application represents a UI and host process for the business objects located in the workflows and service. Using the configuration file, the layers can be connected via the custom
The first The following picture shows a property of the first
The purpose of the following picture is shown in the message exchange with a first
Note, the [ServiceContract(SessionMode = SessionMode.Required)]
public interface ITest
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
void Ping(int value);
[OperationContract(IsInitiating = true)]
[TransactionFlow(TransactionFlowOption.Allowed)]
[CreateWorkflow]
string Ping2(int value);
}
Now, the client can create the using (OperationContextScope scope =
new OperationContextScope((IContextChannel)channel))
{
string instanceId = Guid.NewGuid().ToString();
Dictionary<XmlQualifiedName, string> dic =
new Dictionary<XmlQualifiedName, string>();
dic.Add(new XmlQualifiedName("InstanceId",
"http://schemas.microsoft.com/ws/2006/05/context"), instanceId);
ContextMessageProperty mp = new ContextMessageProperty(dic);
OperationContext.Current.OutgoingMessageProperties.Add
("ContextMessageProperty", mp);
string response = channel.Ping2(loop);
// ...
}
and the outgoing request message will look as it is shown in the following code snippet: <s:Envelope
xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/ITest/Ping2</a:Action>
<a:MessageID>urn:uuid:ce8e478c-ab6a-401d-958b-d1d3de2f972d</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<Context xmlns="http://schemas.microsoft.com/ws/2006/05/context">
<InstanceId>044a3c30-3558-47de-8647-2c0d9b4f9359</InstanceId>
</Context>
<a:To s:mustUnderstand="1">net.null://localhost/gaga</a:To>
</s:Header>
<s:Body>
<Ping2 xmlns="http://tempuri.org/">
<value>0</value>
</Ping2>
</s:Body>
</s:Envelope>
Remoting Vs. namedPipeTransport Vs. nullTransportThe fast standard transport from WCF model is via On my Dell multiprocessors (4 cores) machine, the null transport is faster than pipe in the score of 2.462 ms to 3.062 ms. Also, I built a similar test for remoting object and the result showed me that the null transport is approximately 8% faster than TCP remoting channel. Note, the test was done for a very small message body - see the ConclusionThis article described the in-process of the WCF Transport without using the Encoder layer. This efficient binding enables the usage of a common communication WCF paradigm for connectivity between the business layers located in the same appDomain. Using the config file; the connectivity can be easily changed administratively for crossing the boundary. The upcoming new .netfx 3.5 version related to the common model for connected systems with a workflow behind the service is a suitable candidate for
|
||||||||||||||||||||||