Introduction
This short article builds on the previous article "Secure Web Service via Message oriented Middleware" [1] and extends the framework developed in the former to cover TCP/IP-based communications.
Since the solution will merely extend the original framework with support for a new communications protocol, all the original facilities (such as the ability to secure data using the Web Services Enhancements (WSE) 1.0) are capable of being utilized within the IP communications channel.
It is strongly recommended that the reader become familiar with the first article prior to reading this one as it explains the rationale for the design of the Microsoft "pluggable protocols" architecture.
Soap was designed as a transport neutral protocol to be used in combination with a variety of transport protocols such as HTTP(S), SMTP, FTP, etc. to deliver structured and typed information between two participants. A great deal of work has gone into making ASP.NET Web Services support Soap message generation in a way that promotes interoperability within heterogeneous environments (for example .NET consuming a BEA Weblogic Server hosted Web Service) but to date, the standard access to Web Services within the Microsoft arena has been almost exclusively geared to HTTP.
Article Goals
Simply, we aim to do the following in this article:
- Implement a new transport handler for TCP/IP within the pluggable protocols framework developed in the previous article. This would allow for the support of all the advanced features developed in the earlier code; namely Web Service Enhancements (WSE) security support, DIME support for binary attachments, etc.
- Introduce a mechanism for altering the Soap content of a message to a proprietary format using Soap Extensions and attributes. We’d typically want to do this when dealing with a legacy back end Service. The use of attributes to decorate Web Service (proxy) methods would mean we could simply remove the attributes at a later date to reinstate the standard Soap data format as the back-end the Service is updated for Soap support.
System Requirements
The built solution will require the following deployment environment:
- Windows 2000 SP2 or Windows XP Pro
- .NET Framework 1.0 (SP1 or above)
- WSE 1.0 SP1 runtime.
In order to rebuild the solution binaries, you will require the following additional tools:
- VS.NET (C#)
- WSE 1.0 SP1 – full installation recommended
- VS.NET WSE Settings Tool (optional)
Skills Requirement
From a skills perspective, the reader should have:
- familiarity with C# to an intermediate level. Exposure to writing and consuming a basic Web Service in .NET, since we will be examining the client side infrastructure (including the WSDL-generated proxy) in some detail.
- some appreciation of the work being done in the "second-generation" Web Services arena like WS-Security and WS-Attachments / DIME would be advantageous.
Implementing the TCP/IP Transport
From the original article, we know we can gain access to the Soap stream. Given the framework is in place, we merely need to hook in a new protocol handler, based on WebRequest
. This section highlights the key facets of the implementation for the IP handler.
TCP/IP-Specific Implementation
As with the MSMQ and MQ handlers, all the work is done in the overridden GetResponse()
method.
Accessing IP Functions
The .NET Framework contains a set of managed classes to control the use of IP communications both at a high level via abstracted types such as TcpClient
, and using a lower level API like Sockets. In order to make use of IP, the System.Net.Sockets
namespace must be included in the project:
IP Address / Port Format
In terms of the standard Uri specification discussed in the first article, an IP-based Service is identified by a scheme, an IP Address and Port Number as follows:
ip://127.0.0.1:42000
TCP/IP Processing
This section essentially describes the guts of the GetResponse()
method.
First, we need to access the serialized Soap message, which has been built in the m_RequestStream
data member variable. We read it into a byte array for use shortly. We also close that stream now that we have finished with it:
byte[] bytBody = new byte[m_RequestStream.Length];
m_RequestStream.Read(bytBody, 0, bytBody.Length);
m_RequestStream.InternalClose();
Next, a TcpClient
connection is created. Here, the timeout slot provided by the framework is also wired in and represents the receive timeout of the underlying socket:
TcpClient objTcpClient = new TcpClient();
objTcpClient.ReceiveTimeout = m_intTimeout;
objTcpClient.SendBufferSize = bytBody.Length;
We then attempt to connect to the address and port represented by the Uri:
objTcpClient.Connect(
strHostIPAddr,
intHostPort);
networkStream = objTcpClient.GetStream();
Once done, we can turn our attention to sending the message:
networkStream.Write(
bytBody,
0,
bytBody.Length);
After tidying the TcpClient
object, we are in a position to listen for a result. We actually proactively ask the network stream directly for some results:
byte[] bytResponseBody = new byte[objTcpClient.ReceiveBufferSize];
try
{
networkStream.Read(bytResponseBody, 0,
(int) objTcpClient.ReceiveBufferSize);
}
catch(Exception e)
{
if (e.InnerException != null &&
e.InnerException.GetType() == typeof(System.Net.Sockets.SocketException))
{
if (((SocketException)e.InnerException).ErrorCode == 10060)
throw new WebException(
"Timeout waiting on IP Socket resource.",
e.InnerException,
WebExceptionStatus.Timeout,
null);
else
throw new WebException(
"Failed to receive the message from the IP Socket.",
e.InnerException,
WebExceptionStatus.ReceiveFailure,
null);
}
else
if ((e is System.Net.WebException) == false)
{
throw new WebException(
"Failed to receive the message from the IP Socket.",
e,
WebExceptionStatus.ReceiveFailure,
null);
}
}
finally
{
objTcpClient.Close();
}
One of two things will happen as a result of waiting on a response from the remote host:
- An error occurs (error receiving the response message, timeout waiting for the message response from the Back End, etc. In this case, the error received from the underlying Socket receive is thrown direct to the caller. Referring to the general
WebException
status code table presented in the section named "Handling Errors" in the first article, here we list the specific mapping of major Socket failure scenarios to corresponding WebException
status codes:
WebException Status | Triggering event |
ConnectFailure | Failed to connect to the remote host given by IP Address and Port Number when calling objTcpClient.Connect() in IPWebRequest::GetResponse() |
NameResolutionFailure | N/a |
SendFailure | Failed to send the message to the remote server during call to networkStream.Write() in IPWebRequest::GetResponse() |
Timeout | Failed to receive a response from the remote server before the timeout period was reached in IPWebRequest::GetResponse() |
ReceiveFailure | Failed to receive the corresponding message response from the remote server for a non-timeout reason in IPWebRequest::GetResponse() |
ProtocolError | Any other failure within IPWebRequest / IPWebResponse . In this case, an IPWebResponse is created which details the exact failure. |
Note, in the latter case, an IPWebResponse
is created and passed as a parameter to the constructor of the WebException
class – this also wraps up the actual exception which will become the 'InnerException
' of the WebException
:
catch( … )
{
if ((e is System.Net.WebException) == false)
{
objIPWebResponse = new IPWebResponse(
(into)QueueTransportErrors.UnexpectedFailure,
e.Message,
e.StackTrace);
throw new WebException(
"IP Sockets Pluggable Protocol failure.",
e,
WebExceptionStatus.ProtocolError,
objIPWebResponse);
}
}
- A Soap message is received as a
stream
and the IPWebResponse
instance is created and primed directly with the returned stream
, in preparation for the Web Services client-side infrastructure to process it:
objIPWebResponse = new IPWebResponse();
objIPWebResponse.SetDownloadStream(msg.BodyStream);
…
return objIPWebResponse;
Using the New Proxy From a Client
The only other real change to any of the code in the original article is that the console test program needs to be extended to use IP:
localhost.Service1 objHelloWorldService = null;
string strRequestUrl = "ip://127.0.0.1:42000";
try
{
objHelloWorldService = new localhost.Service1();
objHelloWorldService.Url
= objHelloWorldService.FormatCustomUri(strRequestUrl);
objHelloWorldService.Timeout = vintTimeoutInSecs * 1000;
Console.WriteLine(objHelloWorldService.HelloWorld("Simon"));
}
catch(Exception e)
{
...
}
Extending the "Back-end Service"
The logic contained within the class BEService
is extended to include a listener that waits on traffic on a certain port, and responds to it appropriately. The class first reads the supplied app.config file for all application level settings (that is those within the <appsettings>
node):
NameValueCollection colSettings = ConfigurationSettings.AppSettings;
The port to listen on is found in the configuration file:
string strPortToListenOn = ConfigurationSettings.AppSettings["PortToListenOn"];
int intPortToListenOn = 42000;
if (strPortToListenOn != null &&
strPortToListenOn.Length > 0)
intPortToListenOn = Convert.ToInt32(strPortToListenOn);
The listening on the socket is handled differently to that for the queue-based resources. In this case, a separate thread is created, which handles the listening code as described below.
ProcessIPMessages() Implementation
This method is straightforward. First, we create a high level listener and gear it up to listening on the given IP port. Then we start it listening:
TcpListener tcpListener = new TcpListener(intPortToListenOn);
tcpListener.Start();
At this point, we can accept any inbound messages:
TcpClient objTcpClient = tcpListener.AcceptTcpClient();
If we get past the above call, we have (inbound) data so we should access it:
networkStream = objTcpClient.GetStream();
byte[] bytMessage = new byte[objTcpClient.ReceiveBufferSize];
networkStream.Read(bytMessage, 0, (int) objTcpClient.ReceiveBufferSize);
string strIn = new UTF8Encoding().GetString(bytMessage).Trim(arrTrimChars);
Once we have the strIn
variable populated, we can tap into the existing Back-end functions like BuildFakeResponseFromString()
, i.e., we then check to see if this is a known message and if so, build a response message for it. There is very little intelligence in the matching method or generation of the response message – it exists purely to serve the client response code.
strResp = BuildFakeResponseFromString(
"IP",
strIn,
env.Context);
If we identified a request and were able to generate a response for it, then send it back to the client via the network stream:
if (strResp.Length > 0)
{
byte[] sendBytes = Encoding.ASCII.GetBytes(strResp);
networkStream.Write(sendBytes, 0, sendBytes.Length);
}
Once done, we flush and close the network stream.
That’s really the totality of the enhancements required to make the basic end-end Web Service processing work via TCP/IP.
Changes to Use of Web Services Enhancements (WSE)
Put simply there are none. Or, at least, very few. No changes are required within the client code as the SoapWebRequest
class does the lions share of the work.
Updating the Back-end Service to Use WSE for Secure Communications
The only alteration involves the extension of the method ProcessIPMessages()
described above to include raw WSE pipeline processing as occurs for the MSMQ and MQ listening paths – this is highlighted in bold below.
Invoking the Inbound Message Pipeline for Requests
Assuming we got a message, i.e., no exception has been thrown, we open up the message and get the data content into a stream:
MemoryStream stMessage = new MemoryStream();
stMessage.Write(bytMessage, 0, bytMessage.Length);
SoapEnvelope env = InputStreamToEnvelope(stMessage);
stMessage.Close();
Pipeline objWSEPipe = new Pipeline();
objWSEPipe.ProcessInputMessage(env);
strIn = env.OuterXml;
strResp = BuildFakeResponseFromString(
"IP",
strIn,
env.Context);
The emboldened text shows the hydration of a SoapEnvelope
object from the inbound message stream. Once we have this envelope, we can create a WSE pipeline and run the default input filters against the envelope as described in the original article.
Once the pipeline process completes, our input buffer is then stuffed with the results of the pipeline run, ready to be processed by the rest of our application code.
Invoking the Outbound Message Pipeline for Responses
This is identical as for the MSMQ and MQ oriented messages, all the code being encapsulated in BuildFakeResponseFromString()
. As before, we are supporting 2-way secure messages.
At this point, we now have the ability to send WSE-secured data over an IP link.
Manipulating the Soap Data Content Over a Transport
As the TCP/IP transport was being implemented, a new requirement surfaced around the ability to manipulate the Soap stream into a format suitable for a legacy back-end system. This system expected data sent over an IP link to be in a certain proprietary format (hash-delimited) for the next few months, but the expectation was that the back-end would be upgraded to use Soap over TCP/IP sometime thereafter.
From our perspective then, we need to consider how we can create a short-term client-side approach supporting the generation of messages in our proprietary data format, whilst allowing the use of full-blown Soap messages at a later date and with minimal re-coding effort – ideally in fact just by declarative means – rather than distinct core code changes. This can be achieved with the use of SoapExtensions
[2] and the associated SoapExtensionAttribute
type.
SoapExtension
-derivations allow custom processing to be hooked into the Soap serialization / deserialization process. Previous implementations based on this kind of processing have added support such as custom encryption of a Soap payload, tracing of Soap messages etc.
Using Soap extensions at the client proxy, we can tap the Soap stream just after serialization by hooking into the SoapMessageStage.AfterSerialize
processing stage to re-present the Soap formatted stream as a custom stream.
Similarly, we can intercept the returned (proprietary we assume) response from the back-end and reformat back into well-known Soap for the standard de-serialization process to work.
Implementing the New SoapExtension
The extension we are writing is called Soap2ArgosExtension
. After overriding the Chain()
method to prepare for inserting our own stream in the context chain, we hook the method to access the Soap stream both before it is sent by the client (outbound) and then again before the response is de-serialized by the client (inbound), thus:
public override void ProcessMessage(
SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
TransformSoap2Argos(message);
break;
case SoapMessageStage.BeforeDeserialize:
TransformArgos2Soap(message);
break;
case SoapMessageStage.AfterDeserialize:
break;
default:
throw new ArgumentException(
"Invalid SoapMsgCaptureExtension Soap Message stage [" +
message.Stage + "]", "message");
}
}
The private
methods Transform*2*()
above merely delegate to a common routine called TransformUsingReader()
. Once this has run, the target stream is written with the newly derived stream. It should be appreciated that TransformUsingReader()
is a very specialist method which would need to be rewritten for each bespoke back-end implementation. The given example just implements a fictitious scenario where the stream expected by the back-end is a flat, delimited string
made up of the method call name and one or more parameter values (see below).
A portion of the method is shown below – this handles the outbound transform to turn Soap into our proprietary message format:
bool blnAPICallBuilt = false;
XmlTextReader objReader = new XmlTextReader(newStream);
StringBuilder objFlatAPICall = new StringBuilder(1024);
objFlatAPICall.Append("@@");
while (objReader.Read())
{
if (blnAPICallBuilt == false)
{
switch (objReader.Depth)
{
case 2:
objFlatAPICall.Append(objReader.Name);
objFlatAPICall.Append("#");
break;
case 4:
objFlatAPICall.Append(objReader.Value);
blnAPICallBuilt = true;
break;
default:
break;
}
}
}
byte[] bytNewFormat = new UTF8Encoding().GetBytes(objFlatAPICall.ToString());
newStream = new MemoryStream();
newStream.Write(bytNewFormat, 0, bytNewFormat.Length);
In the above case, we use the XmlTextReader
to read through the XML content and an instance of the StringBuilder
class to construct proprietary messages that look like:
@@<method name>#<parmvalue1>#<parmvalue2> ...
It is messages of this format we actually ship on the wire to the back-end.
Exposing the New SoapExtension to the Generated Proxy
Now we have a SoapExtension
to express the transformation of data, we need to find a way to wire this into the client proxy. This is achieved using declarative attributes – specifically in code by using a class derived from SoapExtensionAttribute
. The class definition indicates that the class derives from the base SoapExtensionAttribute
and can be targeted only at methods (i.e., the attribute makes no sense if declared at the individual parameter level, class level, etc.). The declaration looks like:
[AttributeUsage(AttributeTargets.Method)]
public class Soap2ArgosExtensionAttribute : SoapExtensionAttribute
{
...
Implementing this class allows an attribute to be declared against the relevant methods as shown:
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(...)]
public string DoesTrinketExistInMySize(int viSize) {
object[] results = this.Invoke("DoesTrinketExistInMySize",
new object[] {viSize});
return ((string)(results[0]));
}
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(...)]
[Soap2ArgosExtension]
public string DoesTrinketExistInMySize2(int viSize) {
object[] results = this.Invoke("DoesTrinketExistInMySize2",
new object[] {viSize});
return ((string)(results[0]));
}
The first proxy method declaration shows a standard invocation. The second decorates the method on the proxy class with an attribute, which hooks in our extra processing at run time.
The beauty of this approach is that, when the back-end becomes capable of supporting real Soap messages, the only change required is to remove the attribute from the method declaration above.
Updating the Back-end Service to Support the Proprietary Format
There is a minor modification to the back-end service, which involves the detection of the "@@
" leading characters indicating our specialist format. The logic to listen for IP runs this code on receipt of data:
if (strIn.IndexOf("@@") == 0)
{
strResp = BuildFakeArgosResponseFromString(
"IP",
strIn);
}
else
{
...
}
The code used to build the response is very basic (remember that we are not interested in the server-side implementation – as long as it delivers data back to the client side framework...):
if (vstrIn.IndexOf("DoesTrinketExistInMySize2") >= 0)
{
string strSize = "UNKNOWN";
if (vstrIn.IndexOf("#") >= 0)
strSize = vstrIn.Substring(vstrIn.IndexOf("#")+1, 1);
strResp = "@@DoesTrinketExistInMySize2#Leatherhead, " +
strSize +
" High St." +
vstrScheme +
"-FlatMsg";
}
else
{
TSLog(" *** (BuildFakeResponseFromBytes) Spotted inbound " + vstrScheme +
" [UNKNOWN in message " + vstrIn + "] message");
}
Finally, strResp
is returned to the caller via TCP/IP:
if (strResp.Length > 0)
{
byte[] sendBytes = Encoding.ASCII.GetBytes(strResp);
networkStream.Write(sendBytes, 0, sendBytes.Length);
}
Using the New Proxy From a Client
Now the hard work is out of the way, clients of the proxy can make use of it in the following way – in fact, close inspection of the updated client calling code will show there are no changes from the original article:
localhost.Service1 objHelloWorldService = null;
string strRequestUrl = "ip://100.100.100.217:42000";
try
{
objHelloWorldService = new localhost.Service1();
objHelloWorldService.Url
= objHelloWorldService.FormatCustomUri(strRequestUrl);
objHelloWorldService.Timeout = vintTimeoutInSecs * 1000;
Console.WriteLine(objHelloWorldService.HelloWorld("Simon"));
}
catch(Exception e)
{
...
}
It should be noted that this has only been tested with non-WSE’d messages over MSMQ, MQ, HTTP and TCP/IP transports.
Framework Packaging
The classes we have been describing in the article that make up the framework are packaged into an assembly called WSIPTransports.dll:
The leftmost types make up the support for extra facilities such as the ResponseUrl
. The other types make up our implementation of the socket-based pluggable protocols. The assembly is signed which makes it eligible for the GAC. It is named in order to be a companion to the original framework DLL, WSQTransports.dll.
The Sample Application
The demo application has been extended to cover the new IP transport.
Due to the nature of the work being focused far more on a technology than a real world application of it, the demo is basic in that it shows the interaction of 2 console applications – Client and Server – exchanging messages using the framework we’ve developed in the last article and this one.
The demo shows both passing "clear" and "WSE-secured" Soap messages based on various standard data type and DIME API combinations. While the demo is basic, it does illustrate the various concepts fairly well.
The demo shows the Back End service processing incoming Soap messages on TCP/IP sockets, on MSMQ and MQ queues and in both cases returning responses. The handling of non-trivial data types (real serialized custom objects) is also included in the demo. The screen shot actually shows the processing of complex types based on an arbitrary class called "Product
". It shows arrays of these "Products
" being serialized and passed between Server
and Client
. In the screenshot above, two clients can be seen running Soap calls over queues and via HTTP.
In order to assess the level of concurrency possible, start up one instance of the back end service, followed by as many instances of the test client as you would like to test. All instances of the client should complete their test runs without any exceptions being thrown.
Application Introduction
The following diagram introduces the discrete portions of the sample application and depicts their fit within the client server model:
The two new artifacts are:
- the framework code that implements the pluggable IP protocols lives within a single Assembly (WSIPTransports.dll) so that it can be used within multiple projects,
- the custom
SoapExtension
responsible for transforming data between Soap and some arbitrary proprietary format (ArgosAPIExtensions.dll)
The four changed artifacts are:
- Each of the configuration files (the ones shipped with WSAltRouteBEFake.exe and WSAltRoute.exe) are updated to support TCP/IP – as shown in the section below
- The framework is driven off an enhanced sample console application (WSAltRouteTest.exe), which tests various flavors of API over various transports including the new TCP/IP variant. This new version of the console driver application also exercises the Soap-proprietary conversion functionality.
- An updated back-end process includes the TCP/IP listening code as well as proprietary data format support (WSAltRouteBEFake.exe)
Application Setup
[The new steps for IP support are highlighted] The zip file contains several projects described in the section below. The key actions for deployment are:
- Ensure the prerequisites you require (especially MQ and WSE) are installed.
- Unzip the file preserving folder structure.
- Locate the
\Bin folder. - Run the WSQSetup.exe executable to create the relevant queues. Follow the on-screen prompts.
- Locate the \Client subfolder.
- Edit the WSAltRouteTest.exe.config f
i
le, review and make any changes. The list of entries is:
Entry | Description |
NoTrace | If true , does not dump the stack trace when an exception occurs |
EnableHTTPCalls | If true , make the API calls over HTTP transport |
EnableMSMQCalls | If true , make the API calls over MSMQ transport |
EnableMQCalls | If true , make the API calls over MQ transport |
EnableIPCalls | If true, make the API calls over IP transport |
HTTPUriNonWSE | Endpoint of the WSAltRoute Web Service |
HTTPUriWSE | Endpoint of the WSAltRouteWSE Web Service |
MSMQRequestQ | Endpoint of the MSMQ request queue |
MSMQResponseQ | Endpoint of the MSMQ response queue |
MQRequestQ | Endpoint of the MQ request queue |
MQResponseQ | Endpoint of the MQ response queue |
IPUri | Endpoint of the IP remote service |
LocationOfImages | Path for reading and writing images |
For example, if you do not want to run over HTTP, set EnableHTTPCalls
value to false
.
- Re-locate the \Bin folder
- Locate the \Server subfolder
- Edit the WSAltRouteBEFake.exe.config file, review and make any changes. The list of entries is:
Entry | Description |
NoTrace | If true , does not dump the stack trace when an exception occurs |
QueueToMonitor1 | Name of the 1st Queue to monitor for Soap messages |
QueueToMonitor2 | Name of the 2nd Queue to monitor for Soap messages |
QueueToMonitorX | Name of the Xth Queue to monitor for Soap messages |
PortToListenOn | Port for service to bind to when awaiting requests |
PollDelayTime | Receive delay in ms |
LocationOfImages | Path for reading and writing images |
For example, if you want to add a queue to monitor, add QueueToMonitor3
and set the associated value to the Uri name of the queue.
- If wanting to explore the HTTP transport:
- Locate the \WebServices folder
- Copy the two subfolders (WSAltRoute and WSAltRouteWSE) to your main web site folder (typically c:\inetpub\wwwroot).
- For the first folder, create a new IIS Virtual Directory called WSAltRoute off the default web site (port 80), pointing to the WSAltRoute target folder.
- For the second folder, create a new IIS Virtual Directory called WSAltRouteWSE off the default web site (port 80), pointing to the WSAltRouteWSE target folder.
- If wanting to explore the MSMQ transport:
- Check that the MSMQ request and response queues defined in the WSAltRouteTest.exe.config of the client application exist. Use the Computer Management feature of Windows 2000 or Windows XP to check this.
- If wanting to explore the MQ transport:
- Check that the MQ request and response queues defined in the WSAltRouteTest.exe.config of the client application exist. Use the Websphere MQ Explorer tool to check this.
- If wanting to explore the IP transport:
- Check you have no personal firewalls running that could block IP requests on certain address / port combinations.
- Re-locate the \Bin folder
- Locate the \Images subfolder
- Copy the two .gif files, CD.gif and Help.gif to the folder specified in both the WSAltRouteTest.exe.config of the client and WSAltRouteBEFake.exe.config of the server under the
LocationOfImages
key name. - To run the demo, start up the back end process WSAltRouteBEFake.exe first, observing the startup message appears ok. Then start up the client process WSAltRouteTest.exe. Activity should then be displayed in both consoles over the next few moments, as various Web Service API are exercised across the chosen transports. No exceptions should be seen.
- If exceptions are seen, only the message portions of the exception may be shown. If this appears to be the case, stack trace can be turned on by altering the value for
NoTrace
in the relevant (typically client) application configuration file to false
.
Application Build
The solution is made up of the following projects:
Name of project | New / Updated | Purpose |
WSIPTransports | New | The code supporting the pluggable transport framework and the TCP/IP transport implementations. |
ArgosAPIExtensions | New | SoapExtension indicating that the Soap should be transformed to a proprietary form. |
WSAltRouteTest | Updated | Updated console driver which makes use of IP configuration defined in updated configuration file. |
WSAltRouteBEFake | Updated | Updated back-end service supporting IP listening and proprietary data format detection and response. |
NOTE: Updated refers to an extension to the same project from the original article.
Case Study Review
Here’s a recap of both the fundamentals underpinning the original case study and the major aspects of this particular follow-on article:
- ASP.NET Web Services includes an infrastructure specifically designed to allow support of alternative transport protocols based on
System.Net.WebRequest
and System.Net.WebResponse
. - In order to support new protocols, a scheme registration mechanism is provided as part of the infrastructure. Once new "network schemes" are registered, a client may make use of them when specifying an endpoint.
- Like MSMQ and Websphere MQ, TCP/IP is easily capable of being wrapped with a transport protocol, and it is straightforward for the client to specify an end point based on a scheme. This has the effect of minimizing the code changes required for a client in order to make use of a new transport.
- The WSE can easily secure data over the TCP/IP transport – or indeed any other transport.
- As well as varying the transport over which data is sent, it is trivial to manipulate the actual data sent over any of the supported transports using a Soap extension and associated declarative attribute.
Further Work
Constructing this section is STILL like shooting fish in a barrel! A selection of things to consider include:
- There are places in the code where
string
s are concatenated rather than built using StringBuilder
. Because I use this 'technique', does not mean I endorse it! - A lot more work should be done to secure the various aspects of the solution. Two immediate ones springing to mind:
- The use of
StrongNameIdentityPermissionAttribute
to explicitly control who calls sensitive assemblies (only callers signed with a particular strong name key) - The encryption keys should be stored outside the code, either in a (well secured) file or in the secure area of the Registry
- The demo has been built around a very RPC-style of message protocol, where responses to requests are expected. Investigation of 1-way Soap messages would be interesting - this most closely maps to a Queue style send operation without the need for a corresponding receive and would be a useful addition to the library.
- This framework has been used to interoperate with a Java back end using the Axis Toolkit 1.1 RC1 under J2SE 1.4.1. Unfortunately, throwing Java into the mix here would probably have resulted in double the copy! Depending on reaction to this article, a follow-up concentrating more on this interoperability aspect is possible.
- It would be interesting to add a compression capability like the
SmartLib
compression library [9] into the DIME section for large attachments. - Other transports could be implemented within the framework – SMTP, FTP, SQL2000, etc. are good candidates. As an example, I've implemented TCP/IP recently within this framework which proved to be a trivial process.
- As a separate note, thanks to Scott for pointing out that IBM now ships the Kolban-written managed classes for Websphere MQ [4].
Related Links
History
- 31st May, 2003: Initial version
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.
I am the Technical Director for Myphones.com, which specialises in developing integrated VoIP products and services for the consumer and SME markets. Technology-wise, we are heavily into the 2nd generation WS stack atop .NET, and basically most things Microsoft, as well as C++ on Linux.