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'.