6
SOAP Headers
All the previous chapters had
riveted their attention on two salient components of the SOAP packet, i.e. the
Envelope and the Body. In this chapter, we shall pan our focus onto the third
major component of the SOAP payload, known as the Header. The SOAP Header is
optional, but when present, it should be placed immediately after the Envelope,
but before the Body.
In the case of headers, there
exists abundant flexibility to settle on what they should be used for. These
headers are generally employed in order to send across information that should
not to be sent as part of the SOAP body, since it does not form part of the
data. Thus, specific pieces of information, such as userid, password,
authentication, encryption, security information, etc. are incorporated in the
headers.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
public class zzz
{
public yyy z;
[WebMethod]
[SoapHeader("z")]
public int abc(int i, int j)
{
return i + j;
}
}
public class yyy : SoapHeader
{
public string name;
public string pass;
}
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" ;
int i = a.abc(10,20);
Console.WriteLine(i);
}
[System.Web.Services.Protocols.SoapHeaderAttribute("b")]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/abc")]
public int abc(int i, int j)
{
object[] results = this.Invoke("abc", new object[]
{i,j});
return ((int)(results[0]));
}
public aaa()
{
this.Url = "http://localhost:8080/a.asmx";
}
}
public class yyy : SoapHeader
{
public string name;
public string pass;
}
z.bat
del *.exe
del *.dll
del zzz.cs
wsdl aa.wsdl
csc a.cs
a
Output
30
SOAP request
<?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:Header>
<yyy xmlns="http://tempuri.org/">
<name>vijay</name>
<pass>sonal</pass>
</yyy>
</soap:Header>
<soap:Body>
<abc xmlns="http://tempuri.org/">
<i>10</i>
<j>20</j>
</abc>
</soap:Body>
</soap:Envelope>
SOAP response
<?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>30</abcResult>
</abcResponse>
</soap:Body>
</soap:Envelope>
Here, we have cogitated on the
client program named a.cs, instead of the asmx file, since it is the client
that dispatches the headers that are received by the asmx file. As in the past,
the client program is written manually, and is followed by the wsdl program, in
order to generate the proxy.
After the creation of a new
instance of the aaa object, an object b derived from SoapHeader is created,
since the intent is to send a few headers across. The class yyy, which is
derived from SoapHeader, contains two members called 'name' and 'pass'. They
are initialized to the values 'vijay' and 'sonal', respectively. These are the
values that are required to be sent across in the header. Thus, the basic
principle is to derive a class from SoapHeader, and initiate a list of
variables in it, whose values are to be sent across.
Thereafter, the function abc is
called. It is lodged in the class aaa. The only modification that is carried
out to this function is, the addition of an extra attribute named
SoapHeaderAttribute, containing the object b, since it holds the values that
are to be sent across in the header. Everything else remains unchanged.
The SOAP request reaffirms the
fact that the headers are actually sent across. As usual, the Envelope drifts
into sight first, followed by the SOAP Header. Since the name of the class is
yyy, it becomes the parent for the two child elements named 'name' and 'pass',
which contain the values of 'vijay' and 'sonal', respectively. In this case,
the headers are not sent back.
Thus, custom headers can easily
be created with the help of any user-defined content. For instance, the client
can send the userid and password over in the header. From the SOAP point of
view, the mechanism employed by the server to handle this information is of no
consequence at all.
Let us now expound the asmx file
more comprehensively by perusing another example.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
public class zzz {
public yyy z;
[WebMethod]
[SoapHeader("z")]
public int abc(int i, int j)
{
int k = z.name.Length;
int l = z.pass.Length;
return k + l;
}
}
public class yyy : SoapHeader
{
public string name;
public string pass;
}
Output
10
In the asmx file, the class yyy
is derived from the SoapHeader. This is akin to what was witnessed in the
client program. Thereafter, an object z is created as an instance of the yyy
class.
The function abc has been
provided with an extra attribute named SoapHeader, which in very clear-cut
terms, specifies that the name of the object is z, and that it contains the
soap headers in the string format.
When the function abc gets
called, the system first initializes the object z to the values found in the
Header. In our case, thereafter, we merely determine the length of the two
header values that are sent across, and add these up. Thus, the output obtained
is 10.
However in real life, there
exists ample flexibility for you to act as you desire, when faced with such a
situation. Having unraveled the mystery of the client program, you would
realise that the concept of web service can be comprehended with effortless
ease.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
public class zzz
{
public yyy z;
[WebMethod]
[SoapHeader("z")]
public void abc()
{
z.name = "No";
z.pass = "yes";
}
}
public class yyy : SoapHeader
{
public string name;
public string pass;
}
aa.wsdl
<s:schema attributeFormDefault="qualified"
elementFormDefault="qualified"
targetNamespace="http://tempuri.org/">
<s:element
name="abc">
<s:complexType />
</s:element>
<s:element
name="abcResponse">
<s:complexType />
</s:element>
<s:element
name="yyy" type="s0:yyy" />
<s:complexType
name="yyy">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
name="name" nillable="true" type="s:string" />
<s:element minOccurs="1" maxOccurs="1"
name="pass" nillable="true" type="s:string" />
</s:sequence>
</s:complexType>
</s:schema>
<message name="abcyyy">
<part name="yyy" element="s0:yyy" />
</message>
<operation name="abc">
<soap:operation soapAction="http://tempuri.org/abc"
style="document" />
<input>
<soap:body
use="literal" />
<soap:header n1:required="true"
message="s0:abcyyy" part="yyy" use="literal"
xmlns:n1="http://schemas.xmlsoap.org/wsdl/" />
</input>
<output>
<soap:body
use="literal" />
</output>
</operation>
a.cs
using System;
using System.Web.Services.Protocols;
using System.Web.Services;
public class aaa :
System.Web.Services.Protocols.SoapHttpClientProtocol
{
public static void Main()
{
zzz a= new zzz();
a.yyyValue = new yyy();
a.yyyValue.name="vijay" ;
a.yyyValue.pass="sonal" ;
a.abc();
}
}
zzz.cs
public class zzz :
System.Web.Services.Protocols.SoapHttpClientProtocol {
public yyy yyyValue;
[System.Web.Services.Protocols.SoapHeaderAttribute("yyyValue")]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/abc")]
public void abc()
{
this.Invoke("abc", new object[0]);
}
}
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://tempuri.org/",
IsNullable=false)]
public class yyy : SoapHeader
{
public string name;
public string pass;
}
The SOAP request and the
response packet remain the same as seen in the earlier program.
Once again, our primary focus
remains impaled on the asmx file. The class yyy is derived from the class
SoapHeader. Therefore, the soap headers are visible in the packet. The elements
stipulated here are sent across as tags within the header. In this case, the
header comprises of two elements, viz. 'name' and 'pass'. Prior to every
function, we need to specify the headers that the function shall receive.
The function abc shall receive
its headers in an object z, which is an instance of the class yyy. It has been
provided as a string parameter, because the SoapHeader specifies that the
object name should be in a string format. The function abc merely modifies the
values contained in 'name' and 'pass'. These newly assigned values are then
sent across. In the normal course, we would have accessed the values that are
sent across, and then modified them.
You would have discerned vast
variations in the aa.wsdl file. This is because, when we encounter a class
derived from SoapHeader, IIS is alerted to the fact that Headers too have been
sent across.
The element abc has no
parameters; therefore, it has an empty complexType. Since the name of the class
is yyy, an element called yyy is created. The type of this element is also yyy.
It does not possess any other content. Next, a complexType yyy is created to
accommodate two elements called 'name' and 'pass', which correspond to the
variables in the class yyy.
Upto this point, in the wsdl
file, there has been no indication that the class yyy has been derived from the
SoapHeader class. Besides, there is no allusion to the fact that yyy represents
a class that is responsible for sending the Headers across. This is because the
schema section looks exclusively at schemas, and Headers have no representation
in a schema.
Now, we revert our attention
right back to the message element, which was enucleated in one of the earlier
chapters. Apart from the two messages sighted in the earlier programs, one more
message is sent over. This message contains only a single part named yyy. The
element represented by this part is called yyy. It is of type yyy, which is the
class derived from SoapHeader. Thus, every class derived from SoapHeader is
provided with a message element.
The operation abc has a simple
input and output section. The input section has a Body element, comprising of a
Header element from the soap namespace. The 'required' member with the value of
'true', mandates the presence of a header. The message is named as abcyyy. This
name is formed by the conjunction of the name of the function, and the name of
the header class. It specifies the format of the header.
Thus, headers are dispatched as
per the functions. This statement will be proved beyond doubt in one of the
forthcoming examples. The yyy part is responsible for the message, since this
is where the element is expressly specified. The 'use' attribute works in a
manner analogous to the 'operation' element.
As always, a new instance of the
zzz class is created in the client program, and the yyyValue member is initialized
to a yyy object. The header class is yyy. Therefore, the member that represents
this header in the proxy is named as yyyValue, i.e. classname plus the word
'Value'. The 'name' and 'pass' members are initialized to 'vijay' and 'sonal',
respectively. When the function abc is called, the headers are sent across, as
a consequence of the attributes created in the proxy.
Let us now explore the proxy
generated by the wsdl file. The class yyy is sighted at the absolute end of the
file that has been derived from SoapHeader. This is due to the fact that
'operation' contains the Header tag for the element yyy. The function remains
the same, except that the SoapHeaderAttribute has been added. The string
parameter contains the name of the object that would carry the header data
yyyValue. All functions that carry this attribute, send the headers across the
wire.
So far, the SOAP Headers have
been gliding in one direction only, i.e. from the client to the server. It is
our intent to reverse this direction in the next example, i.e. send the Headers
back from the server to the client.
In order to achieve this, we
effect a minor modification to the asmx file, by changing the SoapHeader
attribute to the following:
[SoapHeader("z",Direction=SoapHeaderDirection.InOut)]
aa.wsdl
<output>
<soap:body use="literal" />
<soap:header n1:required="true"
message="s0:abcyyy" part="yyy" use="literal"
xmlns:n1="http://schemas.xmlsoap.org/wsdl/" />
</output>
a.cs
a.abc();
Console.WriteLine(a.yyyValue.name);
Console.WriteLine(a.yyyValue.pass);
Output
No
Yes
zzz.cs
[System.Web.Services.Protocols.SoapHeaderAttribute("yyyValue",
Direction=System.Web.Services.Protocols.SoapHeaderDirection.InOut)]
soap response
<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:Header>
<yyy xmlns="http://tempuri.org/">
<name>No</name>
<pass>yes</pass>
</yyy>
</soap:Header>
<soap:Body>
<abcResponse
xmlns="http://tempuri.org/" />
</soap:Body>
</soap:Envelope>
By merely adding one more
property to the SoapHeader attribute, we have been able to add a header to the
SOAP response packet. In the asmx file, the values of the 'name' and the 'pass'
properties are initialized to 'no' and 'yes', respectively.
Further, since the Direction
property has been set to 'InOut', the headers now flow in both directions. The
default value of the Direction property is 'In'. The third possible value that
it can assume is 'Out', which is normally used in the response packet only.
The wsdl file does not display
the word 'InOut'. Moreover, the output element in the operation for abc, which
earlier contained a single member body, now has an additional member - the
header, along with its attributes.
Since the wsdl file contains the
Header element in both the input and the output elements, the Direction
property in the proxy is set to 'InOut'. The client program adds two lines at
the end, to display the values of the 'pass' and 'name' members, which are
modified to 'yes' and 'no', respectively.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
public class zzz
{
public yyy z;
public xxx z1;
[WebMethod]
[SoapHeader("z")]
[SoapHeader("z1")]
public void abc()
{
}
[WebMethod]
[SoapHeader("z1")]
public void pqr()
{
}
}
public class yyy : SoapHeader
{
public string name;
public string pass;
}
public class xxx : SoapHeader
{
public string aa;
}
a.cs
using System;
using System.Web.Services.Protocols;
using System.Web.Services;
public class aaa :
System.Web.Services.Protocols.SoapHttpClientProtocol
{
public static void Main()
{
zzz a= new zzz();
a.yyyValue = new yyy();
a.xxxValue = new xxx();
a.yyyValue.name="vijay" ;
a.yyyValue.pass="sonal" ;
a.xxxValue.aa="mukhi" ;
a.abc();
a.pqr();
}
}
aa.wsdl
<message
name="abcyyy">
<part name="yyy" element="s0:yyy" />
</message>
<message
name="abcxxx">
<part name="xxx" element="s0:xxx" />
</message>
<message
name="pqrxxx">
<part name="xxx" element="s0:xxx" />
</message>
<operation name="abc">
<soap:operation soapAction="http://tempuri.org/abc"
style="document" />
<input>
<soap:body
use="literal" />
<soap:header
n1:required="true" message="s0:abcyyy" part="yyy"
use="literal" xmlns:n1="http://schemas.xmlsoap.org/wsdl/"
/>
<soap:header
n1:required="true" message="s0:abcxxx" part="xxx"
use="literal" xmlns:n1="http://schemas.xmlsoap.org/wsdl/"
/>
</input>
<output>
<soap:body
use="literal" />
</output>
</operation>
<operation name="pqr">
<soap:operation soapAction="http://tempuri.org/pqr"
style="document" />
<input>
<soap:body
use="literal" />
<soap:header n1:required="true"
message="s0:pqrxxx" part="xxx" use="literal"
xmlns:n1="http://schemas.xmlsoap.org/wsdl/" />
</input>
<output>
<soap:body
use="literal" />
</output>
</operation>
zzz.cs
public class zzz :
System.Web.Services.Protocols.SoapHttpClientProtocol {
public yyy yyyValue;
public xxx xxxValue;
[System.Web.Services.Protocols.SoapHeaderAttribute("yyyValue")]
[System.Web.Services.Protocols.SoapHeaderAttribute("xxxValue")]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/abc"]
public void abc()
{
this.Invoke("abc", new object[0]);
}
[System.Web.Services.Protocols.SoapHeaderAttribute("xxxValue")]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(http://tempuri.org/pqr)]
public void pqr()
{
this.Invoke("pqr", new object[0]);
}
}
public class yyy : SoapHeader
{
public string name;
public string pass;
}
public class xxx : SoapHeader
{
public string aa;
}
Soap Request 1
<soap:Header>
<xxx xmlns="http://tempuri.org/">
<aa>mukhi</aa>
</xxx>
<yyy xmlns="http://tempuri.org/">
<name>vijay</name>
<pass>sonal</pass>
</yyy>
</soap:Header>
Soap Request 2
<soap:Header>
<xxx xmlns="http://tempuri.org/">
<aa>mukhi</aa>
</xxx>
</soap:Header>
Though the above example may
span numerous pages, its sole purpose is to demonstrate the fundamental concept
that, the SOAP header packets get created in accordance with the attributes
placed before the function. Thus, the function that is called, determines the
SOAP headers that get sent across.
There are two classes, xxx and
yyy, both of which are derived from SoapHeader. Thereafter, two objects
representing the headers, viz. z and z1, are created when the SOAP payload
arrives from the client to the server. The names of these two variables are
inconsequential, and do not get reflected in the wsdl file. The Attribute
SoapHeader is most crucial, as this attribute determines what is to be sent
across.
The function abc utilizes this
attribute twice, and therefore, the contents of both the classes, yyy and xxx,
are sent across as headers. Thus, a call to the function abc will result in the
transmission of three elements in the header, viz. 'name', 'pass' and 'aa'. On
the other hand, the function pqr would have only a single element in the
header, i.e. 'aa', since it has only one SoapHeader attribute.
The wsdl file reinforces the
statement that we have just made. The function abc has two headers; hence, two
message elements named 'abcyyy' and 'abcxxx' are visible, unlike in the earlier
program. Since the function pqr has only a single header, a single part named
'pqrxxx' is visible.
Every function embodies its own
operation element. To begin with, the function abc in the input transmits two
headers across. Hence, the header element is displayed twice. The first is a
message 'abcyyy', while the second is 'abcxxx'. The output does not have any
headers. Thus, no header element exists. We could possibly have combined the
last two programs and sent only a single header across, using the direction
property.
The operation called pqr has a
single header named xxx, which is to be sent across. The value of the direction
property is 'In', by default. We have not displayed the schemas, since they
fail to introduce anything innovative. The schemas that have been created for
yyy and xxx are identical.
The client program is acquainted
with the fact that there exist two objects. Hence, yyyValue and xxxValue are
created, to represent the objects z and z1, respectively. Then, using the
object 'a', the functions abc and pqr are called. In the proxy, we now have two
classes, yyy and xxx, which are derived from the SoapHeader class.
Correspondingly, we also have
two objects named xxxValue and yyyValue, to represent them. The proxy object is
very similar to the asmx file. The function abc sends two headers across. Thus,
it has two SoapHeaderAttribute attributes. The function pqr has only a single
SoapHeaderAttribute.
Here, a round trip gets
executed, in that, the asmx file gets converted to wsdl, which in turn, becomes
the proxy. The first SOAP request is that of the function abc. We are sending
two disparate headers from two different classes. Thus, the element 'aa'
becomes a part of the xxx class, while the 'name' and 'pass' members become
part of the yyy class.
The headers are not bundled and
sent across. Instead, they are dispatched depending upon the class that they
belong to. Therefore, they could enjoy the same names, and yet be
differentiated at the receiving end. The second packet represents the function
pqr, where only a single header is sent across. Each packet represents a single
function.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
public class zzz {
public yyy z;
[WebMethod]
[SoapHeader("z", Required=true)]
public void abc()
{
}
}
public class yyy : SoapHeader
{
public string name;
public string pass;
}
aa.wsdl
<soap:header n1:required="true"
message="s0:abcyyy" part="yyy" use="literal"
xmlns:n1="http://schemas.xmlsoap.org/wsdl/" />
a.cs
using System;
using System.Web.Services.Protocols;
using System.Web.Services;
public class aaa :
System.Web.Services.Protocols.SoapHttpClientProtocol {
public static void Main()
{
zzz a= new zzz();
a.abc();
}
}
The asmx file remains unaltered,
except that the function abc has the SOAP header set to the object 'z', and the
Required property set to 'true'. The value of default signifies that it is
mandatory for the client to send the header across, before it can send the
packet over.
The aa.wsdl file shows only a
single line, where the header element contains a 'Required' attribute. By
default, it is set to a value of 'true'. In the a.cs file, since an instance of
the yyyValue object is not created, an exception is thrown.
Output
Unhandled Exception: System.Web.Services.Protocols.SoapHeaderException:
Required field/property zzz.yyyValue of SOAP header yyy was not set by the
client prior to making the call.
a.asmx
[SoapHeader("z", Required=false)]
aa.wsdl
<soap:header message="s0:abcyyy"
part="yyy" use="literal" />
zzz.cs
[System.Web.Services.Protocols.SoapHeaderAttribute("yyyValue",
Required=false)]
soap request
<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>
As a result of transforming the
value of the Required attribute to 'false', the header element of the wsdl file
that gets generated, is by far much simpler, and contains very few attributes.
It does not have any attribute named Required. The client program remains
unchanged.
The proxy that is generated,
almost mirrors the asmx file. Hence, the Required attribute is visible, along
with the SoapHeader attribute, above the function abc. However, the SOAP
request does not specify any Header details. Thus, in the final analysis, the
value in the Required attribute determines whether a SOAP header should be sent
across or not.
Change the code in the Main
function within a.cs to the following
public static void Main() {
zzz a= new zzz();
a.yyyValue = new yyy();
a.abc();
}
soap request
<soap:Header>
<yyy xmlns="http://tempuri.org/">
<name
xsi:nil="true" />
<pass
xsi:nil="true" />
</yyy>
</soap:Header>
Earlier, since the yyyValue was
not initialized to any object, an exception was thrown. Now, a yyy object is
created, but none of the members are initialized. The SOAP packet discloses the
Header details, but it uses the 'nil' attribute to signify that they are empty
or null. If we change the Required attribute to 'true' in the asmx file, the
SOAP request displayed above, will be generated.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
public class zzz
{
[WebMethod]
public string abc()
{
return "hell";
}
}
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" ;
string i = a.abc();
Console.WriteLine(i);
}
[System.Web.Services.Protocols.SoapHeaderAttribute("b")]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/abc")]
public string abc()
{
object[] results = this.Invoke("abc", new object[0]);
return ((string)(results[0]));
}
public aaa()
{
this.Url = "http://localhost:8080/a.asmx";
}
}
public class yyy : SoapHeader
{
public string name;
public string pass;
}
Output
hell
The above program, which is a C#
client, is a near clone of the first C# program of the chapter. Any difference,
if at all, lies in the fact that the function abc returns a string. The above
program compiles by itself, without any wsdl file.
The salient point to be noted
here is that, despite sending a header across, the asmx file seems unable to
recognize or acknowledge the header's presence. Neither are there any errors
generated.
a.asmx
<%@ WebService Language="C#" class="zzz"
%>
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
public class zzz
{
public SoapUnknownHeader u;
[WebMethod]
[SoapHeader("u", Required=false)]
public string abc()
{
return u.Element.InnerXml + u.Element.Name;
}
}
Output
<name xmlns="http://tempuri.org/">vijay</name>
<pass
xmlns="http://tempuri.org/">sonal</pass>
yyy
In our asmx program, an object
'u' of the class SoapUnknownHeader has been freshly appended. Therefore, before
the function abc gets called, this object contains all the headers that are not
known. This is on account of the SoapHeader attribute having the object name of
'u'. In our case, we have only a singular header named 'yyy'.
The value of the Required
parameter should be 'false', failing which, an unknown header would have to be
sent across manually. In the function, the contents of the header are
displayed, by merely employing the Element member of type XmlElement. This is
achieved by displaying the InnerXml member and the 'name' property that
retrieves the name of the header, i.e. yyy.
A property called DidUnderstand,
which accepts a boolean value, is set by the asmx file, in order to convey that
it has been able to comprehend the unknown header. This is not reflected in the
SOAP packet that is sent across.
Add the following property to
a.cs
a.b.Actor="mukhi" ;
Soap request
<soap:Header>
<yyy soap:actor="mukhi"
xmlns="http://tempuri.org/">
Now, we add one more property
called Actor, which is part of the SoapHeader class and initialize it to mukhi.
This results in the creation of an attribute named 'actor', in the SOAP request
packet. The 'actor' attribute is SOAP's way of determining who should be
reading and understanding the Header.
Here, we have assigned it a
value of 'mukhi', but in practical applications, it should contain a uri.
This is the final illustration
that carries out authentication by using an aspx file, instead of a client
program.
a.aspx
<%@ Page Language="C#"%>
<%@ Import Namespace="Security"%>
<%@ Import Namespace="System.Web.Services.Protocols"
%>
<script runat=server>
public void Page_Load()
{
s1.InnerHtml = "";
}
public void abc(Object sender, EventArgs z)
{
zzz s = new zzz();
aaa a = new aaa();
a.User = us.Value;
a.Password = pass.Value;
s.aaaValue = a;
string r = s.abc();
s1.InnerHtml = r;
}
</script>
<form runat=server>
Username:
<input type=text id=us runat=server />
<br>
Password:
<input type=text id=pass runat=server />
<br>
<input type=submit runat=server OnServerClick="abc"
value="Login" />
</form>
result: <font color=red>
<span id=s1 runat=server />
a.asmx
<%@ WebService Language="C#" Class="zzz"
%>
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
public class aaa : SoapHeader
{
public string User;
public string Password;
}
public class zzz : WebService
{
public aaa z;
[WebMethod]
[SoapHeader("z")]
public string abc()
{
if (z.User == "vijay" &&
z.Password=="mukhi")
return "all is okay";
if (z.User == "sonal" &&
z.Password=="bad")
return "all is not okay";
return "somethings gone wrong";
}
}
a.bat
del *.dll
wsdl a.wsdl /namespace:Security
csc.exe /t:library
zzz.cs
md c:\inetpub\wwwroot\bin
copy zzz.dll c:\inetpub\wwwroot\bin\
SOAP request
<soap:Header>
- <aaa xmlns="http://tempuri.org/">
<User>sonal</User>
<Password>mukhi</Password>
</aaa>
</soap:Header>
- <soap:Body>
<abc
xmlns="http://tempuri.org/" />
</soap:Body>
SOAP response
<abcResponse xmlns="http://tempuri.org/">
<abcResult>something gone wrong</abcResult>
</abcResponse>
We commence by creating the file
a.aspx in the C:\inerpub\wwwroot folder. The language is explicitly specified
as C#, because the default language is VB. This is followed by a series of
unproductive Import statements, to enable you to abandon writing the namespace
names over and over again. The code will eventually emanate from the Namespace
Security. The whole world uses namespaces, so why should we be called the
nonconformists or the odd ones out?
The 'runat' attribute is
mandatory, although we are aware that everything runs only at the server end.
The Page_Load function gets called each time the page is sent from the server
to the client.
Thereafter, we simply initialize
the s1 element, which is a span element, to a blank value. We could have used a
label instead, but on a personal whim, we preferred the span element.
On the screen, the words
'username' and 'password' are displayed along with two textboxes, having ids of
'us' and 'pass', respectively. The user is expected to key-in a user id and a
password. The 'type' for the password box should be specified as 'password' to
avoid displaying the characters, since they are being entered. Moreover, there
is a button that calls the function abc on the server, every time the user
clicks on it.
Before advancing any further
with deciphering the code contained in the function abc, let us examine the
asmx file. The class zzz has a function named abc, which receives a soap header
called 'aaa', in the form of an object 'z'. This header class aaa has two
members, User and Password.
The function abc ascertains
whether the User and Password contain specific pre-defined values or not. The
activity performed by this function has nothing in common with the function abc
of the aspx file.
If the username is entered as
'vijay', and the password is entered as 'mukhi', an OK message is received.
However, if the user name is specified as 'sonal', and the password is
specified as 'bad', an altogether different message is received. If any other
values are specified, a default message is displayed. In real life, the values
are compared with data contained in a database.
Once the asmx file is done with,
create a wsdl file as before, by entering the url of
http://localhost/a.asmx?WSDL in the browser. The only difference here is that
the wsdl file must be saved as 'a.wsdl' in the c:\inetpub\wwwroot\bin folder.
The batch file first calls the wsdl program, and the /namespace option places
all the code of the class zzz in the namespace security. Thereafter, zzz.cs is
compiled into a dll file, as before.
Once the stage is set, load the
browser and enter the url as http://localhost/a.aspx. In the textboxes, enter
the values for the userid and password, and then, click on the button. This
calls the function abc.
The function abc in the aspx
file first creates an object 's', which is an instance of the class zzz. The
rationale behind placing the class in the bin folder is that, the system
normally searches this location for classes.
Another object named aa is
created, which is an instance of the SoapHeader class. Then, the User and
Password members are initialized to the text specified in the textboxes,
identified with the ids of 'us' and 'pass', respectively.
Once this is accomplished, the
aaaValue member, which represents the header object in class zzz, is
initialized to 'a', which is an instance of the SoapHeader class. In this
manner, all the initialized values are passed to the header.
Thereafter, the function abc
sends the header across, and the value returned by the remoted function is
placed into the span's InnerHtml member.
You can change the port number to 8080 in the wsdl file, in order to observe the flow of packets in the trace program. Thus, the SOAP request and response remain the same, irrespective of whether we work with a client C# program or with an aspx file.