7
SOAP Extensions
The SOAP Request packet, when it
arrives at the web server, is in the form of an XML document. The relevant
information, such as function names and parameters, need to be extracted from
the XML document. This act of extraction is termed as de-serialization. At the
other end, the system when it is to send a value over, it has to create a SOAP
Response packet, which is an XML file containing the value. To accomplish this,
a process called Serialization is executed.
This Serialization and
De-serialization of SOAP packets is achieved by using SOAP Extensions. This
chapter explains how the packets can be parsed and messages can be intercepted.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
public class zzz {
[WebMethod]
[vijay()]
public void abc()
{
}
}
The webservice possesses a
simple attribute called 'vijay', which is applied to the function abc. On
loading the asmx file in the web browser, an error is generated, since the
compiler fails to recognize the word 'vijay'.
The error displayed in the browser
window is
'CS0246: The type or namespace name 'vijay' could not be
located (are you missing a using directive or an assembly reference?)
Thus, the compiler needs to be
educated about the fact that 'vijay' is a class.
While loading the asmx file, the
compiler scans the dll files in the bin subdirectory, within the
c:\inetpub\wwwroot folder, for the class named 'vijay'.
Since it is unable to locate it,
it expresses its inability to compile the code. To surmount this error, create
the bin folder, if it is not already present at the location specified, and
enter the following code in a file named aaa.cs:
aaa.cs
using System;
using System.IO;
using System.Web.Services.Protocols;
[AttributeUsage(AttributeTargets.Method)]
public class vijayAttribute : Attribute
{
}
Compile the file by giving the
command as
C:\inetpub\wwwroot\bin>csc /t:library aaa.cs
To enable creation of an
attribute called 'vijay', a class named 'vijayAttribute' or 'vijay' must be
present in the library. The word 'Attribute' is specified after the class name
'vijay', in order to appease the naming conventions. The attribute called
AttributeUsage that is specified over the class name, is optional.
The parameter Method restricts
the use of this attribute over a function; however, there is no curb on its use
over entities such as, a class or a property. To qualify as an attribute, the
most important criteria that the class 'vijay' must satisfy is that, it must be derived from the class Attribute.
The asmx file loads successfully
in the browser. The regular client program is used, to call the function abc.
The process of calling code remains unaltered. After you have created a proxy
from the wsdl file, use the batch file and then, run the program.
a.cs
class aaa {
public static void Main() {
zzz a= new zzz();
a.abc();
}
}
z.bat
del *.exe
del *.dll
del zzz.cs
wsdl aa.wsdl
csc /t:library zzz.cs
csc a.cs /r:zzz.dll
a
In the next program, 'vijay' is
derived from the class SoapExtensionAttribute, which in turn, is derived from
the class Attribute. It is mandatory to derive all custom extensions from the
above class.
aaa.cs
using System;
using System.IO;
using System.Web.Services.Protocols;
[AttributeUsage(AttributeTargets.Method)]
public class vijay : SoapExtensionAttribute
{
}
Compiler Error
>csc /t:library aaa.cs
aaa.cs(5,14): error CS0534: 'vijay' does not implement
inherited abstract member
'System.Web.Services.Protocols.SoapExtensionAttribute.ExtensionType.get'
aaa.cs(5,14): error CS0534: 'vijay' does not implement
inherited abstract member
'System.Web.Services.Protocols.SoapExtensionAttribute.Priority.get'
aaa.cs(5,14): error CS0534: 'vijay' does not implement
inherited abstract member
'System.Web.Services.Protocols.SoapExtensionAttribute.Priority.set'
The SoapExtensionAttribute class
contains two properties, Extensiontype and Priority, hence, if the two members
are not implemented, an error will be generated. We see four errors being
generated because the 'get' and the 'set' accessors of both the properties are
yet to be implemented. The next program implements the required members in the
class.
aaa.cs
using System;
using System.IO;
using System.Web.Services.Protocols;
[AttributeUsage(AttributeTargets.Method)]
public class vijay : SoapExtensionAttribute {
public override Type ExtensionType {
get
{
return typeof(mukhi);
}
}
public override int Priority {
get
{
return 0;
}
set
{
}
}
}
public class mukhi {
}
Other than returning the Type of
a class mukhi, there is nothing more added to these properties. Each time, the
framework will call the ExtensionType property to decipher the type of the
extension class.
Executing the client program
throws an exception with the errors shown below:
Output
C:\www>a
Unhandled Exception:
System.Web.Services.Protocols.SoapException:
System.Web.Services.Protocols.SoapException: Server was unable to process
request. ---> System.InvalidCastException: Exception of type System.InvalidCastException
was thrown.
at
System.Web.Services.Protocols.SoapReflectedExtension.GetInitializer(LogicalMethodInfo
methodInfo)
at
System.Web.Services.Protocols.SoapReflectedExtension.GetInitializers(LogicalMethodInfo
methodInfo, SoapReflectedExtension[] extensions)
at
System.Web.Services.Protocols.SoapServerType..ctor(Type type)
at
System.Web.Services.Protocols.SoapServerProtocol.Initialize()
at
System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type,
HttpContext context, HttpRequest request, HttpResponse response)
at
System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage
message, WebResponse response, Stream responseStream)
at
System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName,
Object[] parameters)
at zzz.abc()
at aaa.Main()
These errors have occurred
because the class 'mukhi' should have been derived from the class
SoapExtension. Hence, we do the same, as depicted below. Henceforth, we will
only display the class 'mukhi', since it contains the code that performs the
required task.
aaa.cs
public class mukhi : SoapExtension
{
}
During the compilation of the
program, after having derived the class 'mukhi' from the SoapExtenstion class,
a few more errors are generated during the creation of the library. This is for
the reason that the class SoapExtension comprises of four abstract functions,
which are yet to be implemented.
Compiler Error
aaa.cs(25,14): error CS0534: 'mukhi' does not implement
inherited abstract member
'System.Web.Services.Protocols.SoapExtension.GetInitializer(System.Web.Services.Protocols.LogicalMethodInfo,
System.Web.Services.Protocols.SoapExtensionAttribute)'
aaa.cs(25,14): error CS0534: 'mukhi' does not implement
inherited abstract member
'System.Web.Services.Protocols.SoapExtension.GetInitializer(System.Type)'
aaa.cs(25,14): error CS0534: 'mukhi' does not implement
inherited abstract member
'System.Web.Services.Protocols.SoapExtension.Initialize(object)'
aaa.cs(25,14): error CS0534: 'mukhi' does not implement
inherited abstract member
'System.Web.Services.Protocols.SoapExtension.
ProcessMessage(System.Web.Services.Protocols.SoapMessage)'
The next program incorporates
the required functions in the class 'mukhi'.
aaa.cs
public class mukhi : SoapExtension
{
public override void Initialize(object o)
{
}
public override object GetInitializer(LogicalMethodInfo m,
SoapExtensionAttribute a)
{
return null;
}
public override object GetInitializer(Type s)
{
return null;
}
public override void ProcessMessage(SoapMessage m)
{
}
}
When the four functions are
added, everything is hunky dory. At this stage, it is inconsequential to
attempt and gauge the wherewithal, i.e. how, when and why these functions are
called. We shall save the explanation for a rainy day.
aaa.cs
public class mukhi : SoapExtension {
public void abc(string s)
{
FileStream fs = new FileStream("c:\\a.txt",
FileMode.Append, FileAccess.Write);
StreamWriter w = new StreamWriter(fs);
w.WriteLine(s);
w.Flush();
w.Close();
}
public override void Initialize(object o)
{
abc("Initialize");
}
public override object GetInitializer(LogicalMethodInfo m,
SoapExtensionAttribute a)
{
abc("GetInitializer 1");
return null;
}
public override object GetInitializer(Type s)
{
abc("GetInitializer 2");
return null;
}
public override void ProcessMessage(SoapMessage m)
{
abc("ProcessMessage " + m.Stage);
}
}
a.txt
GetInitializer 1
Initialize
ProcessMessage BeforeDeserialize
ProcessMessage AfterDeserialize
ProcessMessage BeforeSerialize
ProcessMessage AfterSerialize
The class 'mukhi' now has a
function abc, which accepts a string as a parameter. This string is written to
the file c:\a.txt. The FileStream class constructor is used to specify the file
name and the mode of operation of the file. The two modes indicated here
facilitate addition of text to this file.
Once this has taken effect, an
object w, of type StreamWriter is created, by calling the constructor of the
object and supplying it with a FileStream object. The WriteLine function in
this class is then employed to write information onto the file. The Flush
function actually implements the writing, and the Close function closes the
writer.
Thus, each time the function abc
is called, in effect, we are appending some text to this file. As an outcome,
we are able to detect the functions that have been called, and their sequence
of execution, by merely examining the contents of the file a.txt.
To begin with, the function
GetInitializer, which accepts two parameters, gets called. It is followed by
the Initialize function. Then, it is the ProcessMessage function that gets
called four times; and on each occasion, it is called with a different value
for the Stage property. This property belongs to an enum named
SoapMessageStage.
The first value is
BeforeDeserialize, which occurs after the system has received the SOAP payload,
but before it has deserialized the request. This is followed by the
AfterDeserialize message, where the system deciphers or deserializes the
message, but has not yet handed over the SOAP request packet to the asmx file.
The last two messages deal with the reverse process, i.e. of sending the SOAP
response payload over to the client.
Using the ProcessMessage
function that gets called at a specific instant in time, we can smoothly
construe the SOAP payload, which has either been received, or is being sent
across.
aaa.cs
public class mukhi : SoapExtension
{
public void abc(string s)
{
FileStream fs = new FileStream("c:\\a.txt",
FileMode.Append, FileAccess.Write);
StreamWriter w = new StreamWriter(fs);
w.WriteLine(s);
w.Flush();
w.Close();
}
public override void Initialize(object o)
{
abc("Initialize");
}
public override object GetInitializer(LogicalMethodInfo m,
SoapExtensionAttribute a)
{
abc("GetInitializer 1");
return null;
}
public override object GetInitializer(Type s)
{
abc("GetInitializer 2");
return null;
}
Stream o;
Stream n;
public override void ProcessMessage(SoapMessage m)
{
if ( m.Stage == SoapMessageStage.AfterSerialize)
{
abc("AfterSerialize " + n.Position + " " +
n.Length );
n.Position = 0;
TextWriter w = new StreamWriter(o);
TextReader r = new StreamReader(n);
string s1 = r.ReadToEnd();
w.WriteLine(s1);
abc(s1);
w.Flush();
}
if ( m.Stage == SoapMessageStage.BeforeDeserialize)
{
abc("BeforeDeSerialize " + o.Position + " "
+ n.Position + " " + o.Length + " " + n.Length );
TextWriter w = new StreamWriter(n);
TextReader r = new StreamReader(o);
string s = r.ReadToEnd();
w.WriteLine(s);
abc(s);
abc("BeforeDeSerialize1 " + o.Position + " "
+ n.Position + " " + o.Length + " " + n.Length );
w.Flush();
abc("BeforeDeSerialize2 " + o.Position + " "
+ n.Position + " " + o.Length + " " + n.Length );
n.Position = 0;
}
}
public override Stream ChainStream( Stream s )
{
abc("ChainStream");
o = s;
n = new MemoryStream();
return n;
}
}
a.txt
GetInitializer 1
Initialize
ChainStream
BeforeDeSerialize 0 0 299 0
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<abc
xmlns="http://tempuri.org/" />
</soap:Body>
</soap:Envelope>
BeforeDeSerialize1 299 0 299 0
BeforeDeSerialize2 299 301 299 301
ChainStream
AfterSerialize 307 307
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<abcResponse
xmlns="http://tempuri.org/" />
</soap:Body>
</soap:Envelope>
The singular objective of the
above example is to display the SOAP packet, which is either sent or received
by the web server. Before we dive beneath the surface to explore the details of
its working, we need to offer a terse explanation on XML serialization and
de-serialization.
The relevant parameters, such as
function names and parameters in the SOAP payload, needs to be extracted from
the XML document. This act of extraction is termed as de-serialization.
Before the system commences with
the disintegration of the XML file, it calls ProcessMessage, with the value
BeforeDeSerialize. Upto this instant, only the original SOAP document is
available in its pristine form, untouched by human hands!
Once the system extracts the
requisite information from the XML file, it calls the ProcessMessage with the
value AfterDeserialize; thus, intimating that the task has been completed.
Now to send a value over, that
is to create an XML file containing the value , a process called Serialization
is executed. However, before this is carried out, the ProcessMessage function
is called, with the message code of BeforeSerialize. After the values have been
encoded into an XML document, the ProcessMessage gets called for the last time,
with a value of AfterSerialize.
Thus, at the stage of
BeforeDeserialize, the original XML payload is available, and at the stage of
AfterSerialize, the XML document has been created. We concern ourselves with
these events, since we intend to write the XML documents to the disk. The
salient point to be borne in mind is that, during the stage of Deserialization,
the XML document is to be read, whereas, at the time of Serialization, the
document is to be written to.
Now, let us revert back to the
program.
In the ProcessMessage function,
there is an 'if' statement, which ascertains whether the value of the Stage
property is the enum value BeforeDeserialize, which belongs to the enum
SoapMessageStage. If the 'if' statement passes muster, it signifies that the
incoming SOAP payload must be read.
Using the function abc, the
message is written to the file. However, on closer examination, we realize that
the function ChainStream gets called first. A Stream parameter is passed to
this function. This parameter is used extensively when the XML documents are to
be read or written to, coupled with the SOAP payload.
As the Stream object is very
crucial to get at the SOAP payload, its value is saved in a public variable
named 'o'. Thereafter, a variable 'n', which has been created earlier, is
initialized to a MemoryStream object. Then, it is duly returned. We shall
ponder upon it and scrutinize this return value a little later.
The Stream class is incapable of
reading and writing. Therefore, a StreamReader object named 'r' is created,
whose constructor is passed the Stream 'o'. We could have specified the data
type StreamReader for 'r', but we have specified the abstract class TextReader
instead, since the StreamReader class is eventually derived from the TextReader
class. This was done for yet another reason, i.e. all the illustrations
furnished by Microsoft have also resorted to the above technique.
Stream variable named 'n', which
has been declared earlier and initialized to MemoryStream in the ChainStream
function, is used to create a
StreamWriter object. The Position property of the Stream reveals the cursor
position or the byte number, where the data would be written. The Length
property contains the number of bytes in the Stream.
As we are at the initial stages
of execution, the position of file pointer in the stream that is to be read is
0, and its length is 299 bytes. It is a well-known fact that the size of a SOAP
request is 299 bytes. The stream that is used for writing, does not embody any
value. Hence, the position of the file pointer is at zero, and the value of the
length too is zero.
The ReadToEnd function reads all
the data upto the end of the stream, and returns it in a string format,
ensconced in the variable 's'. The WriteLine function in the TextWriter object
w, finally writes the string s to the MemoryStream n. The function abc writes
the string to the file a.txt.
The function ReadToEnd then
moves the file pointer to the last byte that it has read. Since 299 bytes have
been read, the file pointer is now positioned at byte 299. The Gordian Knot or
the dilemma here is that, the file pointer for the writer has remained static
at position 0. It is the function Flush that actually writes to disk, thereby,
updating the values contained in Position and Length.
Writing to a file on disk is an
expensive process. Therefore, it is economical to consolidate all the Writes.
In addition to the existing bytes, two extra bytes get added. Since, the file
pointer is stuck at byte 301 in the writer at this stage, it is binding to set
it to 0, as this is what the system anticipates.
Please note that the system will
interpret the data written to the stream 'n' as the incoming data. Effectively,
we have just copied data from one stream to another. However, very soon, we
shall amend the data passed to the asmx file.
Now that we have read the
incoming stream, and written it out, let us examine the message AfterSerialize.
At this point in time, the situation is as follows:
• There is an XML document response,
which is ready to bolt the stable doors and reach the client.
• The MemoryStream n contains 307
bytes of data within it.
• The file pointer is positioned at
the end of the file.
Firstly, the file pointer is
repositioned at the absolute start position. Thereafter, a reader is created
using the stream 'n', unlike on the last occasion, where it was used for
creating a writer. As the ChainStream function always gets called, the Stream
'o' is used for writing, and not for reading. Any data that is written to this
stream, shall be sent to the client.
To summarise, the system first
calls ChainStream, obtains a return stream object, writes 307 bytes in it, and
then, calls AfterSerialize. Subsequent to this, the bytes are written to the
stream 'o'. Previously, the stream 'o' was used to read the incoming SOAP
request. But now, we use it to write the SOAP response. In the bargain, both,
the response and request packets are saved to the file on disk.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
public class zzz {
[WebMethod]
[vijay()]
public int abc(int i, int j)
{
return i + j;
}
}
a.cs
class aaa {
public static void Main()
{
zzz a= new zzz();
int k = a.abc(10,20);
System.Console.WriteLine(k);
}
}
aaa.cs
if ( m.Stage == SoapMessageStage.BeforeDeserialize)
{
abc("BeforeDeSerialize " + o.Position + " "
+ n.Position + " " + o.Length + " " + n.Length );
TextWriter w = new StreamWriter(n);
TextReader r = new StreamReader(o);
string s = r.ReadToEnd();
int j = 274;
abc(s[j].ToString());
abc(s[j+1].ToString());
abc(s[j+2].ToString());
abc(s[j+3].ToString());
w.WriteLine(s);
abc(s);
abc("BeforeDeSerialize1 " + o.Position + " "
+ n.Position + " " + o.Length + " " + n.Length );
w.Flush();
n.Position = 274;
w.Write('4');
w.Flush();
n.Position = 0;
}
Output
60
SOAP Request
<abc xmlns="http://tempuri.org/">
<i>10</i>
<j>20</j>
</abc>
a.txt
Initialize
ChainStream
BeforeDeSerialize 0 0 343 0
1
0
<
/
<?xml version="1.0"
encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<abc
xmlns="http://tempuri.org/">
<i>10</i>
<j>20</j>
</abc>
</soap:Body>
</soap:Envelope>
BeforeDeSerialize1 343 0 343 0
ChainStream
AfterSerialize 358 358
<?xml version="1.0"
encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<abcResponse
xmlns="http://tempuri.org/">
<abcResult>60</abcResult>
</abcResponse>
</soap:Body>
</soap:Envelope>
Let us attempt at appreciating
the SOAP extensions from a fresh perspective. The asmx file has the familiar
function abc, which now accepts two parameters and returns an int. Since we
want to trace the bytes that are traversing to and fro, we shall follow the
same process that has been executed innumerable times before.
In other words, before running
the batch file z.bat, we shall create a wsdl file and modify the port number of
localhost to 8080. The values assigned to the function abc are 10 and 20. Thus,
the expected output is the value 30. However, it is quite astonishing to find
the value 60 being displayed. There is definitely something wrong somewhere!
We have only displayed the 'if'
statement, which gets activated when the BeforeDeserialize event gets
triggered. At this instant, the string 's' contains the entire SOAP payload. On
examining bytes 274 to 277 of the SOAP payload, we realize that byte 274 holds
the digit 1, of the number 10. This is the content of the variable i. The
string is written and flushed in the MemoryStream, as before.
Then, the file pointer is
positioned at byte 274, and using an overload of the Write function, the value
of 1 is replaced by the ASCII character 4. Thus, the value changes from 10 to
40, resulting in a return value of 60, instead of the expected 30. All
characters written to the stream 'n' get amalgamated into the incoming SOAP
packet. Therefore, the original stream 's' is not read at all, since the stream
that returned, is the one that is sent to the asmx file.
In a situation where the client
compresses a file and sends it to the server, the server must have a program
running, which will receive the bytes and decompress them, before placing the
new contents in the stream 'n'.
The SOAP request that is
received by the trace, contains the numbers 10 and 20. This is because the
trace receives the output before it is sent over to the server. Thus, the trace
and the client will notice the same SOAP packet. The change is made to the
stream 'n', and not to 's'. The contents that are visible in the a.txt file,
are a spitting image or a replica of what the client sends to the trace
program. These contents are eventually forwarded to the server.
aaa.cs
if ( m.Stage == SoapMessageStage.AfterSerialize)
{
n.Position = 0;
TextWriter w = new StreamWriter(o);
StreamReader r = new StreamReader(n);
string s1 = r.ReadToEnd();
abc(s1);
string s2 = s1.Replace("60","90");
w.WriteLine(s2);
w.Flush();
}
Output
90
a.txt
<abcResponse xmlns="http://tempuri.org/">
<abcResult>60</abcResult>
</abcResponse>
SOAP response
<abcResponse xmlns="http://tempuri.org/">
<abcResult>90</abcResult>
</abcResponse>
In the aaa.cs file, we have only
displayed the AfterSerialize message. At this stage, we are aware that the
string read from the Stream 'n' contains the SOAP output. Now, instead of
writing it directly to the Stream 'o' that is received from ChainStream, we
first write the characters to the file a.txt, in order to verify the result to
be 60.
Then, by using the Replace
function, all occurrences of the string 60 in the string s1, are replaced by
90. The return value contains the modified string. This new string s2 is then
sent across to the client. Since the packet reaches the trace program first,
the SOAP response displays the result as 90. Also, since the value being sent
across is 90, the output of the client is displayed as 90.
We have just demonstrated how we
can exercise absolute control over both, the SOAP payload that is being sent to
the asmx file, and the packets being sent from the server to the client.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
public class zzz
{
[WebMethod]
[vijay()]
public void abc()
{
}
[WebMethod]
[vijay()]
public void pqr()
{
}
}
a.cs
class aaa
{
public static void Main()
{
zzz a= new zzz();
a.abc();
a.abc();
a.pqr();
}
}
aaa.cs
using System;
using System.IO;
using System.Web.Services.Protocols;
[AttributeUsage(AttributeTargets.Method)]
public class vijay : SoapExtensionAttribute
{
public override
Type ExtensionType
{
get
{
return
typeof(mukhi);
}
}
public override
int Priority
{
get {
return 0;
}
set { }
}
}
public class mukhi : SoapExtension
{
public void abc(string s)
{
FileStream fs = new FileStream("c:\\a.txt",
FileMode.Append, FileAccess.Write);
StreamWriter w = new StreamWriter(fs);
w.WriteLine(s);
w.Flush();
w.Close();
}
public mukhi()
{
abc("Constructor");
}
public override void Initialize(object o)
{
abc("Initialize " + o);
}
public override object GetInitializer(LogicalMethodInfo m,
SoapExtensionAttribute a)
{
abc("GetInitializer 1 " + m);
if ( m.Name == "abc")
return "sonal";
else
return "mukhi";
}
public override object GetInitializer(Type s)
{
abc("GetInitializer 2");
return null;
}
public override void ProcessMessage(SoapMessage m)
{
abc("ProcessMessage " + m.Stage);
}
}
a.txt
Constructor
GetInitializer 1 Void abc()
Constructor
GetInitializer 1 Void pqr()
Constructor
Initialize sonal
ProcessMessage BeforeDeserialize
ProcessMessage AfterDeserialize
ProcessMessage BeforeSerialize
ProcessMessage AfterSerialize
Constructor
Initialize sonal
ProcessMessage BeforeDeserialize
ProcessMessage AfterDeserialize
ProcessMessage BeforeSerialize
ProcessMessage AfterSerialize
Constructor
Initialize mukhi
ProcessMessage BeforeDeserialize
ProcessMessage AfterDeserialize
ProcessMessage BeforeSerialize
ProcessMessage AfterSerialize
The asmx file has two functions
named abc and pqr, containing the attribute 'vijay'. The client program calls
the function abc twice, and the function pqr only once. The aaa.dll as usual,
contains the function abc. Additionally, it contains the GetInitializer
function, which displays the string representation of the first parameter, i.e.
LogicalMethodInfo.
The SOAP Extension should at no
cost, trammel the speed of the system. To ensure this, the framework first
ascertains the number of functions contained in the asmx file, and then, it
creates an equal number of copies of the class mukhi. Since the asmx file has
two functions, the constructor is called twice.
Then, it calls the function
GetInitializer with two parameters. The first parameter is a LogicalMethodInfo
that represents a function. The String representation displays the prototype of
the string.
This class has a function called
'name', which provides the name of the function having the attribute of
'vijay'. Thus, on the first occasion, the GetInitializer gets called because of
the function abc, while on the second occasion, it is called due to the
function pqr.
The task of GetInitializer is to
return a value, which will be passed to the Initialize function. The Name
property ascertains whether the name of the function is abc or not. If it is
so, the value returned is 'sonal', or else, it is 'mukhi'. This entire process
is executed only once. This value that is returned is cached internally.
Each time a function gets
called, its constructor gets called once. Thereafter, the function 'Initialize'
gets called, with the value that is returned by function GetInitializer. Thus,
calling the function abc twice, results in a call to the Initialize function,
with the parameter value of 'sonal'. This is how depending upon the function
name, parameters can be passed to functions.
To summarize, when a function is
called manifold times, GetInitializer gets called only once; whereas, the
Constructor and the Initialize function get called every time that the function
is executed. The function ProcessMessage gets called four times per function
call.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
public class zzz
{
[WebMethod]
[vijay(Filename="c:\\z.txt")]
public void abc()
{
}
[WebMethod]
[vijay(Filename="c:\\z1.txt")]
public void pqr()
{
}
[WebMethod]
[vijay()]
public void xyz()
{
}
}
a.cs
class aaa
{
public static void Main()
{
zzz a= new zzz();
a.abc();
a.abc();
a.pqr();
a.xyz();
}
}
aaa.cs
using System;
using System.IO;
using System.Web.Services.Protocols;
[AttributeUsage(AttributeTargets.Method)]
public class vijay : SoapExtensionAttribute
{
private string fi = "c:\\z2.txt";
public override Type ExtensionType
{
get
{
return typeof(mukhi);
}
}
public override int Priority
{
get
{
return 0;
}
set
{
}
}
public string Filename
{
get
{
return fi;
}
set
{
fi= value;
}
}
}
public class mukhi : SoapExtension
{
string f;
public override object GetInitializer(LogicalMethodInfo m,
SoapExtensionAttribute a)
{
vijay b = (vijay)a;
return b.Filename;
}
public override object GetInitializer(Type s)
{
return typeof(vijay);
}
public override void Initialize(object o)
{
f = (string) o;
}
public void abc(string s)
{
FileStream fs = new FileStream(f, FileMode.Append,
FileAccess.Write);
StreamWriter w = new StreamWriter(fs);
w.WriteLine(s);
w.Flush();
w.Close();
}
public override void ProcessMessage(SoapMessage m)
{
if ( m.Stage == SoapMessageStage.AfterSerialize)
abc("ProcessMessage " + m.Stage);
}
}
z.txt
ProcessMessage AfterSerialize
ProcessMessage AfterSerialize
Z1.txt
ProcessMessage AfterSerialize
Z2.txt
ProcessMessage AfterSerialize
This program demonstrates the
phenomenon of passing data dynamically onto the SOAP extension.
For the first time, we have
passed a parameter named FileName, to the attribute of 'vijay'. The first two
functions, abc and pqr, use a different filename, while the last function, xyz
uses none. This parameter is primarily used to determine the file name that is
to be used while writing the text. A total of three files are created in the
root.
The client program a.cs merely
calls the three functions, viz. abc, pqr and xyz. In aaa.cs, the class 'vijay' is of prime significance. Since
every parameter to an attribute must have a corresponding public property by
the same name, there exists a property called 'Filename'. The task of this
read-write property is to return the value held in the variable 'fi' to the
'get' accessor. It is also responsible for setting the same variable to the
reserved parameter value, in the 'set' accessor. The string 'fi' is used only
to store the state or value of the property Filename.
Technically speaking, the state
of the property Priority should also be stored in a variable, but since we are
not interested in its value, we do not do the same. The GetInitializer function
gets called first. Its second parameter is a handle to the attribute class
'vijay'. The parameter is cast to type 'vijay', and then, the Filename property
value is returned.
The function abc sets the value
of 'fi' to "C:\z.txt", and the function pqr sets the value of 'fi' to
"c:\z1.txt". Since the function xyz does not call this property at
all, the default value of 'fi', i.e. "c:\z2.txt", remains unchanged.
When the Initialize function gets called, the above values are sent across as
parameter values. The string 'f' is initialized to the parameter 'o' that is
passed.
Thus, whenever the function abc
gets called in the function ProcessMessage, it assigins a value to the variable
'f' depending upon the function being called.
Thus, the function abc now
stores its data in three different files, namely, z.txt, z1.txt and z2.txt.
This program demonstrates how an attribute can accept parameters, and
thereafter, pass them on to the class 'mukhi'.
Now, we intend to capture the
Headers in our trace program. So, we implement the program from the Soap
Headers chapter, which sends across a header. The SoapMessage parameter is
going to be the main focus of the residual programs of this chapter.
a.cs
using System;
using System.Web.Services.Protocols;
using System.Web.Services;
public class aaa :
System.Web.Services.Protocols.SoapHttpClientProtocol
{
public yyy b;
public static void Main()
{
aaa a= new aaa();
a.b = new yyy();
a.b.name="vijay" ;
a.b.pass="sonal" ;
a.abc();
}
[System.Web.Services.Protocols.SoapHeaderAttribute("b")]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/abc")]
public void abc()
{
object[] results = this.Invoke("abc", new object[0]);
}
public aaa()
{
this.Url = "http://localhost:8080/a.asmx";
}
}
public class yyy : SoapHeader
{
public string name;
public string pass;
}
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
public class zzz
{
[WebMethod]
[vijay()]
public void abc()
{
}
}
aaa.cs
public class mukhi : SoapExtension
{
public override object GetInitializer(LogicalMethodInfo m,
SoapExtensionAttribute a)
{
return null;
}
public override object GetInitializer(Type s)
{
return typeof(vijay);
}
public override void Initialize(object o)
{
}
public void abc(string s)
{
FileStream fs = new FileStream("c:\\a.txt",
FileMode.Append, FileAccess.Write);
StreamWriter w = new StreamWriter(fs);
w.WriteLine(s);
w.Flush();
w.Close();
}
public override void ProcessMessage(SoapMessage m)
{
if ( m.Stage == SoapMessageStage.AfterDeserialize)
{
SoapHeaderCollection s;
s = m.Headers;
abc(s.Count.ToString());
SoapUnknownHeader h = (SoapUnknownHeader)s[0];
System.Xml.XmlElement e = h.Element;
abc(e.OuterXml);
}
}
}
a.txt
1
<yyy xmlns="http://tempuri.org/">
<name>vijay</name>
<pass>sonal</pass>
</yyy>
We commence with a simple asmx
file, where the function abc returns a void. The header program is modified to
return a void instead of a string. In any case, there is no rationale behind
complicating the code unnecessarily.
In order to identify the headers
that have been received, we are required to wait for the AfterDeserialize event
to occur, since the incoming XML has still not been parsed in the
BeforeDeserialize event.
In the AfterDeserialize event,
it is the SoapHeaderCollection object named 's' that is created first. It is
then initialized to the value contained in the Headers property of the
SoapMessage Object, m. The class SoapHeaderCollection, being a collection
class, has a property called Count, which identifies the number of SoapHeader
objects contained in the collection class. Presently, since only a single
header exists, our output displays a value of one.
With the help of the indexer,
s[0] the single SoapHeader object is retrieved and stored in h, a variable of
type SoapUnknownHeader.
Since h is not of type
SoapHeader, the use of a cast operator is imperative.
Thereafter, the Element property
in the object is employed to initialize an object of type XmlElement. The
entire node is then displayed, using the OuterXml function.
This is how we can display the
headers that are sent across. BeforeSerialize is the other event where we deal
with headers. This property is read-only. Therefore, we cannot amend the
headers using this property.
Next, we present the smallest
asmx file, as well as, the smallest client. We don't come across any of the
complications that were present in other programs, such as the trace program,
etc.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
public class zzz {
[WebMethod]
[vijay()]
public int abc(int i, int j)
{
return i + j;
}
}
a.cs
class aaa {
public static void Main() {
zzz a= new zzz();
int k = a.abc(10,20);
System.Console.WriteLine(k);
}
}
aaa.cs
public override void ProcessMessage(SoapMessage m)
{
if ( m.Stage == SoapMessageStage.AfterDeserialize)
{
abc(m.Action);
abc(m.MethodInfo.ToString());
abc(m.OneWay.ToString());
abc(m.Url);
}
}
a.txt
http://tempuri.org/abc
Int32 abc(Int32, Int32)
False
http://localhost/a.asmx
This program outputs four properties
of the SoapMessage class, which are written to the file a.txt. These properties
are as follows:
• The first property is the familiar Action
property, which displays the value of the SOAPAction HTTP header. This is the
only HTTP header that SOAP introduces.
• The second property is the MethodInfo
property, which provides all the details about a function. It helps us to
structure our code to act in a particular way, depending upon the call to a
specific function.
• The third property is the OneWay property,
which returns the value of the OneWay property of the
SoapDocumentMethodAttribute attribute. Here, the value is 'false'.
• The fourth property is the URL property,
which reveals the URL of the remote server, where the execution takes place.
aaa.cs
public override void ProcessMessage(SoapMessage m)
{
if ( m.Stage == SoapMessageStage.AfterDeserialize)
{
int i;
i = (int)m.GetInParameterValue(0);
abc(i.ToString());
i = (int)m.GetInParameterValue(1);
abc(i.ToString());
}
}
Output
30
a.txt
10
20
The SOAP payload triggers off
the BeforeSerialize event, when it first arrives at the web server. Thereafter,
the XML file is Deserialized, wherein, all the parameters are extracted from
the XML document.
Once this is over, the
AfterDeserialize event gets triggered. Here, we use the function
GetInParameterValue with the parameter 'number', and obtain the values of the
parameters that are passed. Thus, the index 0 returns the value of i, and the
index 1 returns the value of j. This is a useful way of ascertaining the values
of parameters directly, instead of parsing the XML stream manually, since the
manual process is extremely time consuming.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
public class zzz {
[WebMethod]
[vijay()]
public int abc(ref int i,int j , ref int k)
{
return 234;
}
}
a.cs
class aaa {
public static void Main()
{
zzz a= new zzz();
int p = 30;
int q = 20;
a.abc(ref p,20, ref q);
}
}
aaa.cs
public override void ProcessMessage(SoapMessage m)
{
if ( m.Stage == SoapMessageStage.BeforeSerialize)
{
int i;
i = (int)m.GetOutParameterValue(0);
abc(i.ToString());
i = (int)m.GetOutParameterValue(1);
abc(i.ToString());
}
}
a.txt
30
20
The asmx file has a function
abc, which starts with a 'ref' parameter, followed by an 'int' parameter, and
finally, ends with a 'ref' parameter. The client program follows along the same
lines. Since the BeforeSerialize event gets fired, it provides ample proof that
the 'out' parameters have been assigned values, and that, the function abc has
been called.
Thus, the function
GetOutParameterValue in the event BeforeSerialize, provides the value stored in
the 'out' parameters, only when it is assigned a number. Thus, the index 0
represents the variable 'i', and the index 1 represents the second 'out'
parameter 'q'. This is because, it is the second 'out' parameter, though
overall, it is the third parameter in the parameter list,
aaa.cs
public override void ProcessMessage(SoapMessage m)
{
if ( m.Stage == SoapMessageStage.BeforeSerialize)
{
int i;
i = (int)m.GetReturnValue();
abc(i.ToString());
}
}
a.txt
234
The BeforeSerialize event can also supply the return value. The function abc returns 234, which is the value returned from the function GetReturnValue. At this point, we cannot amend any of these values, since these values are 'read-only'.